/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "IoPerfCollection.h" #include "MockProcStatCollector.h" #include "MockUidStatsCollector.h" #include "MockWatchdogServiceHelper.h" #include "PackageInfoTestUtils.h" #include #include #include #include #include #include #include #include #include namespace android { namespace automotive { namespace watchdog { namespace { using ::android::RefBase; using ::android::sp; using ::android::automotive::watchdog::internal::PackageInfo; using ::android::base::ReadFdToString; using ::android::base::Result; using ::testing::_; using ::testing::AllOf; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::ExplainMatchResult; using ::testing::Field; using ::testing::IsSubsetOf; using ::testing::Matcher; using ::testing::Return; using ::testing::Test; using ::testing::UnorderedElementsAreArray; using ::testing::VariantWith; constexpr int kTestTopNStatsPerCategory = 5; constexpr int kTestTopNStatsPerSubcategory = 5; constexpr int kTestMaxUserSwitchEvents = 3; MATCHER_P(IoStatsEq, expected, "") { return ExplainMatchResult(AllOf(Field("bytes", &UserPackageStats::IoStats::bytes, ElementsAreArray(expected.bytes)), Field("fsync", &UserPackageStats::IoStats::fsync, ElementsAreArray(expected.fsync))), arg, result_listener); } MATCHER_P(ProcessValueEq, expected, "") { return ExplainMatchResult(AllOf(Field("comm", &UserPackageStats::ProcStats::ProcessValue::comm, Eq(expected.comm)), Field("value", &UserPackageStats::ProcStats::ProcessValue::value, Eq(expected.value))), arg, result_listener); } MATCHER_P(ProcStatsEq, expected, "") { std::vector> processValueMatchers; processValueMatchers.reserve(expected.topNProcesses.size()); for (const auto& processValue : expected.topNProcesses) { processValueMatchers.push_back(ProcessValueEq(processValue)); } return ExplainMatchResult(AllOf(Field("value", &UserPackageStats::ProcStats::value, Eq(expected.value)), Field("topNProcesses", &UserPackageStats::ProcStats::topNProcesses, ElementsAreArray(processValueMatchers))), arg, result_listener); } MATCHER_P(UserPackageStatsEq, expected, "") { const auto uidMatcher = Field("uid", &UserPackageStats::uid, Eq(expected.uid)); const auto packageNameMatcher = Field("genericPackageName", &UserPackageStats::genericPackageName, Eq(expected.genericPackageName)); return std::visit( [&](const auto& stats) -> bool { using T = std::decay_t; if constexpr (std::is_same_v) { return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher, Field("stats:IoStats", &UserPackageStats::stats, VariantWith( IoStatsEq(stats)))), arg, result_listener); } else if constexpr (std::is_same_v) { return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher, Field("stats:ProcStats", &UserPackageStats::stats, VariantWith( ProcStatsEq(stats)))), arg, result_listener); } *result_listener << "Unexpected variant in UserPackageStats::stats"; return false; }, expected.stats); } MATCHER_P(UserPackageSummaryStatsEq, expected, "") { const auto& userPackageStatsMatchers = [&](const std::vector& stats) { std::vector> matchers; for (const auto& curStats : stats) { matchers.push_back(UserPackageStatsEq(curStats)); } return ElementsAreArray(matchers); }; const auto& totalIoStatsArrayMatcher = [&](const int64_t expected[][UID_STATES]) { std::vector> matchers; for (int i = 0; i < METRIC_TYPES; ++i) { matchers.push_back(ElementsAreArray(expected[i], UID_STATES)); } return ElementsAreArray(matchers); }; return ExplainMatchResult(AllOf(Field("topNCpuTimes", &UserPackageSummaryStats::topNCpuTimes, userPackageStatsMatchers(expected.topNCpuTimes)), Field("topNIoReads", &UserPackageSummaryStats::topNIoReads, userPackageStatsMatchers(expected.topNIoReads)), Field("topNIoWrites", &UserPackageSummaryStats::topNIoWrites, userPackageStatsMatchers(expected.topNIoWrites)), Field("topNIoBlocked", &UserPackageSummaryStats::topNIoBlocked, userPackageStatsMatchers(expected.topNIoBlocked)), Field("topNMajorFaults", &UserPackageSummaryStats::topNMajorFaults, userPackageStatsMatchers(expected.topNMajorFaults)), Field("totalIoStats", &UserPackageSummaryStats::totalIoStats, totalIoStatsArrayMatcher(expected.totalIoStats)), Field("taskCountByUid", &UserPackageSummaryStats::taskCountByUid, IsSubsetOf(expected.taskCountByUid)), Field("totalCpuTimeMillis", &UserPackageSummaryStats::totalCpuTimeMillis, Eq(expected.totalCpuTimeMillis)), Field("totalMajorFaults", &UserPackageSummaryStats::totalMajorFaults, Eq(expected.totalMajorFaults)), Field("majorFaultsPercentChange", &UserPackageSummaryStats::majorFaultsPercentChange, Eq(expected.majorFaultsPercentChange))), arg, result_listener); } MATCHER_P(SystemSummaryStatsEq, expected, "") { return ExplainMatchResult(AllOf(Field("cpuIoWaitTimeMillis", &SystemSummaryStats::cpuIoWaitTimeMillis, Eq(expected.cpuIoWaitTimeMillis)), Field("cpuIdleTimeMillis", &SystemSummaryStats::cpuIdleTimeMillis, Eq(expected.cpuIdleTimeMillis)), Field("totalCpuTimeMillis", &SystemSummaryStats::totalCpuTimeMillis, Eq(expected.totalCpuTimeMillis)), Field("contextSwitchesCount", &SystemSummaryStats::contextSwitchesCount, Eq(expected.contextSwitchesCount)), Field("ioBlockedProcessCount", &SystemSummaryStats::ioBlockedProcessCount, Eq(expected.ioBlockedProcessCount)), Field("totalProcessCount", &SystemSummaryStats::totalProcessCount, Eq(expected.totalProcessCount))), arg, result_listener); } MATCHER_P(PerfStatsRecordEq, expected, "") { return ExplainMatchResult(AllOf(Field(&PerfStatsRecord::systemSummaryStats, SystemSummaryStatsEq(expected.systemSummaryStats)), Field(&PerfStatsRecord::userPackageSummaryStats, UserPackageSummaryStatsEq( expected.userPackageSummaryStats))), arg, result_listener); } const std::vector> constructPerfStatsRecordMatchers( const std::vector& records) { std::vector> matchers; for (const auto& record : records) { matchers.push_back(PerfStatsRecordEq(record)); } return matchers; } MATCHER_P(CollectionInfoEq, expected, "") { return ExplainMatchResult(AllOf(Field("maxCacheSize", &CollectionInfo::maxCacheSize, Eq(expected.maxCacheSize)), Field("records", &CollectionInfo::records, ElementsAreArray(constructPerfStatsRecordMatchers( expected.records)))), arg, result_listener); } MATCHER_P(UserSwitchCollectionInfoEq, expected, "") { return ExplainMatchResult(AllOf(Field("from", &UserSwitchCollectionInfo::from, Eq(expected.from)), Field("to", &UserSwitchCollectionInfo::to, Eq(expected.to)), Field("maxCacheSize", &UserSwitchCollectionInfo::maxCacheSize, Eq(expected.maxCacheSize)), Field("records", &UserSwitchCollectionInfo::records, ElementsAreArray(constructPerfStatsRecordMatchers( expected.records)))), arg, result_listener); } MATCHER_P(UserSwitchCollectionsEq, expected, "") { std::vector> userSwitchCollectionMatchers; for (const auto& curCollection : expected) { userSwitchCollectionMatchers.push_back(UserSwitchCollectionInfoEq(curCollection)); } return ExplainMatchResult(ElementsAreArray(userSwitchCollectionMatchers), arg, result_listener); } int countOccurrences(std::string str, std::string subStr) { size_t pos = 0; int occurrences = 0; while ((pos = str.find(subStr, pos)) != std::string::npos) { ++occurrences; pos += subStr.length(); } return occurrences; } std::tuple, UserPackageSummaryStats> sampleUidStats(int multiplier = 1) { /* The number of returned sample stats are less that the top N stats per category/sub-category. * The top N stats per category/sub-category is set to % during test setup. Thus, the default * testing behavior is # reported stats < top N stats. */ const auto int64Multiplier = [&](int64_t bytes) -> int64_t { return static_cast(bytes * multiplier); }; const auto uint64Multiplier = [&](uint64_t count) -> uint64_t { return static_cast(count * multiplier); }; std::vector uidStats{{.packageInfo = constructPackageInfo("mount", 1009), .cpuTimeMillis = int64Multiplier(50), .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/int64Multiplier(14'000), /*fgWrBytes=*/0, /*bgWrBytes=*/int64Multiplier(16'000), /*fgFsync=*/0, /*bgFsync=*/int64Multiplier(100)}, .procStats = {.cpuTimeMillis = int64Multiplier(50), .totalMajorFaults = uint64Multiplier(11'000), .totalTasksCount = 1, .ioBlockedTasksCount = 1, .processStatsByPid = {{/*pid=*/100, {/*comm=*/"disk I/O", /*startTime=*/234, /*cpuTimeMillis=*/int64Multiplier(50), /*totalMajorFaults=*/uint64Multiplier(11'000), /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/1}}}}}, {.packageInfo = constructPackageInfo("com.google.android.car.kitchensink", 1002001), .cpuTimeMillis = int64Multiplier(60), .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/int64Multiplier(3'400), /*fgWrBytes=*/0, /*bgWrBytes=*/int64Multiplier(6'700), /*fgFsync=*/0, /*bgFsync=*/int64Multiplier(200)}, .procStats = {.cpuTimeMillis = int64Multiplier(50), .totalMajorFaults = uint64Multiplier(22'445), .totalTasksCount = 5, .ioBlockedTasksCount = 3, .processStatsByPid = {{/*pid=*/1000, {/*comm=*/"KitchenSinkApp", /*startTime=*/467, /*cpuTimeMillis=*/int64Multiplier(25), /*totalMajorFaults=*/uint64Multiplier(12'345), /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/1}}, {/*pid=*/1001, {/*comm=*/"CTS", /*startTime=*/789, /*cpuTimeMillis=*/int64Multiplier(25), /*totalMajorFaults=*/uint64Multiplier(10'100), /*totalTasksCount=*/3, /*ioBlockedTasksCount=*/2}}}}}, {.packageInfo = constructPackageInfo("", 1012345), .cpuTimeMillis = int64Multiplier(100), .ioStats = {/*fgRdBytes=*/int64Multiplier(1'000), /*bgRdBytes=*/int64Multiplier(4'200), /*fgWrBytes=*/int64Multiplier(300), /*bgWrBytes=*/int64Multiplier(5'600), /*fgFsync=*/int64Multiplier(600), /*bgFsync=*/int64Multiplier(300)}, .procStats = {.cpuTimeMillis = int64Multiplier(100), .totalMajorFaults = uint64Multiplier(50'900), .totalTasksCount = 4, .ioBlockedTasksCount = 2, .processStatsByPid = {{/*pid=*/2345, {/*comm=*/"MapsApp", /*startTime=*/6789, /*cpuTimeMillis=*/int64Multiplier(100), /*totalMajorFaults=*/uint64Multiplier(50'900), /*totalTasksCount=*/4, /*ioBlockedTasksCount=*/2}}}}}, {.packageInfo = constructPackageInfo("com.google.radio", 1015678), .cpuTimeMillis = 0, .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/0, /*fgWrBytes=*/0, /*bgWrBytes=*/0, /*fgFsync=*/0, /*bgFsync=*/0}, .procStats = {.cpuTimeMillis = 0, .totalMajorFaults = 0, .totalTasksCount = 4, .ioBlockedTasksCount = 0, .processStatsByPid = { {/*pid=*/2345, {/*comm=*/"RadioApp", /*startTime=*/19789, /*cpuTimeMillis=*/0, /*totalMajorFaults=*/0, /*totalTasksCount=*/4, /*ioBlockedTasksCount=*/0}}}}}}; UserPackageSummaryStats userPackageSummaryStats{ .topNCpuTimes = {{1012345, "1012345", UserPackageStats::ProcStats{uint64Multiplier(100), {{"MapsApp", uint64Multiplier(100)}}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{uint64Multiplier(60), {{"CTS", uint64Multiplier(25)}, {"KitchenSinkApp", uint64Multiplier(25)}}}}, {1009, "mount", UserPackageStats::ProcStats{uint64Multiplier(50), {{"disk I/O", uint64Multiplier(50)}}}}}, .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, int64Multiplier(14'000)}, {0, int64Multiplier(100)}}}, {1012345, "1012345", UserPackageStats::IoStats{{int64Multiplier(1'000), int64Multiplier(4'200)}, {int64Multiplier(600), int64Multiplier(300)}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::IoStats{{0, int64Multiplier(3'400)}, {0, int64Multiplier(200)}}}}, .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, int64Multiplier(16'000)}, {0, int64Multiplier(100)}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::IoStats{{0, int64Multiplier(6'700)}, {0, int64Multiplier(200)}}}, {1012345, "1012345", UserPackageStats::IoStats{{int64Multiplier(300), int64Multiplier(5'600)}, {int64Multiplier(600), int64Multiplier(300)}}}}, .topNIoBlocked = {{1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{3, {{"CTS", 2}, {"KitchenSinkApp", 1}}}}, {1012345, "1012345", UserPackageStats::ProcStats{2, {{"MapsApp", 2}}}}, {1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}}}, .topNMajorFaults = {{1012345, "1012345", UserPackageStats::ProcStats{uint64Multiplier(50'900), {{"MapsApp", uint64Multiplier(50'900)}}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{uint64Multiplier(22'445), {{"KitchenSinkApp", uint64Multiplier(12'345)}, {"CTS", uint64Multiplier(10'100)}}}}, {1009, "mount", UserPackageStats::ProcStats{uint64Multiplier(11'000), {{"disk I/O", uint64Multiplier(11'000)}}}}}, .totalIoStats = {{int64Multiplier(1'000), int64Multiplier(21'600)}, {int64Multiplier(300), int64Multiplier(28'300)}, {int64Multiplier(600), int64Multiplier(600)}}, .taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}}, .totalCpuTimeMillis = int64Multiplier(48'376), .totalMajorFaults = uint64Multiplier(84'345), .majorFaultsPercentChange = 0.0, }; return std::make_tuple(uidStats, userPackageSummaryStats); } std::tuple sampleProcStat(int multiplier = 1) { const auto int64Multiplier = [&](int64_t bytes) -> int64_t { return static_cast(bytes * multiplier); }; const auto uint64Multiplier = [&](uint64_t bytes) -> uint64_t { return static_cast(bytes * multiplier); }; const auto uint32Multiplier = [&](uint32_t bytes) -> uint32_t { return static_cast(bytes * multiplier); }; ProcStatInfo procStatInfo{/*stats=*/{int64Multiplier(2'900), int64Multiplier(7'900), int64Multiplier(4'900), int64Multiplier(8'900), /*ioWaitTimeMillis=*/int64Multiplier(5'900), int64Multiplier(6'966), int64Multiplier(7'980), 0, 0, int64Multiplier(2'930)}, /*ctxtSwitches=*/uint64Multiplier(500), /*runnableCnt=*/uint32Multiplier(100), /*ioBlockedCnt=*/uint32Multiplier(57)}; SystemSummaryStats systemSummaryStats{/*cpuIoWaitTimeMillis=*/int64Multiplier(5'900), /*cpuIdleTimeMillis=*/int64Multiplier(8'900), /*totalCpuTimeMillis=*/int64Multiplier(48'376), /*contextSwitchesCount=*/uint64Multiplier(500), /*ioBlockedProcessCount=*/uint32Multiplier(57), /*totalProcessCount=*/uint32Multiplier(157)}; return std::make_tuple(procStatInfo, systemSummaryStats); } } // namespace namespace internal { class IoPerfCollectionPeer final : public RefBase { public: explicit IoPerfCollectionPeer(sp collector) : mCollector(collector) {} IoPerfCollectionPeer() = delete; ~IoPerfCollectionPeer() { mCollector->terminate(); mCollector.clear(); } Result init() { return mCollector->init(); } void setTopNStatsPerCategory(int value) { mCollector->mTopNStatsPerCategory = value; } void setTopNStatsPerSubcategory(int value) { mCollector->mTopNStatsPerSubcategory = value; } void setMaxUserSwitchEvents(int value) { mCollector->mMaxUserSwitchEvents = value; } const CollectionInfo& getBoottimeCollectionInfo() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mBoottimeCollection; } const CollectionInfo& getPeriodicCollectionInfo() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mPeriodicCollection; } const std::vector& getUserSwitchCollectionInfos() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mUserSwitchCollections; } const CollectionInfo& getWakeUpCollectionInfo() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mWakeUpCollection; } const CollectionInfo& getCustomCollectionInfo() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mCustomCollection; } private: sp mCollector; }; } // namespace internal class IoPerfCollectionTest : public Test { protected: void SetUp() override { mMockUidStatsCollector = sp::make(); mMockProcStatCollector = sp::make(); mCollector = sp::make(); mCollectorPeer = sp::make(mCollector); ASSERT_RESULT_OK(mCollectorPeer->init()); mCollectorPeer->setTopNStatsPerCategory(kTestTopNStatsPerCategory); mCollectorPeer->setTopNStatsPerSubcategory(kTestTopNStatsPerSubcategory); mCollectorPeer->setMaxUserSwitchEvents(kTestMaxUserSwitchEvents); } void TearDown() override { mMockUidStatsCollector.clear(); mMockProcStatCollector.clear(); mCollector.clear(); mCollectorPeer.clear(); } void checkDumpContents(int wantedEmptyCollectionInstances) { TemporaryFile dump; ASSERT_RESULT_OK(mCollector->onDump(dump.fd)); checkDumpFd(wantedEmptyCollectionInstances, dump.fd); } void checkCustomDumpContents() { TemporaryFile dump; ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(dump.fd)); checkDumpFd(/*wantedEmptyCollectionInstances=*/0, dump.fd); } private: void checkDumpFd(int wantedEmptyCollectionInstances, int fd) { lseek(fd, 0, SEEK_SET); std::string dumpContents; ASSERT_TRUE(ReadFdToString(fd, &dumpContents)); ASSERT_FALSE(dumpContents.empty()); ASSERT_EQ(countOccurrences(dumpContents, kEmptyCollectionMessage), wantedEmptyCollectionInstances) << "Dump contents: " << dumpContents; } protected: sp mMockUidStatsCollector; sp mMockProcStatCollector; sp mCollector; sp mCollectorPeer; }; TEST_F(IoPerfCollectionTest, TestOnBoottimeCollection) { const auto [uidStats, userPackageSummaryStats] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK( mCollector->onBoottimeCollection(now, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getBoottimeCollectionInfo(); const CollectionInfo expected{ .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Boottime collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3)) << "Periodic, wake-up and user-switch collections shouldn't be reported"; } TEST_F(IoPerfCollectionTest, TestOnWakeUpCollection) { const auto [uidStats, userPackageSummaryStats] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK( mCollector->onWakeUpCollection(now, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getWakeUpCollectionInfo(); const CollectionInfo expected{ .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Wake-up collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3)) << "Boot-time, periodic, and user-switch collections shouldn't be reported"; } TEST_F(IoPerfCollectionTest, TestOnSystemStartup) { const auto [uidStats, userPackageSummaryStats] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillRepeatedly(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK( mCollector->onBoottimeCollection(now, mMockUidStatsCollector, mMockProcStatCollector)); ASSERT_RESULT_OK( mCollector->onWakeUpCollection(now, mMockUidStatsCollector, mMockProcStatCollector)); auto actualBoottimeCollection = mCollectorPeer->getBoottimeCollectionInfo(); auto actualWakeUpCollection = mCollectorPeer->getWakeUpCollectionInfo(); EXPECT_THAT(actualBoottimeCollection.records.size(), 1) << "Boot-time collection records is empty."; EXPECT_THAT(actualWakeUpCollection.records.size(), 1) << "Wake-up collection records is empty."; ASSERT_RESULT_OK(mCollector->onSystemStartup()); actualBoottimeCollection = mCollectorPeer->getBoottimeCollectionInfo(); actualWakeUpCollection = mCollectorPeer->getWakeUpCollectionInfo(); EXPECT_THAT(actualBoottimeCollection.records.size(), 0) << "Boot-time collection records is not empty."; EXPECT_THAT(actualWakeUpCollection.records.size(), 0) << "Wake-up collection records is not empty."; } TEST_F(IoPerfCollectionTest, TestOnUserSwitchCollection) { auto [uidStats, userPackageSummaryStats] = sampleUidStats(); auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(now, 100, 101, mMockUidStatsCollector, mMockProcStatCollector)); const auto& actualInfos = mCollectorPeer->getUserSwitchCollectionInfos(); const auto& actual = actualInfos[0]; UserSwitchCollectionInfo expected{ { .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }, .from = 100, .to = 101, }; EXPECT_THAT(actualInfos.size(), 1); EXPECT_THAT(actual, UserSwitchCollectionInfoEq(expected)) << "User switch collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); // Continuation of the previous user switch collection std::vector nextUidStats = { {.packageInfo = constructPackageInfo("mount", 1009), .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/5'000, /*fgWrBytes=*/0, /*bgWrBytes=*/3'000, /*fgFsync=*/0, /*bgFsync=*/50}, .procStats = {.cpuTimeMillis = 50, .totalMajorFaults = 6'000, .totalTasksCount = 1, .ioBlockedTasksCount = 2, .processStatsByPid = {{/*pid=*/100, {/*comm=*/"disk I/O", /*startTime=*/234, /*cpuTimeMillis=*/50, /*totalMajorFaults=*/6'000, /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/2}}}}}}; UserPackageSummaryStats nextUserPackageSummaryStats = { .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 5'000}, {0, 50}}}}, .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 3'000}, {0, 50}}}}, .topNIoBlocked = {{1009, "mount", UserPackageStats::ProcStats{2, {{"disk I/O", 2}}}}}, .topNMajorFaults = {{1009, "mount", UserPackageStats::ProcStats{6'000, {{"disk I/O", 6'000}}}}}, .totalIoStats = {{0, 5'000}, {0, 3'000}, {0, 50}}, .taskCountByUid = {{1009, 1}}, .totalCpuTimeMillis = 48'376, .totalMajorFaults = 6'000, .majorFaultsPercentChange = (6'000.0 - 84'345.0) / 84'345.0 * 100.0, }; ProcStatInfo nextProcStatInfo = procStatInfo; SystemSummaryStats nextSystemSummaryStats = systemSummaryStats; nextProcStatInfo.contextSwitchesCount = 300; nextSystemSummaryStats.contextSwitchesCount = 300; EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(nextUidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(nextProcStatInfo)); now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(now, 100, 101, mMockUidStatsCollector, mMockProcStatCollector)); auto& continuationActualInfos = mCollectorPeer->getUserSwitchCollectionInfos(); auto& continuationActual = continuationActualInfos[0]; expected = { { .maxCacheSize = std::numeric_limits::max(), .records = {{.systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats}, {.systemSummaryStats = nextSystemSummaryStats, .userPackageSummaryStats = nextUserPackageSummaryStats}}, }, .from = 100, .to = 101, }; EXPECT_THAT(continuationActualInfos.size(), 1); EXPECT_THAT(continuationActual, UserSwitchCollectionInfoEq(expected)) << "User switch collection info after continuation doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3)) << "Boot-time, wake-up and periodic collections shouldn't be reported"; } TEST_F(IoPerfCollectionTest, TestUserSwitchCollectionsMaxCacheSize) { auto [uidStats, userPackageSummaryStats] = sampleUidStats(); auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillRepeatedly(Return(procStatInfo)); std::vector expectedEvents; for (userid_t userId = 100; userId < 100 + kTestMaxUserSwitchEvents; ++userId) { expectedEvents.push_back({ { .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }, .from = userId, .to = userId + 1, }); } time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); for (userid_t userId = 100; userId < 100 + kTestMaxUserSwitchEvents; ++userId) { ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(now, userId, userId + 1, mMockUidStatsCollector, mMockProcStatCollector)); } const auto& actual = mCollectorPeer->getUserSwitchCollectionInfos(); EXPECT_THAT(actual.size(), kTestMaxUserSwitchEvents); EXPECT_THAT(actual, UserSwitchCollectionsEq(expectedEvents)) << "User switch collection infos don't match."; // Add new user switch event with max cache size. The oldest user switch event should be dropped // and the new one added to the cache. userid_t userId = 100 + kTestMaxUserSwitchEvents; expectedEvents.push_back({ { .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }, .from = userId, .to = userId + 1, }); expectedEvents.erase(expectedEvents.begin()); ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(now, userId, userId + 1, mMockUidStatsCollector, mMockProcStatCollector)); const auto& actualInfos = mCollectorPeer->getUserSwitchCollectionInfos(); EXPECT_THAT(actualInfos.size(), kTestMaxUserSwitchEvents); EXPECT_THAT(actualInfos, UserSwitchCollectionsEq(expectedEvents)) << "User switch collection infos don't match."; } TEST_F(IoPerfCollectionTest, TestOnPeriodicCollection) { const auto [uidStats, userPackageSummaryStats] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getPeriodicCollectionInfo(); const CollectionInfo expected{ .maxCacheSize = static_cast(sysprop::periodicCollectionBufferSize().value_or( kDefaultPeriodicCollectionBufferSize)), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Periodic collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3)) << "Boot-time, wake-up and user-switch collections shouldn't be reported"; } TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithoutPackageFilter) { const auto [uidStats, userPackageSummaryStats] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE, {}, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getCustomCollectionInfo(); CollectionInfo expected{ .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Custom collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported"; TemporaryFile customDump; ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd)); // Should clear the cache. ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1)); expected.records.clear(); const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo(); EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected)) << "Custom collection should be cleared."; } TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithPackageFilter) { // Filter by package name should ignore this limit with package filter. mCollectorPeer->setTopNStatsPerCategory(1); const auto [uidStats, _] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE, {"mount", "com.google.android.car.kitchensink"}, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getCustomCollectionInfo(); UserPackageSummaryStats userPackageSummaryStats{ .topNCpuTimes = {{1009, "mount", UserPackageStats::ProcStats{50, {{"disk I/O", 50}}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{60, {{"CTS", 25}, {"KitchenSinkApp", 25}}}}}, .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::IoStats{{0, 3'400}, {0, 200}}}}, .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::IoStats{{0, 6'700}, {0, 200}}}}, .topNIoBlocked = {{1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{3, {{"CTS", 2}, {"KitchenSinkApp", 1}}}}}, .topNMajorFaults = {{1009, "mount", UserPackageStats::ProcStats{11'000, {{"disk I/O", 11'000}}}}, {1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{22'445, {{"KitchenSinkApp", 12'345}, {"CTS", 10'100}}}}}, .totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}}, .taskCountByUid = {{1009, 1}, {1002001, 5}}, .totalCpuTimeMillis = 48'376, .totalMajorFaults = 84'345, .majorFaultsPercentChange = 0.0, }; CollectionInfo expected{ .maxCacheSize = std::numeric_limits::max(), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Custom collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported"; TemporaryFile customDump; ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd)); // Should clear the cache. ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1)); expected.records.clear(); const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo(); EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected)) << "Custom collection should be cleared."; } TEST_F(IoPerfCollectionTest, TestOnPeriodicCollectionWithTrimmingStatsAfterTopN) { mCollectorPeer->setTopNStatsPerCategory(1); mCollectorPeer->setTopNStatsPerSubcategory(1); const auto [uidStats, _] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(procStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getPeriodicCollectionInfo(); UserPackageSummaryStats userPackageSummaryStats{ .topNCpuTimes = {{1012345, "1012345", UserPackageStats::ProcStats{100, {{"MapsApp", 100}}}}}, .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}}}, .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}}}, .topNIoBlocked = {{1002001, "com.google.android.car.kitchensink", UserPackageStats::ProcStats{3, {{"CTS", 2}}}}}, .topNMajorFaults = {{1012345, "1012345", UserPackageStats::ProcStats{50'900, {{"MapsApp", 50'900}}}}}, .totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}}, .taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}}, .totalCpuTimeMillis = 48'376, .totalMajorFaults = 84'345, .majorFaultsPercentChange = 0.0, }; const CollectionInfo expected{ .maxCacheSize = static_cast(sysprop::periodicCollectionBufferSize().value_or( kDefaultPeriodicCollectionBufferSize)), .records = {{ .systemSummaryStats = systemSummaryStats, .userPackageSummaryStats = userPackageSummaryStats, }}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Periodic collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3)) << "Boot-time, wake-up and user-switch collections shouldn't be reported"; } TEST_F(IoPerfCollectionTest, TestConsecutiveOnPeriodicCollection) { const auto [firstUidStats, firstUserPackageSummaryStats] = sampleUidStats(); const auto [firstProcStatInfo, firstSystemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(firstUidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(firstProcStatInfo)); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE, mMockUidStatsCollector, mMockProcStatCollector)); auto [secondUidStats, secondUserPackageSummaryStats] = sampleUidStats(/*multiplier=*/2); const auto [secondProcStatInfo, secondSystemSummaryStats] = sampleProcStat(/*multiplier=*/2); secondUserPackageSummaryStats.majorFaultsPercentChange = (static_cast(secondUserPackageSummaryStats.totalMajorFaults - firstUserPackageSummaryStats.totalMajorFaults) / static_cast(firstUserPackageSummaryStats.totalMajorFaults)) * 100.0; EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(secondUidStats)); EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(secondProcStatInfo)); ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE, mMockUidStatsCollector, mMockProcStatCollector)); const auto actual = mCollectorPeer->getPeriodicCollectionInfo(); const CollectionInfo expected{ .maxCacheSize = static_cast(sysprop::periodicCollectionBufferSize().value_or( kDefaultPeriodicCollectionBufferSize)), .records = {{.systemSummaryStats = firstSystemSummaryStats, .userPackageSummaryStats = firstUserPackageSummaryStats}, {.systemSummaryStats = secondSystemSummaryStats, .userPackageSummaryStats = secondUserPackageSummaryStats}}, }; EXPECT_THAT(actual, CollectionInfoEq(expected)) << "Periodic collection info doesn't match.\nExpected:\n" << expected.toString() << "\nActual:\n" << actual.toString(); ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3)) << "Boot-time, wake-up and user-switch collection shouldn't be reported"; } } // namespace watchdog } // namespace automotive } // namespace android