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
ticksToMillis(int32_t clockTicks)63 int64_t ticksToMillis(int32_t clockTicks) {
64 return (clockTicks * 1000) / sysconf(_SC_CLK_TCK);
65 }
66
67 } // namespace
68
TEST(UidProcStatsCollectorTest,TestValidStatFiles)69 TEST(UidProcStatsCollectorTest, TestValidStatFiles) {
70 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
71 {1, {1, 453}},
72 {1000, {1000, 1100}},
73 };
74
75 std::unordered_map<pid_t, std::string> perProcessStat = {
76 {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 6 4 0 0 0 0 2 0 19\n"},
77 {1000, "1000 (system_server) D 1 0 0 0 0 0 0 0 600 0 8000 4000 0 0 0 0 2 0 13400\n"},
78 };
79
80 std::unordered_map<pid_t, std::string> perProcessStatus = {
81 {1, pidStatusStr(1, 0)},
82 {1000, pidStatusStr(1000, 10001234)},
83 };
84
85 std::unordered_map<pid_t, std::string> perThreadStat = {
86 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 3 2 0 0 0 0 2 0 19\n"},
87 {453, "453 (init) D 0 0 0 0 0 0 0 0 20 0 3 2 0 0 0 0 2 0 275\n"},
88 {1000, "1000 (system_server) D 1 0 0 0 0 0 0 0 250 0 4000 2000 0 0 0 0 2 0 13400\n"},
89 {1100, "1100 (system_server) D 1 0 0 0 0 0 0 0 350 0 4000 2000 0 0 0 0 2 0 13900\n"},
90 };
91
92 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
93 {1, "cpu0\n300000 5\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
94 {453, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 5\ncpu7\n2000000 0"},
95 {1000,
96 "cpu0\n300000 0\n1700000 1000\ncpu4\n710000 1000\n1800000 3000\ncpu7\n2000000 6000"},
97 {1100, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 1000\ncpu7\n2000000 0"},
98 };
99
100 std::unordered_map<uid_t, UidProcStats> expected =
101 {{0,
102 UidProcStats{.cpuTimeMillis = ticksToMillis(10),
103 .cpuCycles = 105000000,
104 .totalMajorFaults = 220,
105 .totalTasksCount = 2,
106 .ioBlockedTasksCount = 1,
107 .processStatsByPid = {{1,
108 {"init",
109 ticksToMillis(19),
110 ticksToMillis(10),
111 105000000,
112 220,
113 2,
114 1,
115 {{1, 15000000}, {453, 90000000}}}}}}},
116 {10001234,
117 UidProcStats{.cpuTimeMillis = ticksToMillis(12'000),
118 .cpuCycles = 216100000000,
119 .totalMajorFaults = 600,
120 .totalTasksCount = 2,
121 .ioBlockedTasksCount = 2,
122 .processStatsByPid = {
123 {1000,
124 {"system_server",
125 ticksToMillis(13'400),
126 ticksToMillis(12'000),
127 216100000000,
128 600,
129 2,
130 2,
131 {{1000, 198100000000}, {1100, 18000000000}}}}}}}};
132
133 TemporaryDir firstSnapshot;
134 ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
135 perProcessStatus, perThreadStat, perThreadTimeInState));
136
137 UidProcStatsCollector collector(firstSnapshot.path);
138 collector.init();
139
140 ASSERT_TRUE(collector.enabled())
141 << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
142 ASSERT_RESULT_OK(collector.collect());
143
144 auto actual = collector.deltaStats();
145
146 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
147 << "First snapshot doesn't match.\nExpected:\n"
148 << toString(expected) << "\nActual:\n"
149 << toString(actual);
150
151 pidToTids = {
152 {1, {1, 453}}, {1000, {1000, 1400}}, // TID 1100 terminated and 1400 instantiated.
153 };
154
155 perProcessStat = {
156 {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 10 10 0 0 0 0 2 0 19\n"},
157 {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 1550 0 10000 8000 0 0 0 0 2 0 13400\n"},
158 };
159
160 perThreadStat = {
161 {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 5 5 0 0 0 0 2 0 19\n"},
162 {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 5 5 0 0 0 0 2 0 275\n"},
163 {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 5000 2000 0 0 0 0 2 0 13400\n"},
164 // TID 1100 hits +400 major page faults before terminating. This is counted against
165 // PID 1000's perProcessStat.
166 {1400, "1400 (system_server) S 1 0 0 0 0 0 0 0 200 0 5000 2000 0 0 0 0 2 0 8977476\n"},
167 };
168
169 perThreadTimeInState = {
170 {1, "cpu0\n300000 5\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 10"},
171 {453, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 5\ncpu7\n2000000 0"},
172 {1000,
173 "cpu0\n300000 0\n1700000 1000\ncpu4\n710000 1000\n1800000 3000\ncpu7\n2000000 6000"},
174 {1400, "cpu0\n300000 6000\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
175 };
176
177 expected = {{0,
178 {.cpuTimeMillis = ticksToMillis(10),
179 .cpuCycles = 200'000'000,
180 .totalMajorFaults = 700,
181 .totalTasksCount = 2,
182 .ioBlockedTasksCount = 0,
183 .processStatsByPid = {{1,
184 {"init",
185 ticksToMillis(19),
186 ticksToMillis(10),
187 200'000'000,
188 700,
189 2,
190 0,
191 {{1, 200'000'000}, {453, 0}}}}}}},
192 {10001234,
193 {.cpuTimeMillis = ticksToMillis(6'000),
194 .cpuCycles = 18'000'000'000,
195 .totalMajorFaults = 950,
196 .totalTasksCount = 2,
197 .ioBlockedTasksCount = 0,
198 .processStatsByPid = {{1000,
199 {"system_server",
200 ticksToMillis(13'400),
201 ticksToMillis(6'000),
202 18'000'000'000,
203 950,
204 2,
205 0,
206 {{1000, 0}, {1400, 18'000'000'000}}}}}}}};
207
208 TemporaryDir secondSnapshot;
209 ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
210 perProcessStatus, perThreadStat, perThreadTimeInState));
211
212 collector.mPath = secondSnapshot.path;
213
214 ASSERT_TRUE(collector.enabled())
215 << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
216 ASSERT_RESULT_OK(collector.collect());
217
218 actual = collector.deltaStats();
219 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
220 << "Second snapshot doesn't match.\nExpected:\n"
221 << toString(expected) << "\nActual:\n"
222 << toString(actual);
223 }
224
TEST(UidProcStatsCollectorTest,TestHandlesProcessTerminationBetweenScanningAndParsing)225 TEST(UidProcStatsCollectorTest, TestHandlesProcessTerminationBetweenScanningAndParsing) {
226 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
227 {1, {1}},
228 {100, {100}}, // Process terminates after scanning PID directory.
229 {1000, {1000}}, // Process terminates after reading stat file.
230 {2000, {2000}}, // Process terminates after scanning task directory.
231 {3000, {3000, 3300}}, // TID 3300 terminates after scanning task directory.
232 };
233
234 std::unordered_map<pid_t, std::string> perProcessStat = {
235 {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 10 10 0 0 0 0 1 0 19\n"},
236 // Process 100 terminated.
237 {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 20 20 0 0 0 0 1 0 1000\n"},
238 {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 30 30 0 0 0 0 1 0 4567\n"},
239 {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 10300 0 40 40 0 0 0 0 2 0 67890\n"},
240 };
241
242 std::unordered_map<pid_t, std::string> perProcessStatus = {
243 {1, pidStatusStr(1, 0)},
244 // Process 1000 terminated.
245 {2000, pidStatusStr(2000, 10001234)},
246 {3000, pidStatusStr(3000, 10001234)},
247 };
248
249 std::unordered_map<pid_t, std::string> perThreadStat = {
250 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
251 // Process 2000 terminated.
252 {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 2400 0 30 30 0 0 0 0 2 0 67890\n"},
253 // TID 3300 terminated.
254 };
255
256 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
257 {1, "cpu0\n300000 10\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
258 // Process 3000 terminated.
259 };
260
261 std::unordered_map<uid_t, UidProcStats> expected =
262 {{0,
263 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
264 .cpuCycles = 200'000'000,
265 .totalMajorFaults = 220,
266 .totalTasksCount = 1,
267 .ioBlockedTasksCount = 0,
268 .processStatsByPid = {{1,
269 {"init",
270 ticksToMillis(19),
271 ticksToMillis(20),
272 200'000'000,
273 220,
274 1,
275 0,
276 {{1, 200'000'000}}}}}}},
277 {10001234,
278 UidProcStats{.cpuTimeMillis = ticksToMillis(140),
279 .cpuCycles = 0,
280 .totalMajorFaults = 11500,
281 .totalTasksCount = 2,
282 .ioBlockedTasksCount = 0,
283 .processStatsByPid = {{2000,
284 {"logd",
285 ticksToMillis(4567),
286 ticksToMillis(60),
287 0,
288 1200,
289 1,
290 0,
291 {}}},
292 {3000,
293 {"disk I/O",
294 ticksToMillis(67890),
295 ticksToMillis(80),
296 0,
297 10'300,
298 1,
299 0,
300 {}}}}}}};
301
302 TemporaryDir procDir;
303 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
304 perThreadStat, perThreadTimeInState));
305
306 UidProcStatsCollector collector(procDir.path);
307 collector.init();
308
309 ASSERT_TRUE(collector.enabled())
310 << "Files under the path `" << procDir.path << "` are inaccessible";
311 ASSERT_RESULT_OK(collector.collect());
312
313 auto actual = collector.deltaStats();
314 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
315 << "Proc pid contents doesn't match.\nExpected:\n"
316 << toString(expected) << "\nActual:\n"
317 << toString(actual);
318 }
319
TEST(UidProcStatsCollectorTest,TestHandlesPidTidReuse)320 TEST(UidProcStatsCollectorTest, TestHandlesPidTidReuse) {
321 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
322 {1, {1, 367, 453, 589}},
323 {1000, {1000}},
324 {2345, {2345}},
325 };
326
327 std::unordered_map<pid_t, std::string> perProcessStat = {
328 {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 40 40 0 0 0 0 4 0 19\n"},
329 {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 10 10 0 0 0 0 1 0 1000\n"},
330 {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 10 10 0 0 0 0 1 0 456\n"},
331 };
332
333 std::unordered_map<pid_t, std::string> perProcessStatus = {
334 {1, pidStatusStr(1, 0)},
335 {1000, pidStatusStr(1000, 10001234)},
336 {2345, pidStatusStr(2345, 10001234)},
337 };
338
339 std::unordered_map<pid_t, std::string> perThreadStat = {
340 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 4 0 19\n"},
341 {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 10 10 0 0 0 0 4 0 100\n"},
342 {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 10 10 0 0 0 0 4 0 275\n"},
343 {589, "589 (init) D 0 0 0 0 0 0 0 0 500 0 10 10 0 0 0 0 4 0 600\n"},
344 {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 10 10 0 0 0 0 1 0 1000\n"},
345 {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 10 10 0 0 0 0 1 0 456\n"},
346 };
347
348 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
349 {1, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
350 {367, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
351 {453, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 20\ncpu7\n2000000 0"},
352 {589, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 20"},
353 {1000, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
354 {2345, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 20\ncpu7\n2000000 0"},
355 };
356
357 std::unordered_map<uid_t, UidProcStats> expected =
358 {{0,
359 UidProcStats{.cpuTimeMillis = ticksToMillis(80),
360 .cpuCycles = 1'160'000'000,
361 .totalMajorFaults = 1200,
362 .totalTasksCount = 4,
363 .ioBlockedTasksCount = 1,
364 .processStatsByPid = {{1,
365 {"init",
366 ticksToMillis(19),
367 ticksToMillis(80),
368 1'160'000'000,
369 1200,
370 4,
371 1,
372 {{1, 60'000'000},
373 {367, 340'000'000},
374 {453, 360'000'000},
375 {589, 400'000'000}}}}}}},
376 {10001234,
377 UidProcStats{.cpuTimeMillis = ticksToMillis(40),
378 .cpuCycles = 420'000'000,
379 .totalMajorFaults = 54'604,
380 .totalTasksCount = 2,
381 .ioBlockedTasksCount = 0,
382 .processStatsByPid = {{1000,
383 {"system_server",
384 ticksToMillis(1000),
385 ticksToMillis(20),
386 60'000'000,
387 250,
388 1,
389 0,
390 {{1000, 60'000'000}}}},
391 {2345,
392 {"logd",
393 ticksToMillis(456),
394 ticksToMillis(20),
395 360'000'000,
396 54'354,
397 1,
398 0,
399 {{2345, 360'000'000}}}}}}}};
400
401 TemporaryDir firstSnapshot;
402 ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
403 perProcessStatus, perThreadStat, perThreadTimeInState));
404
405 UidProcStatsCollector collector(firstSnapshot.path);
406 collector.init();
407
408 ASSERT_TRUE(collector.enabled())
409 << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
410 ASSERT_RESULT_OK(collector.collect());
411
412 auto actual = collector.deltaStats();
413
414 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
415 << "First snapshot doesn't match.\nExpected:\n"
416 << toString(expected) << "\nActual:\n"
417 << toString(actual);
418
419 pidToTids = {
420 {1, {1, 589}}, // TID 589 reused by the same process.
421 {367, {367, 2000}}, // TID 367 reused as a PID. PID 2000 reused as a TID.
422 // PID 1000 reused as a new PID. TID 453 reused by a different PID.
423 {1000, {1000, 453}},
424 };
425
426 perProcessStat = {
427 {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 60 60 0 0 0 0 2 0 19\n"},
428 {367, "367 (system_server) R 1 0 0 0 0 0 0 0 100 0 30 30 0 0 0 0 2 0 3450\n"},
429 {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 20 20 0 0 0 0 2 0 4650\n"},
430 };
431
432 perProcessStatus = {
433 {1, pidStatusStr(1, 0)},
434 {367, pidStatusStr(367, 10001234)},
435 {1000, pidStatusStr(1000, 10001234)},
436 };
437
438 perThreadStat = {
439 {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 20 20 0 0 0 0 2 0 19\n"},
440 {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 10 10 0 0 0 0 2 0 2345\n"},
441 {367, "367 (system_server) R 1 0 0 0 0 0 0 0 50 0 15 15 0 0 0 0 2 0 3450\n"},
442 {2000, "2000 (system_server) R 1 0 0 0 0 0 0 0 50 0 15 15 0 0 0 0 2 0 3670\n"},
443 {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 2 0 4650\n"},
444 {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 10 10 0 0 0 0 2 0 4770\n"},
445 };
446
447 perThreadTimeInState = {
448 {1, "cpu0\n300000 20\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
449 {589, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 20"},
450 {367, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 30\n1800000 0\ncpu7\n2000000 0"},
451 {2000, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 30"},
452 {1000, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 20\ncpu7\n2000000 0"},
453 {453, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
454 };
455
456 expected = {{0,
457 UidProcStats{.cpuTimeMillis = ticksToMillis(40),
458 .cpuCycles = 400'000'000,
459 .totalMajorFaults = 600,
460 .totalTasksCount = 2,
461 .ioBlockedTasksCount = 0,
462 .processStatsByPid = {{1,
463 {"init",
464 ticksToMillis(19),
465 ticksToMillis(40),
466 400'000'000,
467 600,
468 2,
469 0,
470 {{1, 340'000'000}, {589, 60'000'000}}}}}}},
471 {10001234,
472 UidProcStats{.cpuTimeMillis = ticksToMillis(100),
473 .cpuCycles = 1'233'000'000,
474 .totalMajorFaults = 2100,
475 .totalTasksCount = 4,
476 .ioBlockedTasksCount = 1,
477 .processStatsByPid = {{367,
478 {"system_server",
479 ticksToMillis(3450),
480 ticksToMillis(60),
481 813'000'000,
482 100,
483 2,
484 0,
485 {{367, 213'000'000}, {2000, 600'000'000}}}},
486 {1000,
487 {"logd",
488 ticksToMillis(4650),
489 ticksToMillis(40),
490 420'000'000,
491 2000,
492 2,
493 1,
494 {{1000, 360'000'000},
495 {453, 60'000'000}}}}}}}};
496
497 TemporaryDir secondSnapshot;
498 ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
499 perProcessStatus, perThreadStat, perThreadTimeInState));
500
501 collector.mPath = secondSnapshot.path;
502
503 ASSERT_TRUE(collector.enabled())
504 << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
505 ASSERT_RESULT_OK(collector.collect());
506
507 actual = collector.deltaStats();
508
509 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
510 << "Second snapshot doesn't match.\nExpected:\n"
511 << toString(expected) << "\nActual:\n"
512 << toString(actual);
513 }
514
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedProcessStatFile)515 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatFile) {
516 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
517 {1, {1}},
518 };
519
520 std::unordered_map<pid_t, std::string> perProcessStat = {
521 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
522 };
523
524 std::unordered_map<pid_t, std::string> perProcessStatus = {
525 {1, pidStatusStr(1, 0)},
526 };
527
528 std::unordered_map<pid_t, std::string> perThreadStat = {
529 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
530 };
531
532 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
533 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
534 };
535
536 TemporaryDir procDir;
537 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
538 perThreadStat, perThreadTimeInState));
539
540 UidProcStatsCollector collector(procDir.path);
541 collector.init();
542
543 ASSERT_TRUE(collector.enabled())
544 << "Files under the path `" << procDir.path << "` are inaccessible";
545 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process stat file";
546 }
547
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedProcessStatusFile)548 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatusFile) {
549 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
550 {1, {1}},
551 };
552
553 std::unordered_map<pid_t, std::string> perProcessStat = {
554 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
555 };
556
557 std::unordered_map<pid_t, std::string> perProcessStatus = {
558 {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
559 };
560
561 std::unordered_map<pid_t, std::string> perThreadStat = {
562 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
563 };
564
565 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
566 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
567 };
568
569 TemporaryDir procDir;
570 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
571 perThreadStat, perThreadTimeInState));
572
573 UidProcStatsCollector collector(procDir.path);
574 collector.init();
575
576 ASSERT_TRUE(collector.enabled())
577 << "Files under the path `" << procDir.path << "` are inaccessible";
578 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process status file";
579 }
580
TEST(UidProcStatsCollectorTest,TestErrorOnProcessStatusFileWithNoUid)581 TEST(UidProcStatsCollectorTest, TestErrorOnProcessStatusFileWithNoUid) {
582 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
583 {1, {1}},
584 };
585
586 std::unordered_map<pid_t, std::string> perProcessStat = {
587 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
588 };
589
590 std::unordered_map<pid_t, std::string> perProcessStatus = {
591 {1, "Pid:\t1\nTgid:\t1\n"},
592 };
593
594 std::unordered_map<pid_t, std::string> perThreadStat = {
595 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
596 };
597
598 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
599 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
600 };
601
602 TemporaryDir procDir;
603 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
604 perThreadStat, perThreadTimeInState));
605
606 UidProcStatsCollector collector(procDir.path);
607 collector.init();
608
609 ASSERT_TRUE(collector.enabled())
610 << "Files under the path `" << procDir.path << "` are inaccessible";
611 ASSERT_FALSE(collector.collect().ok())
612 << "No error returned for process status file without uid";
613 }
614
TEST(UidProcStatsCollectorTest,TestErrorOnProcessStatusFileWithNoTgid)615 TEST(UidProcStatsCollectorTest, TestErrorOnProcessStatusFileWithNoTgid) {
616 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
617 {1, {1}},
618 };
619
620 std::unordered_map<pid_t, std::string> perProcessStat = {
621 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
622 };
623
624 std::unordered_map<pid_t, std::string> perProcessStatus = {
625 {1, "Pid:\t1\nUid:\t1\n"},
626 };
627
628 std::unordered_map<pid_t, std::string> perThreadStat = {
629 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
630 };
631
632 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
633 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
634 };
635
636 TemporaryDir procDir;
637 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
638 perThreadStat, perThreadTimeInState));
639
640 UidProcStatsCollector collector(procDir.path);
641 collector.init();
642
643 ASSERT_TRUE(collector.enabled())
644 << "Files under the path `" << procDir.path << "` are inaccessible";
645 ASSERT_FALSE(collector.collect().ok())
646 << "No error returned for process status file without tgid";
647 }
648
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedThreadStatFile)649 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadStatFile) {
650 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
651 {1, {1, 234}},
652 };
653
654 std::unordered_map<pid_t, std::string> perProcessStat = {
655 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
656 };
657
658 std::unordered_map<pid_t, std::string> perProcessStatus = {
659 {1, pidStatusStr(1, 0)},
660 };
661
662 std::unordered_map<pid_t, std::string> perThreadStat = {
663 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
664 {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
665 };
666
667 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
668 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
669 };
670
671 TemporaryDir procDir;
672 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
673 perThreadStat, perThreadTimeInState));
674
675 UidProcStatsCollector collector(procDir.path);
676 collector.init();
677
678 ASSERT_TRUE(collector.enabled())
679 << "Files under the path `" << procDir.path << "` are inaccessible";
680 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file";
681 }
682
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedThreadTimeInStateFile)683 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadTimeInStateFile) {
684 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
685 {1, {1, 234}},
686 };
687
688 std::unordered_map<pid_t, std::string> perProcessStat = {
689 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
690 };
691
692 std::unordered_map<pid_t, std::string> perProcessStatus = {
693 {1, pidStatusStr(1, 0)},
694 };
695
696 std::unordered_map<pid_t, std::string> perThreadStat = {
697 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
698 {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 500\n"},
699 };
700
701 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
702 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\n"},
703 {234, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 CORRUPTED\n DATA"},
704 };
705
706 TemporaryDir procDir;
707 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
708 perThreadStat, perThreadTimeInState));
709
710 UidProcStatsCollector collector(procDir.path);
711 collector.init();
712
713 ASSERT_TRUE(collector.enabled())
714 << "Files under the path `" << procDir.path << "` are inaccessible";
715 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file";
716 }
717
TEST(UidProcStatsCollectorTest,TestHandlesSpaceInCommName)718 TEST(UidProcStatsCollectorTest, TestHandlesSpaceInCommName) {
719 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
720 {1, {1}},
721 };
722
723 std::unordered_map<pid_t, std::string> perProcessStat = {
724 {1,
725 "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
726 };
727
728 std::unordered_map<pid_t, std::string> perProcessStatus = {
729 {1, pidStatusStr(1, 0)},
730 };
731
732 std::unordered_map<pid_t, std::string> perThreadStat = {
733 {1,
734 "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
735 };
736
737 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
738 {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
739 };
740
741 std::unordered_map<uid_t, UidProcStats> expected = {
742 {0,
743 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
744 .cpuCycles = 340'000'000,
745 .totalMajorFaults = 200,
746 .totalTasksCount = 1,
747 .ioBlockedTasksCount = 0,
748 .processStatsByPid = {{1,
749 {"random process name with space",
750 ticksToMillis(19),
751 ticksToMillis(20),
752 340'000'000,
753 200,
754 1,
755 0,
756 {{1, 340'000'000}}}}}}}};
757
758 TemporaryDir procDir;
759 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
760 perThreadStat, perThreadTimeInState));
761
762 UidProcStatsCollector collector(procDir.path);
763 collector.init();
764
765 ASSERT_TRUE(collector.enabled())
766 << "Files under the path `" << procDir.path << "` are inaccessible";
767 ASSERT_RESULT_OK(collector.collect());
768
769 auto actual = collector.deltaStats();
770
771 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
772 << "Proc pid contents doesn't match.\nExpected:\n"
773 << toString(expected) << "\nActual:\n"
774 << toString(actual);
775 }
776
TEST(UidProcStatsCollectorTest,TestHandlesTimeInStateFileDisabledWithNoFile)777 TEST(UidProcStatsCollectorTest, TestHandlesTimeInStateFileDisabledWithNoFile) {
778 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
779 {1, {1}},
780 };
781
782 std::unordered_map<pid_t, std::string> perProcessStat = {
783 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
784 };
785
786 std::unordered_map<pid_t, std::string> perProcessStatus = {
787 {1, pidStatusStr(1, 0)},
788 };
789
790 std::unordered_map<pid_t, std::string> perThreadStat = {
791 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
792 };
793
794 // No time_in_state file present in procfs
795
796 std::unordered_map<uid_t, UidProcStats> expected = {
797 {0,
798 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
799 .cpuCycles = 0,
800 .totalMajorFaults = 200,
801 .totalTasksCount = 1,
802 .ioBlockedTasksCount = 0,
803 .processStatsByPid = {{1,
804 {"init",
805 ticksToMillis(19),
806 ticksToMillis(20),
807 0,
808 200,
809 1,
810 0,
811 {}}}}}}};
812
813 TemporaryDir procDir;
814 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
815 perThreadStat, {}));
816
817 UidProcStatsCollector collector(procDir.path);
818 collector.init();
819
820 ASSERT_TRUE(collector.enabled())
821 << "Files under the path `" << procDir.path << "` are inaccessible";
822 ASSERT_RESULT_OK(collector.collect());
823
824 auto actual = collector.deltaStats();
825
826 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
827 << "Proc pid contents doesn't match.\nExpected:\n"
828 << toString(expected) << "\nActual:\n"
829 << toString(actual);
830 }
831
TEST(UidProcStatsCollectorTest,TestHandlesTimeInStateFileDisabledWithEmptyFile)832 TEST(UidProcStatsCollectorTest, TestHandlesTimeInStateFileDisabledWithEmptyFile) {
833 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
834 {1, {1}},
835 };
836
837 std::unordered_map<pid_t, std::string> perProcessStat = {
838 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
839 };
840
841 std::unordered_map<pid_t, std::string> perProcessStatus = {
842 {1, pidStatusStr(1, 0)},
843 };
844
845 std::unordered_map<pid_t, std::string> perThreadStat = {
846 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
847 };
848
849 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
850 {1, ""},
851 };
852
853 std::unordered_map<uid_t, UidProcStats> expected = {
854 {0,
855 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
856 .cpuCycles = 0,
857 .totalMajorFaults = 200,
858 .totalTasksCount = 1,
859 .ioBlockedTasksCount = 0,
860 .processStatsByPid = {{1,
861 {"init",
862 ticksToMillis(19),
863 ticksToMillis(20),
864 0,
865 200,
866 1,
867 0,
868 {}}}}}}};
869
870 TemporaryDir procDir;
871 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
872 perThreadStat, {}));
873
874 UidProcStatsCollector collector(procDir.path);
875 collector.init();
876
877 ASSERT_TRUE(collector.enabled())
878 << "Files under the path `" << procDir.path << "` are inaccessible";
879 ASSERT_RESULT_OK(collector.collect());
880
881 auto actual = collector.deltaStats();
882
883 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
884 << "Proc pid contents doesn't match.\nExpected:\n"
885 << toString(expected) << "\nActual:\n"
886 << toString(actual);
887 }
888
TEST(UidProcStatsCollectorTest,TestHandlesTimeInStateFileDisabledWithZeroCpuCycles)889 TEST(UidProcStatsCollectorTest, TestHandlesTimeInStateFileDisabledWithZeroCpuCycles) {
890 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
891 {1, {1}},
892 };
893
894 std::unordered_map<pid_t, std::string> perProcessStat = {
895 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
896 };
897
898 std::unordered_map<pid_t, std::string> perProcessStatus = {
899 {1, pidStatusStr(1, 0)},
900 };
901
902 std::unordered_map<pid_t, std::string> perThreadStat = {
903 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
904 };
905
906 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
907 {1, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
908 };
909
910 std::unordered_map<uid_t, UidProcStats> expected = {
911 {0,
912 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
913 .cpuCycles = 0,
914 .totalMajorFaults = 200,
915 .totalTasksCount = 1,
916 .ioBlockedTasksCount = 0,
917 .processStatsByPid = {{1,
918 {"init",
919 ticksToMillis(19),
920 ticksToMillis(20),
921 0,
922 200,
923 1,
924 0,
925 {}}}}}}};
926
927 TemporaryDir procDir;
928 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
929 perThreadStat, {}));
930
931 UidProcStatsCollector collector(procDir.path);
932 collector.init();
933
934 ASSERT_TRUE(collector.enabled())
935 << "Files under the path `" << procDir.path << "` are inaccessible";
936 ASSERT_RESULT_OK(collector.collect());
937
938 auto actual = collector.deltaStats();
939
940 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
941 << "Proc pid contents doesn't match.\nExpected:\n"
942 << toString(expected) << "\nActual:\n"
943 << toString(actual);
944 }
945
TEST(UidProcStatsCollectorTest,TestHandlesNoTimeInStateFileDuringCollection)946 TEST(UidProcStatsCollectorTest, TestHandlesNoTimeInStateFileDuringCollection) {
947 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
948 {1, {1, 234}},
949 };
950
951 std::unordered_map<pid_t, std::string> perProcessStat = {
952 {1, "1 (init) S 0 0 0 0 0 0 0 0 210 0 15 15 0 0 0 0 2 0 19\n"},
953 };
954
955 std::unordered_map<pid_t, std::string> perProcessStatus = {
956 {1, pidStatusStr(1, 0)},
957 };
958
959 std::unordered_map<pid_t, std::string> perThreadStat = {
960 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 2 0 19\n"},
961 {234, "1 (init) S 0 0 0 0 0 0 0 0 10 0 5 5 0 0 0 0 2 0 19\n"},
962 };
963
964 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
965 {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
966 // No time_in_state file present for TID 234
967 };
968
969 std::unordered_map<uid_t, UidProcStats> expected = {
970 {0,
971 UidProcStats{.cpuTimeMillis = ticksToMillis(30),
972 .cpuCycles = 340'000'000,
973 .totalMajorFaults = 210,
974 .totalTasksCount = 2,
975 .ioBlockedTasksCount = 0,
976 .processStatsByPid = {{1,
977 {"init",
978 ticksToMillis(19),
979 ticksToMillis(30),
980 340'000'000,
981 210,
982 2,
983 0,
984 {{1, 340'000'000}}}}}}}};
985
986 TemporaryDir procDir;
987 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
988 perThreadStat, perThreadTimeInState));
989
990 UidProcStatsCollector collector(procDir.path);
991 collector.init();
992
993 ASSERT_TRUE(collector.enabled())
994 << "Files under the path `" << procDir.path << "` are inaccessible";
995 ASSERT_RESULT_OK(collector.collect());
996
997 auto actual = collector.deltaStats();
998
999 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
1000 << "Proc pid contents doesn't match.\nExpected:\n"
1001 << toString(expected) << "\nActual:\n"
1002 << toString(actual);
1003 }
1004
TEST(UidProcStatsCollectorTest,TestUidProcStatsCollectorContentsFromDevice)1005 TEST(UidProcStatsCollectorTest, TestUidProcStatsCollectorContentsFromDevice) {
1006 UidProcStatsCollector collector;
1007 collector.init();
1008
1009 ASSERT_TRUE(collector.enabled()) << "/proc/[pid]/.* files are inaccessible";
1010 ASSERT_RESULT_OK(collector.collect());
1011
1012 const auto& processStats = collector.deltaStats();
1013
1014 // The below check should pass because there should be at least one process.
1015 EXPECT_GT(processStats.size(), static_cast<size_t>(0));
1016 }
1017
1018 } // namespace watchdog
1019 } // namespace automotive
1020 } // namespace android
1021