| /* |
| * 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 "ProcPidDir.h" |
| #include "UidProcStatsCollector.h" |
| #include "UidProcStatsCollectorTestUtils.h" |
| |
| #include <android-base/file.h> |
| #include <android-base/stringprintf.h> |
| #include <gmock/gmock.h> |
| |
| #include <inttypes.h> |
| |
| #include <algorithm> |
| #include <string> |
| |
| namespace android { |
| namespace automotive { |
| namespace watchdog { |
| |
| using ::android::automotive::watchdog::testing::populateProcPidDir; |
| using ::android::base::StringAppendF; |
| using ::android::base::StringPrintf; |
| using ::testing::UnorderedPointwise; |
| |
| namespace { |
| |
| MATCHER(UidProcStatsByUidEq, "") { |
| const auto& actual = std::get<0>(arg); |
| const auto& expected = std::get<1>(arg); |
| return actual.first == expected.first && |
| ExplainMatchResult(UidProcStatsEq(expected.second), actual.second, result_listener); |
| } |
| |
| std::string pidStatusStr(pid_t pid, uid_t uid) { |
| return StringPrintf("Pid:\t%" PRIu32 "\nTgid:\t%" PRIu32 "\nUid:\t%" PRIu32 "\n", pid, pid, |
| uid); |
| } |
| |
| std::string toString(const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) { |
| std::string buffer; |
| StringAppendF(&buffer, "Number of UIDs: %" PRIi32 "\n", |
| static_cast<int>(uidProcStatsByUid.size())); |
| for (const auto& [uid, stats] : uidProcStatsByUid) { |
| StringAppendF(&buffer, "{UID: %d, %s}", uid, stats.toString().c_str()); |
| } |
| return buffer; |
| } |
| |
| } // namespace |
| |
| TEST(UidProcStatsCollectorTest, TestValidStatFiles) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1, 453}}, |
| {1000, {1000, 1100}}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> 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"}, |
| {1000, "1000 (system_server) D 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 13400\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| {1000, pidStatusStr(1000, 10001234)}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> 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) D 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0 2 0 275\n"}, |
| {1000, "1000 (system_server) D 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 2 0 13400\n"}, |
| {1100, "1100 (system_server) D 1 0 0 0 0 0 0 0 350 0 0 0 0 0 0 0 2 0 13900\n"}, |
| }; |
| |
| std::unordered_map<uid_t, UidProcStats> expected = |
| {{0, |
| UidProcStats{.totalMajorFaults = 220, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 1, |
| .processStatsByPid = {{1, {"init", 0, 220, 2, 1}}}}}, |
| {10001234, |
| UidProcStats{.totalMajorFaults = 600, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 2, |
| .processStatsByPid = {{1000, {"system_server", 13'400, 600, 2, 2}}}}}}; |
| |
| TemporaryDir firstSnapshot; |
| ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat, |
| perProcessStatus, perThreadStat)); |
| |
| UidProcStatsCollector collector(firstSnapshot.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << firstSnapshot.path << "` are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| auto actual = collector.deltaStats(); |
| |
| EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected)) |
| << "First snapshot doesn't match.\nExpected:\n" |
| << toString(expected) << "\nActual:\n" |
| << toString(actual); |
| pidToTids = { |
| {1, {1, 453}}, {1000, {1000, 1400}}, // TID 1100 terminated and 1400 instantiated. |
| }; |
| |
| perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 0 0 0 0 0 0 2 0 0\n"}, |
| {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 1550 0 0 0 0 0 0 0 2 0 13400\n"}, |
| }; |
| |
| perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 0\n"}, |
| {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 0 0 0 0 0 0 2 0 275\n"}, |
| {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 13400\n"}, |
| // TID 1100 hits +400 major page faults before terminating. This is counted against |
| // PID 1000's perProcessStat. |
| {1400, "1400 (system_server) S 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 8977476\n"}, |
| }; |
| |
| expected = {{0, |
| {.totalMajorFaults = 700, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = {{1, {"init", 0, 700, 2, 0}}}}}, |
| {10001234, |
| {.totalMajorFaults = 950, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = {{1000, {"system_server", 13'400, 950, 2, 0}}}}}}; |
| |
| TemporaryDir secondSnapshot; |
| ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat, |
| perProcessStatus, perThreadStat)); |
| |
| collector.mPath = secondSnapshot.path; |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << secondSnapshot.path << "` are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| actual = collector.deltaStats(); |
| EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected)) |
| << "Second snapshot doesn't match.\nExpected:\n" |
| << toString(expected) << "\nActual:\n" |
| << toString(actual); |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestHandlesProcessTerminationBetweenScanningAndParsing) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1}}, |
| {100, {100}}, // Process terminates after scanning PID directory. |
| {1000, {1000}}, // Process terminates after reading stat file. |
| {2000, {2000}}, // Process terminates after scanning task directory. |
| {3000, {3000, 3300}}, // TID 3300 terminates after scanning task directory. |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 1 0 0\n"}, |
| // Process 100 terminated. |
| {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 1 0 1000\n"}, |
| {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 1 0 4567\n"}, |
| {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 10300 0 0 0 0 0 0 0 2 0 67890\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| // Process 1000 terminated. |
| {2000, pidStatusStr(2000, 10001234)}, |
| {3000, pidStatusStr(3000, 10001234)}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"}, |
| // Process 2000 terminated. |
| {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 2400 0 0 0 0 0 0 0 2 0 67890\n"}, |
| // TID 3300 terminated. |
| }; |
| |
| std::unordered_map<uid_t, UidProcStats> expected = |
| {{0, |
| UidProcStats{.totalMajorFaults = 220, |
| .totalTasksCount = 1, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = {{1, {"init", 0, 220, 1, 0}}}}}, |
| {10001234, |
| UidProcStats{.totalMajorFaults = 11500, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = {{2000, {"logd", 4567, 1200, 1, 0}}, |
| {3000, {"disk I/O", 67890, 10'300, 1, 0}}}}}}; |
| |
| TemporaryDir procDir; |
| ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus, |
| perThreadStat)); |
| |
| UidProcStatsCollector collector(procDir.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << procDir.path << "` are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| auto actual = collector.deltaStats(); |
| EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected)) |
| << "Proc pid contents doesn't match.\nExpected:\n" |
| << toString(expected) << "\nActual:\n" |
| << toString(actual); |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestHandlesPidTidReuse) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1, 367, 453, 589}}, |
| {1000, {1000}}, |
| {2345, {2345}}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 4 0 0\n"}, |
| {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"}, |
| {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| {1000, pidStatusStr(1000, 10001234)}, |
| {2345, pidStatusStr(2345, 10001234)}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 4 0 0\n"}, |
| {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 0 0 0 0 0 0 4 0 100\n"}, |
| {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 4 0 275\n"}, |
| {589, "589 (init) D 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 4 0 600\n"}, |
| {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"}, |
| {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"}, |
| }; |
| |
| std::unordered_map<uid_t, UidProcStats> expected = |
| {{0, |
| UidProcStats{.totalMajorFaults = 1200, |
| .totalTasksCount = 4, |
| .ioBlockedTasksCount = 1, |
| .processStatsByPid = {{1, {"init", 0, 1200, 4, 1}}}}}, |
| {10001234, |
| UidProcStats{.totalMajorFaults = 54'604, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = {{1000, {"system_server", 1000, 250, 1, 0}}, |
| {2345, {"logd", 456, 54'354, 1, 0}}}}}}; |
| |
| TemporaryDir firstSnapshot; |
| ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat, |
| perProcessStatus, perThreadStat)); |
| |
| UidProcStatsCollector collector(firstSnapshot.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << firstSnapshot.path << "` are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| auto actual = collector.deltaStats(); |
| |
| EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected)) |
| << "First snapshot doesn't match.\nExpected:\n" |
| << toString(expected) << "\nActual:\n" |
| << toString(actual); |
| |
| pidToTids = { |
| {1, {1, 589}}, // TID 589 reused by the same process. |
| {367, {367, 2000}}, // TID 367 reused as a PID. PID 2000 reused as a TID. |
| // PID 1000 reused as a new PID. TID 453 reused by a different PID. |
| {1000, {1000, 453}}, |
| }; |
| |
| perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 0\n"}, |
| {367, "367 (system_server) R 1 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 2 0 3450\n"}, |
| {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 0 0 0 0 0 0 2 0 4650\n"}, |
| }; |
| |
| perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| {367, pidStatusStr(367, 10001234)}, |
| {1000, pidStatusStr(1000, 10001234)}, |
| }; |
| |
| perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 2 0 0\n"}, |
| {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 0 0 0 0 0 0 2 0 2345\n"}, |
| {367, "367 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3450\n"}, |
| {2000, "2000 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3670\n"}, |
| {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 4650\n"}, |
| {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 4770\n"}, |
| }; |
| |
| expected = {{0, |
| UidProcStats{.totalMajorFaults = 600, |
| .totalTasksCount = 2, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = {{1, {"init", 0, 600, 2, 0}}}}}, |
| {10001234, |
| UidProcStats{.totalMajorFaults = 2100, |
| .totalTasksCount = 4, |
| .ioBlockedTasksCount = 1, |
| .processStatsByPid = {{367, {"system_server", 3450, 100, 2, 0}}, |
| {1000, {"logd", 4650, 2000, 2, 1}}}}}}; |
| |
| TemporaryDir secondSnapshot; |
| ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat, |
| perProcessStatus, perThreadStat)); |
| |
| collector.mPath = secondSnapshot.path; |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << secondSnapshot.path << "` are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| actual = collector.deltaStats(); |
| |
| EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected)) |
| << "Second snapshot doesn't match.\nExpected:\n" |
| << toString(expected) << "\nActual:\n" |
| << toString(actual); |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatFile) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1}}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"}, |
| }; |
| |
| TemporaryDir procDir; |
| ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus, |
| perThreadStat)); |
| |
| UidProcStatsCollector collector(procDir.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << procDir.path << "` are inaccessible"; |
| ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process stat file"; |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatusFile) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1}}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"}, |
| }; |
| |
| TemporaryDir procDir; |
| ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus, |
| perThreadStat)); |
| |
| UidProcStatsCollector collector(procDir.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << procDir.path << "` are inaccessible"; |
| ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process status file"; |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadStatFile) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1, 234}}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perThreadStat = { |
| {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"}, |
| {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"}, |
| }; |
| |
| TemporaryDir procDir; |
| ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus, |
| perThreadStat)); |
| |
| UidProcStatsCollector collector(procDir.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << procDir.path << "` are inaccessible"; |
| ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file"; |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestHandlesSpaceInCommName) { |
| std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = { |
| {1, {1}}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStat = { |
| {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perProcessStatus = { |
| {1, pidStatusStr(1, 0)}, |
| }; |
| |
| std::unordered_map<pid_t, std::string> perThreadStat = { |
| {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"}, |
| }; |
| |
| std::unordered_map<uid_t, UidProcStats> expected = { |
| {0, |
| UidProcStats{.totalMajorFaults = 200, |
| .totalTasksCount = 1, |
| .ioBlockedTasksCount = 0, |
| .processStatsByPid = { |
| {1, {"random process name with space", 0, 200, 1, 0}}}}}}; |
| |
| TemporaryDir procDir; |
| ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus, |
| perThreadStat)); |
| |
| UidProcStatsCollector collector(procDir.path); |
| |
| ASSERT_TRUE(collector.enabled()) |
| << "Files under the path `" << procDir.path << "` are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| auto actual = collector.deltaStats(); |
| |
| EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected)) |
| << "Proc pid contents doesn't match.\nExpected:\n" |
| << toString(expected) << "\nActual:\n" |
| << toString(actual); |
| } |
| |
| TEST(UidProcStatsCollectorTest, TestUidProcStatsCollectorContentsFromDevice) { |
| UidProcStatsCollector collector; |
| ASSERT_TRUE(collector.enabled()) << "/proc/[pid]/.* files are inaccessible"; |
| ASSERT_RESULT_OK(collector.collect()); |
| |
| const auto& processStats = collector.deltaStats(); |
| |
| // The below check should pass because there should be at least one process. |
| EXPECT_GT(processStats.size(), 0); |
| } |
| |
| } // namespace watchdog |
| } // namespace automotive |
| } // namespace android |