/* * 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 "MockProcStat.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 { 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; namespace { 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(ProcessCountEq, expected, "") { return ExplainMatchResult(AllOf(Field("comm", &UserPackageStats::ProcStats::ProcessCount::comm, Eq(expected.comm)), Field("count", &UserPackageStats::ProcStats::ProcessCount::count, Eq(expected.count))), arg, result_listener); } MATCHER_P(ProcStatsEq, expected, "") { std::vector> processCountMatchers; for (const auto& processCount : expected.topNProcesses) { processCountMatchers.push_back(ProcessCountEq(processCount)); } return ExplainMatchResult(AllOf(Field("count", &UserPackageStats::ProcStats::count, Eq(expected.count)), Field("topNProcesses", &UserPackageStats::ProcStats::topNProcesses, ElementsAreArray(processCountMatchers))), 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("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("totalMajorFaults", &UserPackageSummaryStats::totalMajorFaults, Eq(expected.totalMajorFaults)), Field("majorFaultsPercentChange", &UserPackageSummaryStats::majorFaultsPercentChange, Eq(expected.majorFaultsPercentChange))), arg, result_listener); } MATCHER_P(SystemSummaryStatsEq, expected, "") { return ExplainMatchResult(AllOf(Field("cpuIoWaitTime", &SystemSummaryStats::cpuIoWaitTime, Eq(expected.cpuIoWaitTime)), Field("totalCpuTime", &SystemSummaryStats::totalCpuTime, Eq(expected.totalCpuTime)), 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); } 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), .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/int64Multiplier(14'000), /*fgWrBytes=*/0, /*bgWrBytes=*/int64Multiplier(16'000), /*fgFsync=*/0, /*bgFsync=*/int64Multiplier(100)}, .procStats = {.totalMajorFaults = uint64Multiplier(11'000), .totalTasksCount = 1, .ioBlockedTasksCount = 1, .processStatsByPid = {{/*pid=*/100, {/*comm=*/"disk I/O", /*startTime=*/234, /*totalMajorFaults=*/uint64Multiplier(11'000), /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/1}}}}}, {.packageInfo = constructPackageInfo("com.google.android.car.kitchensink", 1002001), .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/int64Multiplier(3'400), /*fgWrBytes=*/0, /*bgWrBytes=*/int64Multiplier(6'700), /*fgFsync=*/0, /*bgFsync=*/int64Multiplier(200)}, .procStats = {.totalMajorFaults = uint64Multiplier(22'445), .totalTasksCount = 5, .ioBlockedTasksCount = 3, .processStatsByPid = {{/*pid=*/1000, {/*comm=*/"KitchenSinkApp", /*startTime=*/467, /*totalMajorFaults=*/uint64Multiplier(12'345), /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/1}}, {/*pid=*/1001, {/*comm=*/"CTS", /*startTime=*/789, /*totalMajorFaults=*/uint64Multiplier(10'100), /*totalTasksCount=*/3, /*ioBlockedTasksCount=*/2}}}}}, {.packageInfo = constructPackageInfo("", 1012345), .ioStats = {/*fgRdBytes=*/int64Multiplier(1'000), /*bgRdBytes=*/int64Multiplier(4'200), /*fgWrBytes=*/int64Multiplier(300), /*bgWrBytes=*/int64Multiplier(5'600), /*fgFsync=*/int64Multiplier(600), /*bgFsync=*/int64Multiplier(300)}, .procStats = {.totalMajorFaults = uint64Multiplier(50'900), .totalTasksCount = 4, .ioBlockedTasksCount = 2, .processStatsByPid = {{/*pid=*/2345, {/*comm=*/"MapsApp", /*startTime=*/6789, /*totalMajorFaults=*/uint64Multiplier(50'900), /*totalTasksCount=*/4, /*ioBlockedTasksCount=*/2}}}}}, {.packageInfo = constructPackageInfo("com.google.radio", 1015678), .ioStats = {/*fgRdBytes=*/0, /*bgRdBytes=*/0, /*fgWrBytes=*/0, /*bgWrBytes=*/0, /*fgFsync=*/0, /*bgFsync=*/0}, .procStats = {.totalMajorFaults = 0, .totalTasksCount = 4, .ioBlockedTasksCount = 0, .processStatsByPid = { {/*pid=*/2345, {/*comm=*/"RadioApp", /*startTime=*/19789, /*totalMajorFaults=*/0, /*totalTasksCount=*/4, /*ioBlockedTasksCount=*/0}}}}}}; UserPackageSummaryStats userPackageSummaryStats{ .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}}, .totalMajorFaults = uint64Multiplier(84'345), .majorFaultsPercentChange = 0.0, }; return std::make_tuple(uidStats, userPackageSummaryStats); } std::tuple sampleProcStat(int multiplier = 1) { 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{/*cpuStats=*/{uint64Multiplier(2'900), uint64Multiplier(7'900), uint64Multiplier(4'900), uint64Multiplier(8'900), /*ioWaitTime=*/uint64Multiplier(5'900), uint64Multiplier(6'966), uint64Multiplier(7'980), 0, 0, uint64Multiplier(2'930)}, /*runnableProcessCount=*/uint32Multiplier(100), /*ioBlockedProcessCount=*/uint32Multiplier(57)}; SystemSummaryStats systemSummaryStats{/*cpuIoWaitTime=*/uint64Multiplier(5'900), /*totalCpuTime=*/uint64Multiplier(48'376), /*ioBlockedProcessCount=*/uint32Multiplier(57), /*totalProcessCount=*/uint32Multiplier(157)}; return std::make_tuple(procStatInfo, systemSummaryStats); } } // namespace namespace internal { class IoPerfCollectionPeer : 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; } const CollectionInfo& getBoottimeCollectionInfo() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mBoottimeCollection; } const CollectionInfo& getPeriodicCollectionInfo() { Mutex::Autolock lock(mCollector->mMutex); return mCollector->mPeriodicCollection; } 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(); mMockProcStat = sp::make(); mCollector = sp::make(); mCollectorPeer = sp::make(mCollector); ASSERT_RESULT_OK(mCollectorPeer->init()); mCollectorPeer->setTopNStatsPerCategory(5); mCollectorPeer->setTopNStatsPerSubcategory(5); } void TearDown() override { mMockUidStatsCollector.clear(); mMockProcStat.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 mMockProcStat; 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(*mMockProcStat, 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, mMockProcStat)); 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=*/1)) << "Periodic collection shouldn't be reported"; } TEST_F(IoPerfCollectionTest, TestOnPeriodicCollection) { const auto [uidStats, userPackageSummaryStats] = sampleUidStats(); const auto [procStatInfo, systemSummaryStats] = sampleProcStat(); EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats)); EXPECT_CALL(*mMockProcStat, 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, mMockProcStat)); 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=*/1)) << "Boot-time collection 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(*mMockProcStat, 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, mMockProcStat)); 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(*mMockProcStat, 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, mMockProcStat)); const auto actual = mCollectorPeer->getCustomCollectionInfo(); UserPackageSummaryStats userPackageSummaryStats{ .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}}, .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(*mMockProcStat, 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, mMockProcStat)); const auto actual = mCollectorPeer->getPeriodicCollectionInfo(); UserPackageSummaryStats userPackageSummaryStats{ .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}}, .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=*/1)) << "Boot-time collection 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(*mMockProcStat, 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, mMockProcStat)); 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(*mMockProcStat, deltaStats()).WillOnce(Return(secondProcStatInfo)); ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE, mMockUidStatsCollector, mMockProcStat)); 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=*/1)) << "Boot-time collection shouldn't be reported"; } } // namespace watchdog } // namespace automotive } // namespace android