1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "ProcPidDir.h"
18 #include "UidProcStatsCollector.h"
19 #include "UidProcStatsCollectorTestUtils.h"
20
21 #include <android-base/file.h>
22 #include <android-base/stringprintf.h>
23 #include <gmock/gmock.h>
24
25 #include <inttypes.h>
26
27 #include <algorithm>
28 #include <string>
29
30 namespace android {
31 namespace automotive {
32 namespace watchdog {
33
34 using ::android::automotive::watchdog::testing::populateProcPidDir;
35 using ::android::base::StringAppendF;
36 using ::android::base::StringPrintf;
37 using ::testing::UnorderedPointwise;
38
39 namespace {
40
41 MATCHER(UidProcStatsByUidEq, "") {
42 const auto& actual = std::get<0>(arg);
43 const auto& expected = std::get<1>(arg);
44 return actual.first == expected.first &&
45 ExplainMatchResult(UidProcStatsEq(expected.second), actual.second, result_listener);
46 }
47
pidStatusStr(pid_t pid,uid_t uid)48 std::string pidStatusStr(pid_t pid, uid_t uid) {
49 return StringPrintf("Pid:\t%" PRIu32 "\nTgid:\t%" PRIu32 "\nUid:\t%" PRIu32 "\n", pid, pid,
50 uid);
51 }
52
toString(const std::unordered_map<uid_t,UidProcStats> & uidProcStatsByUid)53 std::string toString(const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) {
54 std::string buffer;
55 StringAppendF(&buffer, "Number of UIDs: %" PRIi32 "\n",
56 static_cast<int>(uidProcStatsByUid.size()));
57 for (const auto& [uid, stats] : uidProcStatsByUid) {
58 StringAppendF(&buffer, "{UID: %d, %s}", uid, stats.toString().c_str());
59 }
60 return buffer;
61 }
62
63 } // namespace
64
TEST(UidProcStatsCollectorTest,TestValidStatFiles)65 TEST(UidProcStatsCollectorTest, TestValidStatFiles) {
66 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
67 {1, {1, 453}},
68 {1000, {1000, 1100}},
69 };
70
71 std::unordered_map<pid_t, std::string> perProcessStat = {
72 {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0\n"},
73 {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"},
74 };
75
76 std::unordered_map<pid_t, std::string> perProcessStatus = {
77 {1, pidStatusStr(1, 0)},
78 {1000, pidStatusStr(1000, 10001234)},
79 };
80
81 std::unordered_map<pid_t, std::string> perThreadStat = {
82 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 0\n"},
83 {453, "453 (init) D 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0 2 0 275\n"},
84 {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"},
85 {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"},
86 };
87
88 std::unordered_map<uid_t, UidProcStats> expected =
89 {{0,
90 UidProcStats{.totalMajorFaults = 220,
91 .totalTasksCount = 2,
92 .ioBlockedTasksCount = 1,
93 .processStatsByPid = {{1, {"init", 0, 220, 2, 1}}}}},
94 {10001234,
95 UidProcStats{.totalMajorFaults = 600,
96 .totalTasksCount = 2,
97 .ioBlockedTasksCount = 2,
98 .processStatsByPid = {{1000, {"system_server", 13'400, 600, 2, 2}}}}}};
99
100 TemporaryDir firstSnapshot;
101 ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
102 perProcessStatus, perThreadStat));
103
104 UidProcStatsCollector collector(firstSnapshot.path);
105
106 ASSERT_TRUE(collector.enabled())
107 << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
108 ASSERT_RESULT_OK(collector.collect());
109
110 auto actual = collector.deltaStats();
111
112 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
113 << "First snapshot doesn't match.\nExpected:\n"
114 << toString(expected) << "\nActual:\n"
115 << toString(actual);
116 pidToTids = {
117 {1, {1, 453}}, {1000, {1000, 1400}}, // TID 1100 terminated and 1400 instantiated.
118 };
119
120 perProcessStat = {
121 {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 0 0 0 0 0 0 2 0 0\n"},
122 {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"},
123 };
124
125 perThreadStat = {
126 {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 0\n"},
127 {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 0 0 0 0 0 0 2 0 275\n"},
128 {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"},
129 // TID 1100 hits +400 major page faults before terminating. This is counted against
130 // PID 1000's perProcessStat.
131 {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"},
132 };
133
134 expected = {{0,
135 {.totalMajorFaults = 700,
136 .totalTasksCount = 2,
137 .ioBlockedTasksCount = 0,
138 .processStatsByPid = {{1, {"init", 0, 700, 2, 0}}}}},
139 {10001234,
140 {.totalMajorFaults = 950,
141 .totalTasksCount = 2,
142 .ioBlockedTasksCount = 0,
143 .processStatsByPid = {{1000, {"system_server", 13'400, 950, 2, 0}}}}}};
144
145 TemporaryDir secondSnapshot;
146 ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
147 perProcessStatus, perThreadStat));
148
149 collector.mPath = secondSnapshot.path;
150
151 ASSERT_TRUE(collector.enabled())
152 << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
153 ASSERT_RESULT_OK(collector.collect());
154
155 actual = collector.deltaStats();
156 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
157 << "Second snapshot doesn't match.\nExpected:\n"
158 << toString(expected) << "\nActual:\n"
159 << toString(actual);
160 }
161
TEST(UidProcStatsCollectorTest,TestHandlesProcessTerminationBetweenScanningAndParsing)162 TEST(UidProcStatsCollectorTest, TestHandlesProcessTerminationBetweenScanningAndParsing) {
163 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
164 {1, {1}},
165 {100, {100}}, // Process terminates after scanning PID directory.
166 {1000, {1000}}, // Process terminates after reading stat file.
167 {2000, {2000}}, // Process terminates after scanning task directory.
168 {3000, {3000, 3300}}, // TID 3300 terminates after scanning task directory.
169 };
170
171 std::unordered_map<pid_t, std::string> perProcessStat = {
172 {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 1 0 0\n"},
173 // Process 100 terminated.
174 {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"},
175 {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 1 0 4567\n"},
176 {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"},
177 };
178
179 std::unordered_map<pid_t, std::string> perProcessStatus = {
180 {1, pidStatusStr(1, 0)},
181 // Process 1000 terminated.
182 {2000, pidStatusStr(2000, 10001234)},
183 {3000, pidStatusStr(3000, 10001234)},
184 };
185
186 std::unordered_map<pid_t, std::string> perThreadStat = {
187 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
188 // Process 2000 terminated.
189 {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"},
190 // TID 3300 terminated.
191 };
192
193 std::unordered_map<uid_t, UidProcStats> expected =
194 {{0,
195 UidProcStats{.totalMajorFaults = 220,
196 .totalTasksCount = 1,
197 .ioBlockedTasksCount = 0,
198 .processStatsByPid = {{1, {"init", 0, 220, 1, 0}}}}},
199 {10001234,
200 UidProcStats{.totalMajorFaults = 11500,
201 .totalTasksCount = 2,
202 .ioBlockedTasksCount = 0,
203 .processStatsByPid = {{2000, {"logd", 4567, 1200, 1, 0}},
204 {3000, {"disk I/O", 67890, 10'300, 1, 0}}}}}};
205
206 TemporaryDir procDir;
207 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
208 perThreadStat));
209
210 UidProcStatsCollector collector(procDir.path);
211
212 ASSERT_TRUE(collector.enabled())
213 << "Files under the path `" << procDir.path << "` are inaccessible";
214 ASSERT_RESULT_OK(collector.collect());
215
216 auto actual = collector.deltaStats();
217 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
218 << "Proc pid contents doesn't match.\nExpected:\n"
219 << toString(expected) << "\nActual:\n"
220 << toString(actual);
221 }
222
TEST(UidProcStatsCollectorTest,TestHandlesPidTidReuse)223 TEST(UidProcStatsCollectorTest, TestHandlesPidTidReuse) {
224 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
225 {1, {1, 367, 453, 589}},
226 {1000, {1000}},
227 {2345, {2345}},
228 };
229
230 std::unordered_map<pid_t, std::string> perProcessStat = {
231 {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 4 0 0\n"},
232 {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"},
233 {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
234 };
235
236 std::unordered_map<pid_t, std::string> perProcessStatus = {
237 {1, pidStatusStr(1, 0)},
238 {1000, pidStatusStr(1000, 10001234)},
239 {2345, pidStatusStr(2345, 10001234)},
240 };
241
242 std::unordered_map<pid_t, std::string> perThreadStat = {
243 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 4 0 0\n"},
244 {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 0 0 0 0 0 0 4 0 100\n"},
245 {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 4 0 275\n"},
246 {589, "589 (init) D 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 4 0 600\n"},
247 {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"},
248 {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
249 };
250
251 std::unordered_map<uid_t, UidProcStats> expected =
252 {{0,
253 UidProcStats{.totalMajorFaults = 1200,
254 .totalTasksCount = 4,
255 .ioBlockedTasksCount = 1,
256 .processStatsByPid = {{1, {"init", 0, 1200, 4, 1}}}}},
257 {10001234,
258 UidProcStats{.totalMajorFaults = 54'604,
259 .totalTasksCount = 2,
260 .ioBlockedTasksCount = 0,
261 .processStatsByPid = {{1000, {"system_server", 1000, 250, 1, 0}},
262 {2345, {"logd", 456, 54'354, 1, 0}}}}}};
263
264 TemporaryDir firstSnapshot;
265 ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
266 perProcessStatus, perThreadStat));
267
268 UidProcStatsCollector collector(firstSnapshot.path);
269
270 ASSERT_TRUE(collector.enabled())
271 << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
272 ASSERT_RESULT_OK(collector.collect());
273
274 auto actual = collector.deltaStats();
275
276 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
277 << "First snapshot doesn't match.\nExpected:\n"
278 << toString(expected) << "\nActual:\n"
279 << toString(actual);
280
281 pidToTids = {
282 {1, {1, 589}}, // TID 589 reused by the same process.
283 {367, {367, 2000}}, // TID 367 reused as a PID. PID 2000 reused as a TID.
284 // PID 1000 reused as a new PID. TID 453 reused by a different PID.
285 {1000, {1000, 453}},
286 };
287
288 perProcessStat = {
289 {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 0\n"},
290 {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"},
291 {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 0 0 0 0 0 0 2 0 4650\n"},
292 };
293
294 perProcessStatus = {
295 {1, pidStatusStr(1, 0)},
296 {367, pidStatusStr(367, 10001234)},
297 {1000, pidStatusStr(1000, 10001234)},
298 };
299
300 perThreadStat = {
301 {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 2 0 0\n"},
302 {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 0 0 0 0 0 0 2 0 2345\n"},
303 {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"},
304 {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"},
305 {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 4650\n"},
306 {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 4770\n"},
307 };
308
309 expected = {{0,
310 UidProcStats{.totalMajorFaults = 600,
311 .totalTasksCount = 2,
312 .ioBlockedTasksCount = 0,
313 .processStatsByPid = {{1, {"init", 0, 600, 2, 0}}}}},
314 {10001234,
315 UidProcStats{.totalMajorFaults = 2100,
316 .totalTasksCount = 4,
317 .ioBlockedTasksCount = 1,
318 .processStatsByPid = {{367, {"system_server", 3450, 100, 2, 0}},
319 {1000, {"logd", 4650, 2000, 2, 1}}}}}};
320
321 TemporaryDir secondSnapshot;
322 ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
323 perProcessStatus, perThreadStat));
324
325 collector.mPath = secondSnapshot.path;
326
327 ASSERT_TRUE(collector.enabled())
328 << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
329 ASSERT_RESULT_OK(collector.collect());
330
331 actual = collector.deltaStats();
332
333 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
334 << "Second snapshot doesn't match.\nExpected:\n"
335 << toString(expected) << "\nActual:\n"
336 << toString(actual);
337 }
338
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedProcessStatFile)339 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatFile) {
340 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
341 {1, {1}},
342 };
343
344 std::unordered_map<pid_t, std::string> perProcessStat = {
345 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
346 };
347
348 std::unordered_map<pid_t, std::string> perProcessStatus = {
349 {1, pidStatusStr(1, 0)},
350 };
351
352 std::unordered_map<pid_t, std::string> perThreadStat = {
353 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
354 };
355
356 TemporaryDir procDir;
357 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
358 perThreadStat));
359
360 UidProcStatsCollector collector(procDir.path);
361
362 ASSERT_TRUE(collector.enabled())
363 << "Files under the path `" << procDir.path << "` are inaccessible";
364 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process stat file";
365 }
366
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedProcessStatusFile)367 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatusFile) {
368 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
369 {1, {1}},
370 };
371
372 std::unordered_map<pid_t, std::string> perProcessStat = {
373 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
374 };
375
376 std::unordered_map<pid_t, std::string> perProcessStatus = {
377 {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
378 };
379
380 std::unordered_map<pid_t, std::string> perThreadStat = {
381 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
382 };
383
384 TemporaryDir procDir;
385 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
386 perThreadStat));
387
388 UidProcStatsCollector collector(procDir.path);
389
390 ASSERT_TRUE(collector.enabled())
391 << "Files under the path `" << procDir.path << "` are inaccessible";
392 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process status file";
393 }
394
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedThreadStatFile)395 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadStatFile) {
396 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
397 {1, {1, 234}},
398 };
399
400 std::unordered_map<pid_t, std::string> perProcessStat = {
401 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
402 };
403
404 std::unordered_map<pid_t, std::string> perProcessStatus = {
405 {1, pidStatusStr(1, 0)},
406 };
407
408 std::unordered_map<pid_t, std::string> perThreadStat = {
409 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
410 {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
411 };
412
413 TemporaryDir procDir;
414 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
415 perThreadStat));
416
417 UidProcStatsCollector collector(procDir.path);
418
419 ASSERT_TRUE(collector.enabled())
420 << "Files under the path `" << procDir.path << "` are inaccessible";
421 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file";
422 }
423
TEST(UidProcStatsCollectorTest,TestHandlesSpaceInCommName)424 TEST(UidProcStatsCollectorTest, TestHandlesSpaceInCommName) {
425 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
426 {1, {1}},
427 };
428
429 std::unordered_map<pid_t, std::string> perProcessStat = {
430 {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"},
431 };
432
433 std::unordered_map<pid_t, std::string> perProcessStatus = {
434 {1, pidStatusStr(1, 0)},
435 };
436
437 std::unordered_map<pid_t, std::string> perThreadStat = {
438 {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"},
439 };
440
441 std::unordered_map<uid_t, UidProcStats> expected = {
442 {0,
443 UidProcStats{.totalMajorFaults = 200,
444 .totalTasksCount = 1,
445 .ioBlockedTasksCount = 0,
446 .processStatsByPid = {
447 {1, {"random process name with space", 0, 200, 1, 0}}}}}};
448
449 TemporaryDir procDir;
450 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
451 perThreadStat));
452
453 UidProcStatsCollector collector(procDir.path);
454
455 ASSERT_TRUE(collector.enabled())
456 << "Files under the path `" << procDir.path << "` are inaccessible";
457 ASSERT_RESULT_OK(collector.collect());
458
459 auto actual = collector.deltaStats();
460
461 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
462 << "Proc pid contents doesn't match.\nExpected:\n"
463 << toString(expected) << "\nActual:\n"
464 << toString(actual);
465 }
466
TEST(UidProcStatsCollectorTest,TestUidProcStatsCollectorContentsFromDevice)467 TEST(UidProcStatsCollectorTest, TestUidProcStatsCollectorContentsFromDevice) {
468 UidProcStatsCollector collector;
469 ASSERT_TRUE(collector.enabled()) << "/proc/[pid]/.* files are inaccessible";
470 ASSERT_RESULT_OK(collector.collect());
471
472 const auto& processStats = collector.deltaStats();
473
474 // The below check should pass because there should be at least one process.
475 EXPECT_GT(processStats.size(), 0);
476 }
477
478 } // namespace watchdog
479 } // namespace automotive
480 } // namespace android
481