/* * 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 #include #include #include #include #include #include #include #include "LooperStub.h" #include "ProcPidDir.h" #include "ProcPidStat.h" #include "ProcStat.h" #include "UidIoStats.h" #include "gmock/gmock.h" namespace android { namespace automotive { namespace watchdog { using android::base::Error; using android::base::Result; using android::base::WriteStringToFile; using testing::LooperStub; using testing::populateProcPidDir; namespace { const std::chrono::seconds kTestBootInterval = 1s; const std::chrono::seconds kTestPeriodicInterval = 2s; const std::chrono::seconds kTestCustomInterval = 3s; const std::chrono::seconds kTestCustomCollectionDuration = 11s; class UidIoStatsStub : public UidIoStats { public: explicit UidIoStatsStub(bool enabled = false) : mEnabled(enabled) {} Result> collect() override { if (mCache.empty()) { return Error() << "Cache is empty"; } const auto entry = mCache.front(); mCache.pop(); return entry; } bool enabled() override { return mEnabled; } std::string filePath() override { return kUidIoStatsPath; } void push(const std::unordered_map& entry) { mCache.push(entry); } private: bool mEnabled; std::queue> mCache; }; class ProcStatStub : public ProcStat { public: explicit ProcStatStub(bool enabled = false) : mEnabled(enabled) {} Result collect() override { if (mCache.empty()) { return Error() << "Cache is empty"; } const auto entry = mCache.front(); mCache.pop(); return entry; } bool enabled() override { return mEnabled; } std::string filePath() override { return kProcStatPath; } void push(const ProcStatInfo& entry) { mCache.push(entry); } private: bool mEnabled; std::queue mCache; }; class ProcPidStatStub : public ProcPidStat { public: explicit ProcPidStatStub(bool enabled = false) : mEnabled(enabled) {} Result> collect() override { if (mCache.empty()) { return Error() << "Cache is empty"; } const auto entry = mCache.front(); mCache.pop(); return entry; } bool enabled() override { return mEnabled; } std::string dirPath() override { return kProcDirPath; } void push(const std::vector& entry) { mCache.push(entry); } private: bool mEnabled; std::queue> mCache; }; bool isEqual(const UidIoPerfData& lhs, const UidIoPerfData& rhs) { if (lhs.topNReads.size() != rhs.topNReads.size() || lhs.topNWrites.size() != rhs.topNWrites.size()) { return false; } for (int i = 0; i < METRIC_TYPES; ++i) { for (int j = 0; j < UID_STATES; ++j) { if (lhs.total[i][j] != rhs.total[i][j]) { return false; } } } auto comp = [&](const UidIoPerfData::Stats& l, const UidIoPerfData::Stats& r) -> bool { bool isEqual = l.userId == r.userId && l.packageName == r.packageName; for (int i = 0; i < UID_STATES; ++i) { isEqual &= l.bytes[i] == r.bytes[i] && l.fsync[i] == r.fsync[i]; } return isEqual; }; return lhs.topNReads.size() == rhs.topNReads.size() && std::equal(lhs.topNReads.begin(), lhs.topNReads.end(), rhs.topNReads.begin(), comp) && lhs.topNWrites.size() == rhs.topNWrites.size() && std::equal(lhs.topNWrites.begin(), lhs.topNWrites.end(), rhs.topNWrites.begin(), comp); } bool isEqual(const SystemIoPerfData& lhs, const SystemIoPerfData& rhs) { return lhs.cpuIoWaitTime == rhs.cpuIoWaitTime && lhs.totalCpuTime == rhs.totalCpuTime && lhs.ioBlockedProcessesCnt == rhs.ioBlockedProcessesCnt && lhs.totalProcessesCnt == rhs.totalProcessesCnt; } bool isEqual(const ProcessIoPerfData& lhs, const ProcessIoPerfData& rhs) { if (lhs.topNIoBlockedUids.size() != rhs.topNIoBlockedUids.size() || lhs.topNMajorFaultUids.size() != rhs.topNMajorFaultUids.size() || lhs.totalMajorFaults != rhs.totalMajorFaults || lhs.majorFaultsPercentChange != rhs.majorFaultsPercentChange) { return false; } auto comp = [&](const ProcessIoPerfData::UidStats& l, const ProcessIoPerfData::UidStats& r) -> bool { auto comp = [&](const ProcessIoPerfData::UidStats::ProcessStats& l, const ProcessIoPerfData::UidStats::ProcessStats& r) -> bool { return l.comm == r.comm && l.count == r.count; }; return l.userId == r.userId && l.packageName == r.packageName && l.count == r.count && l.topNProcesses.size() == r.topNProcesses.size() && std::equal(l.topNProcesses.begin(), l.topNProcesses.end(), r.topNProcesses.begin(), comp); }; return lhs.topNIoBlockedUids.size() == lhs.topNIoBlockedUids.size() && std::equal(lhs.topNIoBlockedUids.begin(), lhs.topNIoBlockedUids.end(), rhs.topNIoBlockedUids.begin(), comp) && lhs.topNIoBlockedUidsTotalTaskCnt.size() == rhs.topNIoBlockedUidsTotalTaskCnt.size() && std::equal(lhs.topNIoBlockedUidsTotalTaskCnt.begin(), lhs.topNIoBlockedUidsTotalTaskCnt.end(), rhs.topNIoBlockedUidsTotalTaskCnt.begin()) && lhs.topNMajorFaultUids.size() == rhs.topNMajorFaultUids.size() && std::equal(lhs.topNMajorFaultUids.begin(), lhs.topNMajorFaultUids.end(), rhs.topNMajorFaultUids.begin(), comp); } bool isEqual(const IoPerfRecord& lhs, const IoPerfRecord& rhs) { return isEqual(lhs.uidIoPerfData, rhs.uidIoPerfData) && isEqual(lhs.systemIoPerfData, rhs.systemIoPerfData) && isEqual(lhs.processIoPerfData, rhs.processIoPerfData); } } // namespace TEST(IoPerfCollectionTest, TestCollectionStartAndTerminate) { sp collector = new IoPerfCollection(); const auto& ret = collector->start(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_TRUE(collector->mCollectionThread.joinable()) << "Collection thread not created"; ASSERT_FALSE(collector->start()) << "No error returned when collector was started more than once"; ASSERT_TRUE(sysprop::topNStatsPerCategory().has_value()); ASSERT_EQ(collector->mTopNStatsPerCategory, sysprop::topNStatsPerCategory().value()); ASSERT_TRUE(sysprop::topNStatsPerSubcategory().has_value()); ASSERT_EQ(collector->mTopNStatsPerSubcategory, sysprop::topNStatsPerSubcategory().value()); ASSERT_TRUE(sysprop::boottimeCollectionInterval().has_value()); ASSERT_EQ(std::chrono::duration_cast( collector->mBoottimeCollection.interval) .count(), sysprop::boottimeCollectionInterval().value()); ASSERT_TRUE(sysprop::topNStatsPerCategory().has_value()); ASSERT_EQ(std::chrono::duration_cast( collector->mPeriodicCollection.interval) .count(), sysprop::periodicCollectionInterval().value()); ASSERT_TRUE(sysprop::periodicCollectionBufferSize().has_value()); ASSERT_EQ(collector->mPeriodicCollection.maxCacheSize, sysprop::periodicCollectionBufferSize().value()); collector->terminate(); ASSERT_FALSE(collector->mCollectionThread.joinable()) << "Collection thread did not terminate"; } TEST(IoPerfCollectionTest, TestValidCollectionSequence) { sp uidIoStatsStub = new UidIoStatsStub(true); sp procStatStub = new ProcStatStub(true); sp procPidStatStub = new ProcPidStatStub(true); sp looperStub = new LooperStub(); sp collector = new IoPerfCollection(); collector->mUidIoStats = uidIoStatsStub; collector->mProcStat = procStatStub; collector->mProcPidStat = procPidStatStub; collector->mHandlerLooper = looperStub; auto ret = collector->start(); ASSERT_TRUE(ret) << ret.error().message(); collector->mBoottimeCollection.interval = kTestBootInterval; collector->mPeriodicCollection.interval = kTestPeriodicInterval; collector->mPeriodicCollection.maxCacheSize = 1; // #1 Boot-time collection uidIoStatsStub->push({{1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}}}); procStatStub->push(ProcStatInfo{ /*stats=*/{6200, 5700, 1700, 3100, /*ioWaitTime=*/1100, 5200, 3900, 0, 0, 0}, /*runnableCnt=*/17, /*ioBlockedCnt=*/5, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 5000, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 5000, .numThreads = 1, .startTime = 234}}}}}); IoPerfRecord bootExpectedFirst = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 20000}, .fsync{0, 300}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 30000}, .fsync{0, 300}}}, .total = {{0, 20000}, {0, 30000}, {0, 300}}}, .systemIoPerfData = {.cpuIoWaitTime = 1100, .totalCpuTime = 26900, .ioBlockedProcessesCnt = 5, .totalProcessesCnt = 22}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 1, {{"disk I/O", 1}}}}, .topNIoBlockedUidsTotalTaskCnt = {1}, .topNMajorFaultUids = {{0, "mount", 5000, {{"disk I/O", 5000}}}}, .totalMajorFaults = 5000, .majorFaultsPercentChange = 0.0}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), 0) << "Boot-time collection didn't start immediately"; // #2 Boot-time collection uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 2000, 0, 3000, 0, 100}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{1200, 1700, 2700, 7800, /*ioWaitTime=*/5500, 500, 300, 0, 0, 100}, /*runnableCnt=*/8, /*ioBlockedCnt=*/6, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 11000, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 10000, .numThreads = 1, .startTime = 234}}, {200, {.pid = 200, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 1000, .numThreads = 1, .startTime = 1234}}}}}); IoPerfRecord bootExpectedSecond = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 2000}, .fsync{0, 100}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 3000}, .fsync{0, 100}}}, .total = {{0, 2000}, {0, 3000}, {0, 100}}}, .systemIoPerfData = {.cpuIoWaitTime = 5500, .totalCpuTime = 19800, .ioBlockedProcessesCnt = 6, .totalProcessesCnt = 14}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 11000, {{"disk I/O", 11000}}}}, .totalMajorFaults = 11000, .majorFaultsPercentChange = ((11000.0 - 5000.0) / 5000.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), kTestBootInterval.count()) << "Subsequent boot-time collection didn't happen at " << kTestBootInterval.count() << " seconds interval"; // #3 Last boot-time collection ret = collector->onBootFinished(); ASSERT_TRUE(ret) << ret.error().message(); uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 7000, 0, 8000, 0, 50}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{1400, 1900, 2900, 8000, /*ioWaitTime=*/5700, 700, 500, 0, 0, 300}, /*runnableCnt=*/10, /*ioBlockedCnt=*/8, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 5000, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 3000, .numThreads = 1, .startTime = 234}}, {200, {.pid = 200, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 2000, .numThreads = 1, .startTime = 1234}}}}}); IoPerfRecord bootExpectedThird = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 7000}, .fsync{0, 50}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 8000}, .fsync{0, 50}}}, .total = {{0, 7000}, {0, 8000}, {0, 50}}}, .systemIoPerfData = {.cpuIoWaitTime = 5700, .totalCpuTime = 21400, .ioBlockedProcessesCnt = 8, .totalProcessesCnt = 18}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 5000, {{"disk I/O", 5000}}}}, .totalMajorFaults = 5000, .majorFaultsPercentChange = ((5000.0 - 11000.0) / 11000.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), 0) << "Last boot-time collection didn't happen immediately after receiving boot complete " << "notification"; ASSERT_EQ(collector->mBoottimeCollection.records.size(), 3); ASSERT_TRUE(isEqual(collector->mBoottimeCollection.records[0], bootExpectedFirst)) << "Boot-time collection record 1 doesn't match.\nExpected:\n" << toString(bootExpectedFirst) << "\nActual:\n" << toString(collector->mBoottimeCollection.records[0]); ASSERT_TRUE(isEqual(collector->mBoottimeCollection.records[1], bootExpectedSecond)) << "Boot-time collection record 2 doesn't match.\nExpected:\n" << toString(bootExpectedSecond) << "\nActual:\n" << toString(collector->mBoottimeCollection.records[1]); ASSERT_TRUE(isEqual(collector->mBoottimeCollection.records[2], bootExpectedThird)) << "Boot-time collection record 3 doesn't match.\nExpected:\n" << toString(bootExpectedSecond) << "\nActual:\n" << toString(collector->mBoottimeCollection.records[2]); // #4 Periodic collection uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 4000, 0, 6000, 0, 100}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{200, 700, 400, 800, /*ioWaitTime=*/500, 666, 780, 0, 0, 230}, /*runnableCnt=*/12, /*ioBlockedCnt=*/3, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 4100, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 100, .numThreads = 1, .startTime = 234}}, {1200, {.pid = 1200, .comm = "disk I/O", .state = "S", .ppid = 1, .majorFaults = 4000, .numThreads = 1, .startTime = 567890}}}}}); IoPerfRecord periodicExpectedFirst = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 4000}, .fsync{0, 100}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 6000}, .fsync{0, 100}}}, .total = {{0, 4000}, {0, 6000}, {0, 100}}}, .systemIoPerfData = {.cpuIoWaitTime = 500, .totalCpuTime = 4276, .ioBlockedProcessesCnt = 3, .totalProcessesCnt = 15}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 1, {{"disk I/O", 1}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 4100, {{"disk I/O", 4100}}}}, .totalMajorFaults = 4100, .majorFaultsPercentChange = ((4100.0 - 5000.0) / 5000.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), kTestPeriodicInterval.count()) << "First periodic collection didn't happen at " << kTestPeriodicInterval.count() << " seconds interval"; // #5 Periodic collection uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 3000, 0, 5000, 0, 800}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{2300, 7300, 4300, 8300, /*ioWaitTime=*/5300, 6366, 7380, 0, 0, 2330}, /*runnableCnt=*/2, /*ioBlockedCnt=*/4, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 44300, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 1300, .numThreads = 1, .startTime = 234}}, {1200, {.pid = 1200, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 43000, .numThreads = 1, .startTime = 567890}}}}}); IoPerfRecord periodicExpectedSecond = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 3000}, .fsync{0, 800}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 5000}, .fsync{0, 800}}}, .total = {{0, 3000}, {0, 5000}, {0, 800}}}, .systemIoPerfData = {.cpuIoWaitTime = 5300, .totalCpuTime = 43576, .ioBlockedProcessesCnt = 4, .totalProcessesCnt = 6}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 44300, {{"disk I/O", 44300}}}}, .totalMajorFaults = 44300, .majorFaultsPercentChange = ((44300.0 - 4100.0) / 4100.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), kTestPeriodicInterval.count()) << "Subsequent periodic collection didn't happen at " << kTestPeriodicInterval.count() << " seconds interval"; ASSERT_EQ(collector->mPeriodicCollection.records.size(), 2); ASSERT_TRUE(isEqual(collector->mPeriodicCollection.records[0], periodicExpectedFirst)) << "Periodic collection snapshot 1, record 1 doesn't match.\nExpected:\n" << toString(periodicExpectedFirst) << "\nActual:\n" << toString(collector->mPeriodicCollection.records[0]); ASSERT_TRUE(isEqual(collector->mPeriodicCollection.records[1], periodicExpectedSecond)) << "Periodic collection snapshot 1, record 2 doesn't match.\nExpected:\n" << toString(periodicExpectedSecond) << "\nActual:\n" << toString(collector->mPeriodicCollection.records[1]); // #6 Custom collection Vector args; args.push_back(String16(kStartCustomCollectionFlag)); args.push_back(String16(kIntervalFlag)); args.push_back(String16(std::to_string(kTestCustomInterval.count()).c_str())); args.push_back(String16(kMaxDurationFlag)); args.push_back(String16(std::to_string(kTestCustomCollectionDuration.count()).c_str())); ret = collector->dump(-1, args); ASSERT_TRUE(ret.ok()) << ret.error().message(); uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 13000, 0, 15000, 0, 100}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{2800, 7800, 4800, 8800, /*ioWaitTime=*/5800, 6866, 7880, 0, 0, 2830}, /*runnableCnt=*/200, /*ioBlockedCnt=*/13, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 49800, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 1800, .numThreads = 1, .startTime = 234}}, {1200, {.pid = 1200, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 48000, .numThreads = 1, .startTime = 567890}}}}}); IoPerfRecord customExpectedFirst = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 13000}, .fsync{0, 100}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 15000}, .fsync{0, 100}}}, .total = {{0, 13000}, {0, 15000}, {0, 100}}}, .systemIoPerfData = {.cpuIoWaitTime = 5800, .totalCpuTime = 47576, .ioBlockedProcessesCnt = 13, .totalProcessesCnt = 213}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 49800, {{"disk I/O", 49800}}}}, .totalMajorFaults = 49800, .majorFaultsPercentChange = ((49800.0 - 44300.0) / 44300.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), 0) << "Custom collection didn't start immediately"; // #7 Custom collection uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 14000, 0, 16000, 0, 100}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{2900, 7900, 4900, 8900, /*ioWaitTime=*/5900, 6966, 7980, 0, 0, 2930}, /*runnableCnt=*/100, /*ioBlockedCnt=*/57, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 50900, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 1900, .numThreads = 1, .startTime = 234}}, {1200, {.pid = 1200, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 49000, .numThreads = 1, .startTime = 567890}}}}}); IoPerfRecord customExpectedSecond = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 14000}, .fsync{0, 100}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 16000}, .fsync{0, 100}}}, .total = {{0, 14000}, {0, 16000}, {0, 100}}}, .systemIoPerfData = {.cpuIoWaitTime = 5900, .totalCpuTime = 48376, .ioBlockedProcessesCnt = 57, .totalProcessesCnt = 157}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 50900, {{"disk I/O", 50900}}}}, .totalMajorFaults = 50900, .majorFaultsPercentChange = ((50900.0 - 49800.0) / 49800.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), kTestCustomInterval.count()) << "Subsequent custom collection didn't happen at " << kTestCustomInterval.count() << " seconds interval"; ASSERT_EQ(collector->mCustomCollection.records.size(), 2); ASSERT_TRUE(isEqual(collector->mCustomCollection.records[0], customExpectedFirst)) << "Custom collection record 1 doesn't match.\nExpected:\n" << toString(customExpectedFirst) << "\nActual:\n" << toString(collector->mCustomCollection.records[0]); ASSERT_TRUE(isEqual(collector->mCustomCollection.records[1], customExpectedSecond)) << "Custom collection record 2 doesn't match.\nExpected:\n" << toString(customExpectedSecond) << "\nActual:\n" << toString(collector->mCustomCollection.records[1]); // #8 Switch to periodic collection args.clear(); args.push_back(String16(kEndCustomCollectionFlag)); TemporaryFile customDump; ret = collector->dump(customDump.fd, args); ASSERT_TRUE(ret.ok()) << ret.error().message(); ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); // Custom collection cache should be emptied on ending the collection. ASSERT_EQ(collector->mCustomCollection.records.size(), 0); // #7 periodic collection uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 123, 0, 456, 0, 25}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{3400, 2300, 5600, 7800, /*ioWaitTime=*/1100, 166, 180, 0, 0, 130}, /*runnableCnt=*/3, /*ioBlockedCnt=*/1, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 5701, .numThreads = 1, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 23, .numThreads = 1, .startTime = 234}}, {1200, {.pid = 1200, .comm = "disk I/O", .state = "D", .ppid = 1, .majorFaults = 5678, .numThreads = 1, .startTime = 567890}}}}}); IoPerfRecord periodicExpectedThird = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "mount", .bytes = {0, 123}, .fsync{0, 25}}}, .topNWrites = {{.userId = 0, .packageName = "mount", .bytes = {0, 456}, .fsync{0, 25}}}, .total = {{0, 123}, {0, 456}, {0, 25}}}, .systemIoPerfData = {.cpuIoWaitTime = 1100, .totalCpuTime = 20676, .ioBlockedProcessesCnt = 1, .totalProcessesCnt = 4}, .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}}, .topNIoBlockedUidsTotalTaskCnt = {2}, .topNMajorFaultUids = {{0, "mount", 5701, {{"disk I/O", 5701}}}}, .totalMajorFaults = 5701, .majorFaultsPercentChange = ((5701.0 - 50900.0) / 50900.0) * 100}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), 0) << "Periodic collection didn't start immediately after ending custom collection"; // Maximum periodic collection buffer size is 2. ASSERT_EQ(collector->mPeriodicCollection.records.size(), 2); ASSERT_TRUE(isEqual(collector->mPeriodicCollection.records[0], periodicExpectedSecond)) << "Periodic collection snapshot 2, record 1 doesn't match.\nExpected:\n" << toString(periodicExpectedSecond) << "\nActual:\n" << toString(collector->mPeriodicCollection.records[0]); ASSERT_TRUE(isEqual(collector->mPeriodicCollection.records[1], periodicExpectedThird)) << "Periodic collection snapshot 2, record 2 doesn't match.\nExpected:\n" << toString(periodicExpectedThird) << "\nActual:\n" << toString(collector->mPeriodicCollection.records[1]); ASSERT_EQ(collector->mBoottimeCollection.records.size(), 3) << "Boot-time records not persisted until collector termination"; TemporaryFile bugreportDump; ret = collector->dump(bugreportDump.fd, {}); ASSERT_TRUE(ret.ok()) << ret.error().message(); collector->terminate(); } TEST(IoPerfCollectionTest, TestCollectionTerminatesOnZeroEnabledCollectors) { sp collector = new IoPerfCollection(); collector->mUidIoStats = new UidIoStatsStub(); collector->mProcStat = new ProcStatStub(); collector->mProcPidStat = new ProcPidStatStub(); const auto& ret = collector->start(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(std::async([&]() { if (collector->mCollectionThread.joinable()) { collector->mCollectionThread.join(); } }).wait_for(1s), std::future_status::ready) << "Collection thread didn't terminate within 1 second."; ASSERT_EQ(collector->mCurrCollectionEvent, CollectionEvent::TERMINATED); // When the collection doesn't auto-terminate on error, the test will hang if the collector is // not terminated explicitly. Thus call terminate to avoid this. collector->terminate(); } TEST(IoPerfCollectionTest, TestCollectionTerminatesOnError) { sp collector = new IoPerfCollection(); collector->mUidIoStats = new UidIoStatsStub(true); collector->mProcStat = new ProcStatStub(true); collector->mProcPidStat = new ProcPidStatStub(true); // Stub caches are empty so polling them should trigger error. const auto& ret = collector->start(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(std::async([&]() { if (collector->mCollectionThread.joinable()) { collector->mCollectionThread.join(); } }).wait_for(1s), std::future_status::ready) << "Collection thread didn't terminate within 1 second."; ASSERT_EQ(collector->mCurrCollectionEvent, CollectionEvent::TERMINATED); // When the collection doesn't auto-terminate on error, the test will hang if the collector is // not terminated explicitly. Thus call terminate to avoid this. collector->terminate(); } TEST(IoPerfCollectionTest, TestCustomCollectionFiltersPackageNames) { sp uidIoStatsStub = new UidIoStatsStub(true); sp procStatStub = new ProcStatStub(true); sp procPidStatStub = new ProcPidStatStub(true); sp looperStub = new LooperStub(); sp collector = new IoPerfCollection(); collector->mUidIoStats = uidIoStatsStub; collector->mProcStat = procStatStub; collector->mProcPidStat = procPidStatStub; collector->mHandlerLooper = looperStub; // Filter by package name should ignore this limit. collector->mTopNStatsPerCategory = 1; auto ret = collector->start(); ASSERT_TRUE(ret) << ret.error().message(); // Dummy boot-time collection uidIoStatsStub->push({}); procStatStub->push(ProcStatInfo{}); procPidStatStub->push({}); ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); // Dummy Periodic collection ret = collector->onBootFinished(); ASSERT_TRUE(ret) << ret.error().message(); uidIoStatsStub->push({}); procStatStub->push(ProcStatInfo{}); procPidStatStub->push({}); ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); // Start custom Collection Vector args; args.push_back(String16(kStartCustomCollectionFlag)); args.push_back(String16(kIntervalFlag)); args.push_back(String16(std::to_string(kTestCustomInterval.count()).c_str())); args.push_back(String16(kMaxDurationFlag)); args.push_back(String16(std::to_string(kTestCustomCollectionDuration.count()).c_str())); args.push_back(String16(kFilterPackagesFlag)); args.push_back(String16("android.car.cts,system_server")); ret = collector->dump(-1, args); ASSERT_TRUE(ret.ok()) << ret.error().message(); // Custom collection collector->mUidToPackageNameMapping[1009] = "android.car.cts"; collector->mUidToPackageNameMapping[2001] = "system_server"; collector->mUidToPackageNameMapping[3456] = "random_process"; uidIoStatsStub->push({ {1009, {.uid = 1009, .ios = {0, 14000, 0, 16000, 0, 100}}}, {2001, {.uid = 2001, .ios = {0, 3400, 0, 6700, 0, 200}}}, {3456, {.uid = 3456, .ios = {0, 4200, 0, 5600, 0, 300}}}, }); procStatStub->push(ProcStatInfo{ /*stats=*/{2900, 7900, 4900, 8900, /*ioWaitTime=*/5900, 6966, 7980, 0, 0, 2930}, /*runnableCnt=*/100, /*ioBlockedCnt=*/57, }); procPidStatStub->push({{.tgid = 100, .uid = 1009, .process = {.pid = 100, .comm = "cts_test", .state = "D", .ppid = 1, .majorFaults = 50900, .numThreads = 2, .startTime = 234}, .threads = {{100, {.pid = 100, .comm = "cts_test", .state = "D", .ppid = 1, .majorFaults = 50900, .numThreads = 1, .startTime = 234}}, {200, {.pid = 200, .comm = "cts_test_2", .state = "D", .ppid = 1, .majorFaults = 0, .numThreads = 1, .startTime = 290}}}}, {.tgid = 1000, .uid = 2001, .process = {.pid = 1000, .comm = "system_server", .state = "D", .ppid = 1, .majorFaults = 1234, .numThreads = 1, .startTime = 345}, .threads = {{1000, {.pid = 1000, .comm = "system_server", .state = "D", .ppid = 1, .majorFaults = 1234, .numThreads = 1, .startTime = 345}}}}, {.tgid = 4000, .uid = 3456, .process = {.pid = 4000, .comm = "random_process", .state = "D", .ppid = 1, .majorFaults = 3456, .numThreads = 1, .startTime = 890}, .threads = {{4000, {.pid = 4000, .comm = "random_process", .state = "D", .ppid = 1, .majorFaults = 50900, .numThreads = 1, .startTime = 890}}}}}); IoPerfRecord expected = { .uidIoPerfData = {.topNReads = {{.userId = 0, .packageName = "android.car.cts", .bytes = {0, 14000}, .fsync{0, 100}}, {.userId = 0, .packageName = "system_server", .bytes = {0, 3400}, .fsync{0, 200}}}, .topNWrites = {{.userId = 0, .packageName = "android.car.cts", .bytes = {0, 16000}, .fsync{0, 100}}, {.userId = 0, .packageName = "system_server", .bytes = {0, 6700}, .fsync{0, 200}}}, .total = {{0, 21600}, {0, 28300}, {0, 600}}}, .systemIoPerfData = {.cpuIoWaitTime = 5900, .totalCpuTime = 48376, .ioBlockedProcessesCnt = 57, .totalProcessesCnt = 157}, .processIoPerfData = {.topNIoBlockedUids = {{0, "android.car.cts", 2, {{"cts_test", 2}}}, {0, "system_server", 1, {{"system_server", 1}}}}, .topNIoBlockedUidsTotalTaskCnt = {2, 1}, .topNMajorFaultUids = {{0, "android.car.cts", 50900, {{"cts_test", 50900}}}, {0, "system_server", 1234, {{"system_server", 1234}}}}, .totalMajorFaults = 55590, .majorFaultsPercentChange = 0}, }; ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), 0) << "Custom collection didn't start immediately"; ASSERT_EQ(collector->mCurrCollectionEvent, CollectionEvent::CUSTOM); ASSERT_EQ(collector->mCustomCollection.records.size(), 1); ASSERT_TRUE(isEqual(collector->mCustomCollection.records[0], expected)) << "Custom collection record doesn't match.\nExpected:\n" << toString(expected) << "\nActual:\n" << toString(collector->mCustomCollection.records[0]); collector->terminate(); } TEST(IoPerfCollectionTest, TestCustomCollectionTerminatesAfterMaxDuration) { sp uidIoStatsStub = new UidIoStatsStub(true); sp procStatStub = new ProcStatStub(true); sp procPidStatStub = new ProcPidStatStub(true); sp looperStub = new LooperStub(); sp collector = new IoPerfCollection(); collector->mUidIoStats = uidIoStatsStub; collector->mProcStat = procStatStub; collector->mProcPidStat = procPidStatStub; collector->mHandlerLooper = looperStub; auto ret = collector->start(); ASSERT_TRUE(ret) << ret.error().message(); // Dummy boot-time collection uidIoStatsStub->push({}); procStatStub->push(ProcStatInfo{}); procPidStatStub->push({}); ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); // Dummy Periodic collection ret = collector->onBootFinished(); ASSERT_TRUE(ret) << ret.error().message(); uidIoStatsStub->push({}); procStatStub->push(ProcStatInfo{}); procPidStatStub->push({}); ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); // Start custom Collection Vector args; args.push_back(String16(kStartCustomCollectionFlag)); args.push_back(String16(kIntervalFlag)); args.push_back(String16(std::to_string(kTestCustomInterval.count()).c_str())); args.push_back(String16(kMaxDurationFlag)); args.push_back(String16(std::to_string(kTestCustomCollectionDuration.count()).c_str())); ret = collector->dump(-1, args); ASSERT_TRUE(ret.ok()) << ret.error().message(); // Maximum custom collection iterations during |kTestCustomCollectionDuration|. int maxIterations = static_cast(kTestCustomCollectionDuration.count() / kTestCustomInterval.count()); for (int i = 0; i < maxIterations; ++i) { ASSERT_TRUE(ret) << ret.error().message(); uidIoStatsStub->push({}); procStatStub->push(ProcStatInfo{}); procPidStatStub->push({}); ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); int secondsElapsed = (i == 0 ? 0 : kTestCustomInterval.count()); ASSERT_EQ(looperStub->numSecondsElapsed(), secondsElapsed) << "Custom collection didn't happen at " << secondsElapsed << " seconds interval in iteration " << i; } ASSERT_EQ(collector->mCurrCollectionEvent, CollectionEvent::CUSTOM); ASSERT_GT(collector->mCustomCollection.records.size(), 0); // Next looper message was injected during startCustomCollection to end the custom collection // after |kTestCustomCollectionDuration|. Thus on processing this message the custom collection // should terminate. ret = looperStub->pollCache(); ASSERT_TRUE(ret) << ret.error().message(); ASSERT_EQ(looperStub->numSecondsElapsed(), kTestCustomCollectionDuration.count() % kTestCustomInterval.count()) << "Custom collection did't end after " << kTestCustomCollectionDuration.count() << " seconds"; ASSERT_EQ(collector->mCurrCollectionEvent, CollectionEvent::PERIODIC); ASSERT_EQ(collector->mCustomCollection.records.size(), 0) << "Custom collection records not discarded at the end of the collection"; collector->terminate(); } TEST(IoPerfCollectionTest, TestValidUidIoStatFile) { // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes // fgFsync bgFsync constexpr char firstSnapshot[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n" "1005678 500 100 30 50 300 400 100 200 45 60\n" "1009 0 0 0 0 40000 50000 20000 30000 0 300\n" "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n"; struct UidIoPerfData expectedUidIoPerfData = {}; expectedUidIoPerfData.total[READ_BYTES][FOREGROUND] = 5030; expectedUidIoPerfData.total[READ_BYTES][BACKGROUND] = 20300; expectedUidIoPerfData.total[WRITE_BYTES][FOREGROUND] = 1550; expectedUidIoPerfData.total[WRITE_BYTES][BACKGROUND] = 30300; expectedUidIoPerfData.total[FSYNC_COUNT][FOREGROUND] = 115; expectedUidIoPerfData.total[FSYNC_COUNT][BACKGROUND] = 370; expectedUidIoPerfData.topNReads.push_back({ // uid: 1009 .userId = 0, .packageName = "mount", .bytes = {0, 20000}, .fsync = {0, 300}, }); expectedUidIoPerfData.topNReads.push_back({ // uid: 1001234 .userId = 10, .packageName = "1001234", .bytes = {3000, 0}, .fsync = {20, 0}, }); expectedUidIoPerfData.topNWrites.push_back({ // uid: 1009 .userId = 0, .packageName = "mount", .bytes = {0, 30000}, .fsync = {0, 300}, }); expectedUidIoPerfData.topNWrites.push_back({ // uid: 1001000 .userId = 10, .packageName = "shared:android.uid.system", .bytes = {1000, 100}, .fsync = {50, 10}, }); TemporaryFile tf; ASSERT_NE(tf.fd, -1); ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path)); IoPerfCollection collector; collector.mUidIoStats = new UidIoStats(tf.path); collector.mTopNStatsPerCategory = 2; ASSERT_TRUE(collector.mUidIoStats->enabled()) << "Temporary file is inaccessible"; struct UidIoPerfData actualUidIoPerfData = {}; auto ret = collector.collectUidIoPerfDataLocked(CollectionInfo{}, &actualUidIoPerfData); ASSERT_RESULT_OK(ret); EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData)) << "First snapshot doesn't match.\nExpected:\n" << toString(expectedUidIoPerfData) << "\nActual:\n" << toString(actualUidIoPerfData); constexpr char secondSnapshot[] = "1001234 10000 2000 7000 950 0 0 0 0 45 0\n" "1005678 600 100 40 50 1000 1000 1000 600 50 70\n" "1003456 300 500 200 300 0 0 0 0 50 0\n" "1001000 400 300 200 100 40 30 20 10 5 1\n"; expectedUidIoPerfData = {}; expectedUidIoPerfData.total[READ_BYTES][FOREGROUND] = 4210; expectedUidIoPerfData.total[READ_BYTES][BACKGROUND] = 900; expectedUidIoPerfData.total[WRITE_BYTES][FOREGROUND] = 750; expectedUidIoPerfData.total[WRITE_BYTES][BACKGROUND] = 400; expectedUidIoPerfData.total[FSYNC_COUNT][FOREGROUND] = 80; expectedUidIoPerfData.total[FSYNC_COUNT][BACKGROUND] = 10; expectedUidIoPerfData.topNReads.push_back({ // uid: 1001234 .userId = 10, .packageName = "1001234", .bytes = {4000, 0}, .fsync = {25, 0}, }); expectedUidIoPerfData.topNReads.push_back({ // uid: 1005678 .userId = 10, .packageName = "1005678", .bytes = {10, 900}, .fsync = {5, 10}, }); expectedUidIoPerfData.topNWrites.push_back({ // uid: 1001234 .userId = 10, .packageName = "1001234", .bytes = {450, 0}, .fsync = {25, 0}, }); expectedUidIoPerfData.topNWrites.push_back({ // uid: 1005678 .userId = 10, .packageName = "1005678", .bytes = {0, 400}, .fsync = {5, 10}, }); ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path)); actualUidIoPerfData = {}; ret = collector.collectUidIoPerfDataLocked(CollectionInfo{}, &actualUidIoPerfData); ASSERT_RESULT_OK(ret); EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData)) << "Second snapshot doesn't match.\nExpected:\n" << toString(expectedUidIoPerfData) << "\nActual:\n" << toString(actualUidIoPerfData); } TEST(IoPerfCollectionTest, TestUidIOStatsLessThanTopNStatsLimit) { // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes // fgFsync bgFsync constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"; struct UidIoPerfData expectedUidIoPerfData = {}; expectedUidIoPerfData.total[READ_BYTES][FOREGROUND] = 3000; expectedUidIoPerfData.total[READ_BYTES][BACKGROUND] = 0; expectedUidIoPerfData.total[WRITE_BYTES][FOREGROUND] = 500; expectedUidIoPerfData.total[WRITE_BYTES][BACKGROUND] = 0; expectedUidIoPerfData.total[FSYNC_COUNT][FOREGROUND] = 20; expectedUidIoPerfData.total[FSYNC_COUNT][BACKGROUND] = 0; expectedUidIoPerfData.topNReads.push_back({ // uid: 1001234 .userId = 10, .packageName = "1001234", .bytes = {3000, 0}, .fsync = {20, 0}, }); expectedUidIoPerfData.topNWrites.push_back({ // uid: 1001234 .userId = 10, .packageName = "1001234", .bytes = {500, 0}, .fsync = {20, 0}, }); TemporaryFile tf; ASSERT_NE(tf.fd, -1); ASSERT_TRUE(WriteStringToFile(contents, tf.path)); IoPerfCollection collector; collector.mUidIoStats = new UidIoStats(tf.path); collector.mTopNStatsPerCategory = 10; ASSERT_TRUE(collector.mUidIoStats->enabled()) << "Temporary file is inaccessible"; struct UidIoPerfData actualUidIoPerfData = {}; const auto& ret = collector.collectUidIoPerfDataLocked(CollectionInfo{}, &actualUidIoPerfData); ASSERT_RESULT_OK(ret); EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData)) << "Collected data doesn't match.\nExpected:\n" << toString(expectedUidIoPerfData) << "\nActual:\n" << toString(actualUidIoPerfData); } TEST(IoPerfCollectionTest, TestValidProcStatFile) { constexpr char firstSnapshot[] = "cpu 6200 5700 1700 3100 1100 5200 3900 0 0 0\n" "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n" "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n" "cpu2 900 400 400 1000 600 400 160 0 0 0\n" "cpu3 1000 20 190 650 109 130 140 0 0 0\n" "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " "0 0\n" // Skipped most of the intr line as it is not important for testing the ProcStat parsing // logic. "ctxt 579020168\n" "btime 1579718450\n" "processes 113804\n" "procs_running 17\n" "procs_blocked 5\n" "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n"; struct SystemIoPerfData expectedSystemIoPerfData = { .cpuIoWaitTime = 1100, .totalCpuTime = 26900, .ioBlockedProcessesCnt = 5, .totalProcessesCnt = 22, }; TemporaryFile tf; ASSERT_NE(tf.fd, -1); ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path)); IoPerfCollection collector; collector.mProcStat = new ProcStat(tf.path); ASSERT_TRUE(collector.mProcStat->enabled()) << "Temporary file is inaccessible"; struct SystemIoPerfData actualSystemIoPerfData = {}; auto ret = collector.collectSystemIoPerfDataLocked(&actualSystemIoPerfData); ASSERT_RESULT_OK(ret); EXPECT_TRUE(isEqual(expectedSystemIoPerfData, actualSystemIoPerfData)) << "First snapshot doesn't match.\nExpected:\n" << toString(expectedSystemIoPerfData) << "\nActual:\n" << toString(actualSystemIoPerfData); constexpr char secondSnapshot[] = "cpu 16200 8700 2000 4100 2200 6200 5900 0 0 0\n" "cpu0 4400 3400 700 890 800 4500 3100 0 0 0\n" "cpu1 5900 3380 610 960 100 670 2000 0 0 0\n" "cpu2 2900 1000 450 1400 800 600 460 0 0 0\n" "cpu3 3000 920 240 850 500 430 340 0 0 0\n" "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " "0 0\n" "ctxt 579020168\n" "btime 1579718450\n" "processes 113804\n" "procs_running 10\n" "procs_blocked 2\n" "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n"; expectedSystemIoPerfData = { .cpuIoWaitTime = 1100, .totalCpuTime = 18400, .ioBlockedProcessesCnt = 2, .totalProcessesCnt = 12, }; ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path)); actualSystemIoPerfData = {}; ret = collector.collectSystemIoPerfDataLocked(&actualSystemIoPerfData); ASSERT_RESULT_OK(ret); EXPECT_TRUE(isEqual(expectedSystemIoPerfData, actualSystemIoPerfData)) << "Second snapshot doesn't match.\nExpected:\n" << toString(expectedSystemIoPerfData) << "\nActual:\n" << toString(actualSystemIoPerfData); } TEST(IoPerfCollectionTest, TestValidProcPidContents) { std::unordered_map> pidToTids = { {1, {1, 453}}, {2546, {2546, 3456, 4789}}, {7890, {7890, 8978, 12890}}, {18902, {18902, 21345, 32452}}, {28900, {28900}}, }; std::unordered_map perProcessStat = { {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0\n"}, {2546, "2546 (system_server) R 1 0 0 0 0 0 0 0 6000 0 0 0 0 0 0 0 3 0 1000\n"}, {7890, "7890 (logd) D 1 0 0 0 0 0 0 0 15000 0 0 0 0 0 0 0 3 0 2345\n"}, {18902, "18902 (disk I/O) D 1 0 0 0 0 0 0 0 45678 0 0 0 0 0 0 0 3 0 897654\n"}, {28900, "28900 (tombstoned) D 1 0 0 0 0 0 0 0 89765 0 0 0 0 0 0 0 3 0 2345671\n"}, }; std::unordered_map perProcessStatus = { {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"}, {2546, "Pid:\t2546\nTgid:\t2546\nUid:\t1001000\t1001000\t1001000\t1001000\n"}, {7890, "Pid:\t7890\nTgid:\t7890\nUid:\t1001000\t1001000\t1001000\t1001000\n"}, {18902, "Pid:\t18902\nTgid:\t18902\nUid:\t1009\t1009\t1009\t1009\n"}, {28900, "Pid:\t28900\nTgid:\t28900\nUid:\t1001234\t1001234\t1001234\t1001234\n"}, }; std::unordered_map perThreadStat = { {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 0\n"}, {453, "453 (init) S 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0 2 0 275\n"}, {2546, "2546 (system_server) R 1 0 0 0 0 0 0 0 1000 0 0 0 0 0 0 0 3 0 1000\n"}, {3456, "3456 (system_server) S 1 0 0 0 0 0 0 0 3000 0 0 0 0 0 0 0 3 0 2300\n"}, {4789, "4789 (system_server) D 1 0 0 0 0 0 0 0 2000 0 0 0 0 0 0 0 3 0 4500\n"}, {7890, "7890 (logd) D 1 0 0 0 0 0 0 0 10000 0 0 0 0 0 0 0 3 0 2345\n"}, {8978, "8978 (logd) D 1 0 0 0 0 0 0 0 1000 0 0 0 0 0 0 0 3 0 2500\n"}, {12890, "12890 (logd) D 1 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 3 0 2900\n"}, {18902, "18902 (disk I/O) D 1 0 0 0 0 0 0 0 30000 0 0 0 0 0 0 0 3 0 897654\n"}, {21345, "21345 (disk I/O) D 1 0 0 0 0 0 0 0 15000 0 0 0 0 0 0 0 3 0 904000\n"}, {32452, "32452 (disk I/O) D 1 0 0 0 0 0 0 0 678 0 0 0 0 0 0 0 3 0 1007000\n"}, {28900, "28900 (tombstoned) D 1 0 0 0 0 0 0 0 89765 0 0 0 0 0 0 0 3 0 2345671\n"}, }; struct ProcessIoPerfData expectedProcessIoPerfData = {}; expectedProcessIoPerfData.topNIoBlockedUids.push_back({ // uid: 1001000 .userId = 10, .packageName = "shared:android.uid.system", .count = 4, .topNProcesses = {{"logd", 3}, {"system_server", 1}}, }); expectedProcessIoPerfData.topNIoBlockedUidsTotalTaskCnt.push_back(6); expectedProcessIoPerfData.topNIoBlockedUids.push_back({ // uid: 1009 .userId = 0, .packageName = "mount", .count = 3, .topNProcesses = {{"disk I/O", 3}}, }); expectedProcessIoPerfData.topNIoBlockedUidsTotalTaskCnt.push_back(3); expectedProcessIoPerfData.topNMajorFaultUids.push_back({ // uid: 1001234 .userId = 10, .packageName = "1001234", .count = 89765, .topNProcesses = {{"tombstoned", 89765}}, }); expectedProcessIoPerfData.topNMajorFaultUids.push_back({ // uid: 1009 .userId = 0, .packageName = "mount", .count = 45678, .topNProcesses = {{"disk I/O", 45678}}, }); expectedProcessIoPerfData.totalMajorFaults = 156663; expectedProcessIoPerfData.majorFaultsPercentChange = 0; TemporaryDir firstSnapshot; auto ret = populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat, perProcessStatus, perThreadStat); ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error(); IoPerfCollection collector; collector.mProcPidStat = new ProcPidStat(firstSnapshot.path); collector.mTopNStatsPerCategory = 2; collector.mTopNStatsPerSubcategory = 2; ASSERT_TRUE(collector.mProcPidStat->enabled()) << "Files under the temporary proc directory are inaccessible"; struct ProcessIoPerfData actualProcessIoPerfData = {}; ret = collector.collectProcessIoPerfDataLocked(CollectionInfo{}, &actualProcessIoPerfData); ASSERT_TRUE(ret) << "Failed to collect first snapshot: " << ret.error(); EXPECT_TRUE(isEqual(expectedProcessIoPerfData, actualProcessIoPerfData)) << "First snapshot doesn't match.\nExpected:\n" << toString(expectedProcessIoPerfData) << "\nActual:\n" << toString(actualProcessIoPerfData); pidToTids = { {1, {1, 453}}, {2546, {2546, 3456, 4789}}, }; perProcessStat = { {1, "1 (init) S 0 0 0 0 0 0 0 0 880 0 0 0 0 0 0 0 2 0 0\n"}, {2546, "2546 (system_server) R 1 0 0 0 0 0 0 0 18000 0 0 0 0 0 0 0 3 0 1000\n"}, }; perProcessStatus = { {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"}, {2546, "Pid:\t2546\nTgid:\t2546\nUid:\t1001000\t1001000\t1001000\t1001000\n"}, }; perThreadStat = { {1, "1 (init) S 0 0 0 0 0 0 0 0 800 0 0 0 0 0 0 0 2 0 0\n"}, {453, "453 (init) S 0 0 0 0 0 0 0 0 80 0 0 0 0 0 0 0 2 0 275\n"}, {2546, "2546 (system_server) R 1 0 0 0 0 0 0 0 3000 0 0 0 0 0 0 0 3 0 1000\n"}, {3456, "3456 (system_server) S 1 0 0 0 0 0 0 0 9000 0 0 0 0 0 0 0 3 0 2300\n"}, {4789, "4789 (system_server) D 1 0 0 0 0 0 0 0 6000 0 0 0 0 0 0 0 3 0 4500\n"}, }; expectedProcessIoPerfData = {}; expectedProcessIoPerfData.topNIoBlockedUids.push_back({ // uid: 1001000 .userId = 10, .packageName = "shared:android.uid.system", .count = 1, .topNProcesses = {{"system_server", 1}}, }); expectedProcessIoPerfData.topNIoBlockedUidsTotalTaskCnt.push_back(3); expectedProcessIoPerfData.topNMajorFaultUids.push_back({ // uid: 1001000 .userId = 10, .packageName = "shared:android.uid.system", .count = 12000, .topNProcesses = {{"system_server", 12000}}, }); expectedProcessIoPerfData.topNMajorFaultUids.push_back({ // uid: 0 .userId = 0, .packageName = "root", .count = 660, .topNProcesses = {{"init", 660}}, }); expectedProcessIoPerfData.totalMajorFaults = 12660; expectedProcessIoPerfData.majorFaultsPercentChange = ((12660.0 - 156663.0) / 156663.0) * 100; TemporaryDir secondSnapshot; ret = populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat, perProcessStatus, perThreadStat); ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error(); collector.mProcPidStat->mPath = secondSnapshot.path; actualProcessIoPerfData = {}; ret = collector.collectProcessIoPerfDataLocked(CollectionInfo{}, &actualProcessIoPerfData); ASSERT_TRUE(ret) << "Failed to collect second snapshot: " << ret.error(); EXPECT_TRUE(isEqual(expectedProcessIoPerfData, actualProcessIoPerfData)) << "Second snapshot doesn't match.\nExpected:\n" << toString(expectedProcessIoPerfData) << "\nActual:\n" << toString(actualProcessIoPerfData); } TEST(IoPerfCollectionTest, TestProcPidContentsLessThanTopNStatsLimit) { std::unordered_map> pidToTids = { {1, {1, 453}}, }; std::unordered_map perProcessStat = { {1, "1 (init) S 0 0 0 0 0 0 0 0 880 0 0 0 0 0 0 0 2 0 0\n"}, }; std::unordered_map perProcessStatus = { {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"}, }; std::unordered_map perThreadStat = { {1, "1 (init) S 0 0 0 0 0 0 0 0 800 0 0 0 0 0 0 0 2 0 0\n"}, {453, "453 (init) S 0 0 0 0 0 0 0 0 80 0 0 0 0 0 0 0 2 0 275\n"}, }; struct ProcessIoPerfData expectedProcessIoPerfData = {}; expectedProcessIoPerfData.topNMajorFaultUids.push_back({ // uid: 0 .userId = 0, .packageName = "root", .count = 880, .topNProcesses = {{"init", 880}}, }); expectedProcessIoPerfData.totalMajorFaults = 880; expectedProcessIoPerfData.majorFaultsPercentChange = 0.0; TemporaryDir prodDir; auto ret = populateProcPidDir(prodDir.path, pidToTids, perProcessStat, perProcessStatus, perThreadStat); ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error(); IoPerfCollection collector; collector.mTopNStatsPerCategory = 5; collector.mTopNStatsPerSubcategory = 3; collector.mProcPidStat = new ProcPidStat(prodDir.path); struct ProcessIoPerfData actualProcessIoPerfData = {}; ret = collector.collectProcessIoPerfDataLocked(CollectionInfo{}, &actualProcessIoPerfData); ASSERT_TRUE(ret) << "Failed to collect proc pid contents: " << ret.error(); EXPECT_TRUE(isEqual(expectedProcessIoPerfData, actualProcessIoPerfData)) << "proc pid contents don't match.\nExpected:\n" << toString(expectedProcessIoPerfData) << "\nActual:\n" << toString(actualProcessIoPerfData); } TEST(IoPerfCollectionTest, TestHandlesInvalidDumpArguments) { sp collector = new IoPerfCollection(); collector->start(); Vector args; args.push_back(String16(kStartCustomCollectionFlag)); args.push_back(String16("Invalid flag")); args.push_back(String16("Invalid value")); ASSERT_FALSE(collector->dump(-1, args).ok()); args.clear(); args.push_back(String16(kStartCustomCollectionFlag)); args.push_back(String16(kIntervalFlag)); args.push_back(String16("Invalid interval")); ASSERT_FALSE(collector->dump(-1, args).ok()); args.clear(); args.push_back(String16(kStartCustomCollectionFlag)); args.push_back(String16(kMaxDurationFlag)); args.push_back(String16("Invalid duration")); ASSERT_FALSE(collector->dump(-1, args).ok()); args.clear(); args.push_back(String16(kEndCustomCollectionFlag)); args.push_back(String16(kMaxDurationFlag)); args.push_back(String16(std::to_string(kTestCustomCollectionDuration.count()).c_str())); ASSERT_FALSE(collector->dump(-1, args).ok()); args.clear(); args.push_back(String16("Invalid flag")); ASSERT_FALSE(collector->dump(-1, args).ok()); collector->terminate(); } } // namespace watchdog } // namespace automotive } // namespace android