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 <android_car_feature.h>
26 #include <inttypes.h>
27
28 #include <algorithm>
29 #include <string>
30
31 namespace android {
32 namespace automotive {
33 namespace watchdog {
34
35 using ::android::automotive::watchdog::testing::populateProcPidDir;
36 using ::android::base::StringAppendF;
37 using ::android::base::StringPrintf;
38 using ::android::car::feature::car_watchdog_memory_profiling;
39 using ::testing::UnorderedPointwise;
40
41 namespace {
42
43 constexpr uint64_t kTestPrivateDirtyKb = 100;
44
45 MATCHER(UidProcStatsByUidEq, "") {
46 const auto& actual = std::get<0>(arg);
47 const auto& expected = std::get<1>(arg);
48 return actual.first == expected.first &&
49 ExplainMatchResult(UidProcStatsEq(expected.second), actual.second, result_listener);
50 }
51
pidStatusStr(pid_t pid,uid_t uid)52 std::string pidStatusStr(pid_t pid, uid_t uid) {
53 return StringPrintf("Pid:\t%" PRIu32 "\nTgid:\t%" PRIu32 "\nUid:\t%" PRIu32 "\n", pid, pid,
54 uid);
55 }
56
smapsRollupStr(uint64_t rssKb,uint64_t pssKb,uint64_t ussKb,uint64_t swapPssKb)57 std::string smapsRollupStr(uint64_t rssKb, uint64_t pssKb, uint64_t ussKb, uint64_t swapPssKb) {
58 std::string buffer;
59 StringAppendF(&buffer,
60 "5592470000-7ffc9a9000 ---p 00000000 00:00 0 "
61 "[rollup]\n");
62 // clang-format off
63 StringAppendF(&buffer, "Rss: %" PRIu64 " kB\n", rssKb);
64 StringAppendF(&buffer, "Pss: %" PRIu64 " kB\n", pssKb);
65 StringAppendF(&buffer, "Pss_Anon: 1628 kB\n"
66 "Pss_File: 360 kB\n"
67 "Pss_Shmem: 303 kB\n"
68 "Shared_Clean: 2344 kB\n"
69 "Shared_Dirty: 688 kB\n");
70 /**
71 * Private_Dirty is 100 KB and ussKB = Private_Dirty + Private_Clean. So, ensure that ussKb
72 * is at least 100 KB before proceeding.
73 */
74 EXPECT_GT(ussKb, kTestPrivateDirtyKb);
75 StringAppendF(&buffer, "Private_Clean: %" PRIu64 " kB\n", ussKb - kTestPrivateDirtyKb);
76 StringAppendF(&buffer, "Private_Dirty: %" PRIu64 " kB\n", kTestPrivateDirtyKb);
77 StringAppendF(&buffer, "Referenced: 4908 kB\n"
78 "Anonymous: 1628 kB\n"
79 "LazyFree: 0 kB\n"
80 "AnonHugePages: 0 kB\n"
81 "ShmemPmdMapped: 0 kB\n"
82 "FilePmdMapped: 0 kB\n"
83 "Shared_Hugetlb: 0 kB\n"
84 "Private_Hugetlb: 0 kB\n"
85 "Swap: 5860 kB\n");
86 StringAppendF(&buffer, "SwapPss: %" PRIu64 " kB\n", swapPssKb);
87 StringAppendF(&buffer, "Locked: 0 kB");
88 // clang-format on
89 return buffer;
90 }
91
toString(const std::unordered_map<uid_t,UidProcStats> & uidProcStatsByUid)92 std::string toString(const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) {
93 std::string buffer;
94 StringAppendF(&buffer, "Number of UIDs: %" PRIi32 "\n",
95 static_cast<int>(uidProcStatsByUid.size()));
96 for (const auto& [uid, stats] : uidProcStatsByUid) {
97 StringAppendF(&buffer, "{UID: %d, %s}", uid, stats.toString().c_str());
98 }
99 return buffer;
100 }
101
ticksToMillis(int32_t clockTicks)102 int64_t ticksToMillis(int32_t clockTicks) {
103 return (clockTicks * 1000) / sysconf(_SC_CLK_TCK);
104 }
105
applyFeatureFilter(std::unordered_map<uid_t,UidProcStats> * uidProcStatsByUid)106 void applyFeatureFilter(std::unordered_map<uid_t, UidProcStats>* uidProcStatsByUid) {
107 if (car_watchdog_memory_profiling()) {
108 return;
109 }
110 for (auto& [uid, uidProcStats] : *uidProcStatsByUid) {
111 uidProcStats.totalRssKb = 0;
112 uidProcStats.totalPssKb = 0;
113 for (auto& [pid, processStats] : uidProcStats.processStatsByPid) {
114 processStats.rssKb = 0;
115 processStats.pssKb = 0;
116 processStats.ussKb = 0;
117 processStats.swapPssKb = 0;
118 }
119 }
120 }
121
isSmapsRollupSupported(std::string rootPath)122 bool isSmapsRollupSupported(std::string rootPath) {
123 std::string path = StringPrintf((rootPath + kSmapsRollupFileFormat).c_str(), 1);
124 return access(path.c_str(), R_OK) == 0;
125 }
126
127 } // namespace
128
TEST(UidProcStatsCollectorTest,TestValidStatFiles)129 TEST(UidProcStatsCollectorTest, TestValidStatFiles) {
130 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
131 {1, {1, 453}},
132 {1000, {1000, 1100}},
133 };
134
135 std::unordered_map<pid_t, std::string> perProcessStat = {
136 {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 6 4 0 0 0 0 2 0 19\n"},
137 {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"},
138 };
139
140 std::unordered_map<pid_t, std::string> perProcessStatus = {
141 {1, pidStatusStr(1, 0)},
142 {1000, pidStatusStr(1000, 10001234)},
143 };
144
145 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
146 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
147 {1000,
148 smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
149 };
150
151 std::unordered_map<pid_t, std::string> perThreadStat = {
152 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 3 2 0 0 0 0 2 0 19\n"},
153 {453, "453 (init) D 0 0 0 0 0 0 0 0 20 0 3 2 0 0 0 0 2 0 275\n"},
154 {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"},
155 {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"},
156 };
157
158 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
159 {1, "cpu0\n300000 5\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
160 {453, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 5\ncpu7\n2000000 0"},
161 {1000,
162 "cpu0\n300000 0\n1700000 1000\ncpu4\n710000 1000\n1800000 3000\ncpu7\n2000000 6000"},
163 {1100, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 1000\ncpu7\n2000000 0"},
164 };
165
166 std::unordered_map<uid_t, UidProcStats> expected =
167 {{0,
168 UidProcStats{.cpuTimeMillis = ticksToMillis(10),
169 .cpuCycles = 105'000'000,
170 .totalMajorFaults = 220,
171 .totalTasksCount = 2,
172 .ioBlockedTasksCount = 1,
173 .totalRssKb = 1000,
174 .totalPssKb = 865,
175 .processStatsByPid =
176 {{1,
177 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
178 /*cpuTimeMillis=*/ticksToMillis(10),
179 /*totalCpuCycles=*/105'000'000, /*totalMajorFaults=*/220,
180 /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/1,
181 /*cpuCyclesByTid=*/{{1, 15'000'000}, {453, 90'000'000}},
182 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
183 /*swapPssKb=*/200}}}}},
184 {10001234,
185 UidProcStats{.cpuTimeMillis = ticksToMillis(12'000),
186 .cpuCycles = 216100000000,
187 .totalMajorFaults = 600,
188 .totalTasksCount = 2,
189 .ioBlockedTasksCount = 2,
190 .totalRssKb = 2000,
191 .totalPssKb = 1635,
192 .processStatsByPid = {{1000,
193 {/*comm=*/"system_server",
194 /*startTimeMillis=*/ticksToMillis(13'400),
195 /*cpuTimeMillis=*/ticksToMillis(12'000),
196 /*totalCpuCycles=*/216100000000,
197 /*totalMajorFaults=*/600,
198 /*totalTasksCount=*/2,
199 /*ioBlockedTasksCount=*/2, /*cpuCyclesByTid=*/
200 {{1000, 198100000000}, {1100, 18000000000}},
201 /*rssKb=*/2000,
202 /*pssKb=*/1635,
203 /*ussKb=*/1286,
204 /*swapPssKb=*/600}}}}}};
205 applyFeatureFilter(&expected);
206
207 TemporaryDir firstSnapshot;
208 ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
209 perProcessStatus, perProcessSmapsRollup,
210 /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
211
212 UidProcStatsCollector collector(firstSnapshot.path, isSmapsRollupSupported(firstSnapshot.path));
213 collector.init();
214
215 ASSERT_TRUE(collector.enabled())
216 << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
217 ASSERT_RESULT_OK(collector.collect());
218
219 auto actual = collector.deltaStats();
220
221 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
222 << "First snapshot doesn't match.\nExpected:\n"
223 << toString(expected) << "\nActual:\n"
224 << toString(actual);
225
226 pidToTids = {
227 {1, {1, 453}}, {1000, {1000, 1400}}, // TID 1100 terminated and 1400 instantiated.
228 };
229
230 perProcessStat = {
231 {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 10 10 0 0 0 0 2 0 19\n"},
232 {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"},
233 };
234
235 perProcessSmapsRollup = {
236 {1, smapsRollupStr(/*rssKb=*/3000, /*pssKb=*/1865, /*ussKb=*/1656, /*swapPssKb=*/900)},
237 {1000,
238 smapsRollupStr(/*rssKb=*/2010, /*pssKb=*/1645, /*ussKb=*/1296, /*swapPssKb=*/610)},
239 };
240
241 perThreadStat = {
242 {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 5 5 0 0 0 0 2 0 19\n"},
243 {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 5 5 0 0 0 0 2 0 275\n"},
244 {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"},
245 // TID 1100 hits +400 major page faults before terminating. This is counted against
246 // PID 1000's perProcessStat.
247 {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"},
248 };
249
250 perThreadTimeInState = {
251 {1, "cpu0\n300000 5\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 10"},
252 {453, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 5\ncpu7\n2000000 0"},
253 {1000,
254 "cpu0\n300000 0\n1700000 1000\ncpu4\n710000 1000\n1800000 3000\ncpu7\n2000000 6000"},
255 {1400, "cpu0\n300000 6000\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
256 };
257
258 expected = {{0,
259 {.cpuTimeMillis = ticksToMillis(10),
260 .cpuCycles = 200'000'000,
261 .totalMajorFaults = 700,
262 .totalTasksCount = 2,
263 .ioBlockedTasksCount = 0,
264 .totalRssKb = 3000,
265 .totalPssKb = 1865,
266 .processStatsByPid = {{1,
267 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
268 /*cpuTimeMillis=*/ticksToMillis(10),
269 /*totalCpuCycles=*/200'000'000, /*totalMajorFaults=*/700,
270 /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
271 /*cpuCyclesByTid=*/{{1, 200'000'000}, {453, 0}},
272 /*rssKb=*/3000,
273 /*pssKb=*/1865,
274 /*ussKb=*/1656,
275 /*swapPssKb=*/900}}}}},
276 {10001234,
277 {.cpuTimeMillis = ticksToMillis(6'000),
278 .cpuCycles = 18'000'000'000,
279 .totalMajorFaults = 950,
280 .totalTasksCount = 2,
281 .ioBlockedTasksCount = 0,
282 .totalRssKb = 2010,
283 .totalPssKb = 1645,
284 .processStatsByPid = {
285 {1000,
286 {/*comm=*/"system_server", /*startTimeMillis=*/ticksToMillis(13'400),
287 /*cpuTimeMillis=*/ticksToMillis(6'000),
288 /*totalCpuCycles=*/18'000'000'000, /*totalMajorFaults=*/950,
289 /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
290 /*cpuCyclesByTid=*/{{1000, 0}, {1400, 18'000'000'000}},
291 /*rssKb=*/2010,
292 /*pssKb=*/1645,
293 /*ussKb=*/1296,
294 /*swapPssKb=*/610}}}}}};
295 applyFeatureFilter(&expected);
296
297 TemporaryDir secondSnapshot;
298 ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
299 perProcessStatus, perProcessSmapsRollup,
300 /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
301
302 collector.mPath = secondSnapshot.path;
303
304 ASSERT_TRUE(collector.enabled())
305 << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
306 ASSERT_RESULT_OK(collector.collect());
307
308 actual = collector.deltaStats();
309 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
310 << "Second snapshot doesn't match.\nExpected:\n"
311 << toString(expected) << "\nActual:\n"
312 << toString(actual);
313 }
314
TEST(UidProcStatsCollectorTest,TestHandlesProcessTerminationBetweenScanningAndParsing)315 TEST(UidProcStatsCollectorTest, TestHandlesProcessTerminationBetweenScanningAndParsing) {
316 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
317 {1, {1}},
318 {100, {100}}, // Process terminates after scanning PID directory.
319 {1000, {1000}}, // Process terminates after reading stat file.
320 {2000, {2000}}, // Process terminates after scanning task directory.
321 {3000, {3000, 3300}}, // TID 3300 terminates after scanning task directory.
322 };
323
324 std::unordered_map<pid_t, std::string> perProcessStat = {
325 {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 10 10 0 0 0 0 1 0 19\n"},
326 // Process 100 terminated.
327 {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"},
328 {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 30 30 0 0 0 0 1 0 4567\n"},
329 {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"},
330 };
331
332 std::unordered_map<pid_t, std::string> perProcessStatus = {
333 {1, pidStatusStr(1, 0)},
334 // Process 1000 terminated.
335 {2000, pidStatusStr(2000, 10001234)},
336 {3000, pidStatusStr(3000, 10001234)},
337 };
338
339 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
340 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
341 {2000,
342 smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
343 {3000,
344 smapsRollupStr(/*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944, /*swapPssKb=*/500)},
345 };
346
347 std::unordered_map<pid_t, std::string> perThreadStat = {
348 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
349 // Process 2000 terminated.
350 {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"},
351 // TID 3300 terminated.
352 };
353
354 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
355 {1, "cpu0\n300000 10\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
356 // Process 3000 terminated.
357 };
358
359 std::unordered_map<uid_t, UidProcStats> expected =
360 {{0,
361 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
362 .cpuCycles = 200'000'000,
363 .totalMajorFaults = 220,
364 .totalTasksCount = 1,
365 .ioBlockedTasksCount = 0,
366 .totalRssKb = 1000,
367 .totalPssKb = 865,
368 .processStatsByPid =
369 {{1,
370 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
371 /*cpuTimeMillis=*/ticksToMillis(20),
372 /*totalCpuCycles=*/200'000'000, /*totalMajorFaults=*/220,
373 /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
374 /*cpuCyclesByTid=*/{{1, 200'000'000}},
375 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
376 /*swapPssKb=*/200}}}}},
377 {10001234,
378 UidProcStats{.cpuTimeMillis = ticksToMillis(140),
379 .cpuCycles = 0,
380 .totalMajorFaults = 11500,
381 .totalTasksCount = 2,
382 .ioBlockedTasksCount = 0,
383 .totalRssKb = 7642,
384 .totalPssKb = 3947,
385 .processStatsByPid =
386 {{2000,
387 {/*comm=*/"logd", /*startTimeMillis=*/ticksToMillis(4567),
388 /*cpuTimeMillis=*/ticksToMillis(60), /*totalCpuCycles=*/0,
389 /*totalMajorFaults=*/1200, /*totalTasksCount=*/1,
390 /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
391 /*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286,
392 /*swapPssKb=*/600}},
393 {3000,
394 {/*comm=*/"disk I/O",
395 /*startTimeMillis=*/ticksToMillis(67890),
396 /*cpuTimeMillis=*/ticksToMillis(80), /*totalCpuCycles=*/0,
397 /*totalMajorFaults=*/10'300, /*totalTasksCount=*/1,
398 /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
399 /*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944,
400 /*swapPssKb=*/500}}}}}};
401 applyFeatureFilter(&expected);
402
403 TemporaryDir procDir;
404 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
405 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
406 perThreadTimeInState));
407
408 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
409 collector.init();
410
411 ASSERT_TRUE(collector.enabled())
412 << "Files under the path `" << procDir.path << "` are inaccessible";
413 ASSERT_RESULT_OK(collector.collect());
414
415 auto actual = collector.deltaStats();
416 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
417 << "Proc pid contents doesn't match.\nExpected:\n"
418 << toString(expected) << "\nActual:\n"
419 << toString(actual);
420 }
421
TEST(UidProcStatsCollectorTest,TestHandlesPidTidReuse)422 TEST(UidProcStatsCollectorTest, TestHandlesPidTidReuse) {
423 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
424 {1, {1, 367, 453, 589}},
425 {1000, {1000}},
426 {2345, {2345}},
427 };
428
429 std::unordered_map<pid_t, std::string> perProcessStat = {
430 {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 40 40 0 0 0 0 4 0 19\n"},
431 {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"},
432 {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 10 10 0 0 0 0 1 0 456\n"},
433 };
434
435 std::unordered_map<pid_t, std::string> perProcessStatus = {
436 {1, pidStatusStr(1, 0)},
437 {1000, pidStatusStr(1000, 10001234)},
438 {2345, pidStatusStr(2345, 10001234)},
439 };
440
441 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
442 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
443 {1000,
444 smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
445 {2345,
446 smapsRollupStr(/*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944, /*swapPssKb=*/500)},
447 };
448
449 std::unordered_map<pid_t, std::string> perThreadStat = {
450 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 4 0 19\n"},
451 {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 10 10 0 0 0 0 4 0 100\n"},
452 {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 10 10 0 0 0 0 4 0 275\n"},
453 {589, "589 (init) D 0 0 0 0 0 0 0 0 500 0 10 10 0 0 0 0 4 0 600\n"},
454 {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"},
455 {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 10 10 0 0 0 0 1 0 456\n"},
456 };
457
458 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
459 {1, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
460 {367, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
461 {453, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 20\ncpu7\n2000000 0"},
462 {589, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 20"},
463 {1000, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
464 {2345, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 20\ncpu7\n2000000 0"},
465 };
466
467 std::unordered_map<uid_t, UidProcStats> expected =
468 {{0,
469 UidProcStats{.cpuTimeMillis = ticksToMillis(80),
470 .cpuCycles = 1'160'000'000,
471 .totalMajorFaults = 1200,
472 .totalTasksCount = 4,
473 .ioBlockedTasksCount = 1,
474 .totalRssKb = 1000,
475 .totalPssKb = 865,
476 .processStatsByPid = {{1,
477 {/*comm=*/"init",
478 /*startTimeMillis=*/ticksToMillis(19),
479 /*cpuTimeMillis=*/ticksToMillis(80),
480 /*totalCpuCycles=*/1'160'000'000,
481 /*totalMajorFaults=*/1200,
482 /*totalTasksCount=*/4,
483 /*ioBlockedTasksCount=*/1, /*cpuCyclesByTid=*/
484 {{1, 60'000'000},
485 {367, 340'000'000},
486 {453, 360'000'000},
487 {589, 400'000'000}},
488 /*rssKb=*/1000,
489 /*pssKb=*/865,
490 /*ussKb=*/656,
491 /*swapPssKb=*/200}}}}},
492 {10001234,
493 UidProcStats{.cpuTimeMillis = ticksToMillis(40),
494 .cpuCycles = 420'000'000,
495 .totalMajorFaults = 54'604,
496 .totalTasksCount = 2,
497 .ioBlockedTasksCount = 0,
498 .totalRssKb = 7642,
499 .totalPssKb = 3947,
500 .processStatsByPid =
501 {{1000,
502 {/*comm=*/"system_server",
503 /*startTimeMillis=*/ticksToMillis(1000),
504 /*cpuTimeMillis=*/ticksToMillis(20),
505 /*totalCpuCycles=*/60'000'000, /*totalMajorFaults=*/250,
506 /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
507 /*cpuCyclesByTid=*/{{1000, 60'000'000}},
508 /*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286,
509 /*swapPssKb=*/600}},
510 {2345,
511 {/*comm=*/"logd", /*startTimeMillis=*/ticksToMillis(456),
512 /*cpuTimeMillis=*/ticksToMillis(20),
513 /*totalCpuCycles=*/360'000'000,
514 /*totalMajorFaults=*/54'354,
515 /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
516 /*cpuCyclesByTid=*/{{2345, 360'000'000}},
517 /*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944,
518 /*swapPssKb=*/500}}}}}};
519 applyFeatureFilter(&expected);
520
521 TemporaryDir firstSnapshot;
522 ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
523 perProcessStatus, perProcessSmapsRollup,
524 /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
525
526 UidProcStatsCollector collector(firstSnapshot.path, isSmapsRollupSupported(firstSnapshot.path));
527 collector.init();
528
529 ASSERT_TRUE(collector.enabled())
530 << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
531 ASSERT_RESULT_OK(collector.collect());
532
533 auto actual = collector.deltaStats();
534
535 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
536 << "First snapshot doesn't match.\nExpected:\n"
537 << toString(expected) << "\nActual:\n"
538 << toString(actual);
539
540 pidToTids = {
541 {1, {1, 589}}, // TID 589 reused by the same process.
542 {367, {367, 2000}}, // TID 367 reused as a PID. PID 2000 reused as a TID.
543 // PID 1000 reused as a new PID. TID 453 reused by a different PID.
544 {1000, {1000, 453}},
545 };
546
547 perProcessStat = {
548 {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 60 60 0 0 0 0 2 0 19\n"},
549 {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"},
550 {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 20 20 0 0 0 0 2 0 4650\n"},
551 };
552
553 perProcessStatus = {
554 {1, pidStatusStr(1, 0)},
555 {367, pidStatusStr(367, 10001234)},
556 {1000, pidStatusStr(1000, 10001234)},
557 };
558
559 perProcessSmapsRollup = {
560 {1, smapsRollupStr(/*rssKb=*/1500, /*pssKb=*/965, /*ussKb=*/756, /*swapPssKb=*/300)},
561 {367,
562 smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
563 {1000,
564 smapsRollupStr(/*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944, /*swapPssKb=*/500)},
565 };
566
567 perThreadStat = {
568 {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 20 20 0 0 0 0 2 0 19\n"},
569 {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 10 10 0 0 0 0 2 0 2345\n"},
570 {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"},
571 {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"},
572 {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 2 0 4650\n"},
573 {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 10 10 0 0 0 0 2 0 4770\n"},
574 };
575
576 perThreadTimeInState = {
577 {1, "cpu0\n300000 20\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
578 {589, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 20"},
579 {367, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 30\n1800000 0\ncpu7\n2000000 0"},
580 {2000, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 30"},
581 {1000, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 20\ncpu7\n2000000 0"},
582 {453, "cpu0\n300000 20\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
583 };
584
585 expected = {{0,
586 UidProcStats{.cpuTimeMillis = ticksToMillis(40),
587 .cpuCycles = 400'000'000,
588 .totalMajorFaults = 600,
589 .totalTasksCount = 2,
590 .ioBlockedTasksCount = 0,
591 .totalRssKb = 1500,
592 .totalPssKb = 965,
593 .processStatsByPid =
594 {{1,
595 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
596 /*cpuTimeMillis=*/ticksToMillis(40),
597 /*totalCpuCycles=*/400'000'000, /*totalMajorFaults=*/600,
598 /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
599 /*cpuCyclesByTid=*/{{1, 340'000'000}, {589, 60'000'000}},
600 /*rssKb=*/1500, /*pssKb=*/965, /*ussKb=*/756,
601 /*swapPssKb=*/300}}}}},
602 {10001234,
603 UidProcStats{.cpuTimeMillis = ticksToMillis(100),
604 .cpuCycles = 1'233'000'000,
605 .totalMajorFaults = 2100,
606 .totalTasksCount = 4,
607 .ioBlockedTasksCount = 1,
608 .totalRssKb = 7642,
609 .totalPssKb = 3947,
610 .processStatsByPid = {{367,
611 {/*comm=*/"system_server",
612 /*startTimeMillis=*/ticksToMillis(3450),
613 /*cpuTimeMillis=*/ticksToMillis(60),
614 /*totalCpuCycles=*/813'000'000,
615 /*totalMajorFaults=*/100,
616 /*totalTasksCount=*/2,
617 /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/
618 {{367, 213'000'000}, {2000, 600'000'000}},
619 /*rssKb=*/2000,
620 /*pssKb=*/1635,
621 /*ussKb=*/1286,
622 /*swapPssKb=*/600}},
623 {1000,
624 {/*comm=*/"logd",
625 /*startTimeMillis=*/ticksToMillis(4650),
626 /*cpuTimeMillis=*/ticksToMillis(40),
627 /*totalCpuCycles=*/420'000'000,
628 /*totalMajorFaults=*/2000,
629 /*totalTasksCount=*/2,
630 /*ioBlockedTasksCount=*/1, /*cpuCyclesByTid=*/
631 {{1000, 360'000'000}, {453, 60'000'000}},
632 /*rssKb=*/5642,
633 /*pssKb=*/2312,
634 /*ussKb=*/944,
635 /*swapPssKb=*/500}}}}}};
636 applyFeatureFilter(&expected);
637
638 TemporaryDir secondSnapshot;
639 ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
640 perProcessStatus, perProcessSmapsRollup,
641 /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
642
643 collector.mPath = secondSnapshot.path;
644
645 ASSERT_TRUE(collector.enabled())
646 << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
647 ASSERT_RESULT_OK(collector.collect());
648
649 actual = collector.deltaStats();
650
651 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
652 << "Second snapshot doesn't match.\nExpected:\n"
653 << toString(expected) << "\nActual:\n"
654 << toString(actual);
655 }
656
TEST(UidProcStatsCollectorTest,TestHandlesNoSmapsRollupKernelSupport)657 TEST(UidProcStatsCollectorTest, TestHandlesNoSmapsRollupKernelSupport) {
658 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
659 {1, {1}},
660 };
661
662 std::unordered_map<pid_t, std::string> perProcessStat = {
663 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
664 };
665
666 std::unordered_map<pid_t, std::string> perProcessStatus = {
667 {1, pidStatusStr(1, 0)},
668 };
669
670 std::unordered_map<pid_t, std::string> perProcessStatm = {
671 {1, "2969783 1481 938 530 0 5067 0"},
672 };
673
674 std::unordered_map<pid_t, std::string> perThreadStat = {
675 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
676 };
677
678 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
679 {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
680 };
681
682 std::unordered_map<uid_t, UidProcStats> expected = {
683 {0,
684 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
685 .cpuCycles = 340'000'000,
686 .totalMajorFaults = 200,
687 .totalTasksCount = 1,
688 .ioBlockedTasksCount = 0,
689 .totalRssKb = 5924,
690 .totalPssKb = 0,
691 .processStatsByPid = {
692 {1,
693 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
694 /*cpuTimeMillis=*/ticksToMillis(20),
695 /*totalCpuCycles=*/340'000'000,
696 /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
697 /*ioBlockedTasksCount=*/0,
698 /*cpuCyclesByTid=*/{{1, 340'000'000}},
699 /*rssKb=*/5924, /*pssKb=*/0, /*ussKb=*/2172,
700 /*swapPssKb=*/0}}}}}};
701 applyFeatureFilter(&expected);
702
703 TemporaryDir procDir;
704 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
705 /*processSmapsRollup=*/{}, perProcessStatm, perThreadStat,
706 perThreadTimeInState));
707
708 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
709 collector.init();
710
711 ASSERT_TRUE(collector.enabled())
712 << "Files under the path `" << procDir.path << "` are inaccessible";
713 ASSERT_RESULT_OK(collector.collect());
714
715 auto actual = collector.deltaStats();
716
717 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
718 << "Proc pid contents doesn't match.\nExpected:\n"
719 << toString(expected) << "\nActual:\n"
720 << toString(actual);
721 }
722
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedProcessStatFile)723 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatFile) {
724 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
725 {1, {1}},
726 };
727
728 std::unordered_map<pid_t, std::string> perProcessStat = {
729 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
730 };
731
732 std::unordered_map<pid_t, std::string> perProcessStatus = {
733 {1, pidStatusStr(1, 0)},
734 };
735
736 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
737 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
738 };
739
740 std::unordered_map<pid_t, std::string> perThreadStat = {
741 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
742 };
743
744 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
745 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
746 };
747
748 TemporaryDir procDir;
749 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
750 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
751 perThreadTimeInState));
752
753 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
754 collector.init();
755
756 ASSERT_TRUE(collector.enabled())
757 << "Files under the path `" << procDir.path << "` are inaccessible";
758 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process stat file";
759 }
760
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedProcessStatusFile)761 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatusFile) {
762 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
763 {1, {1}},
764 };
765
766 std::unordered_map<pid_t, std::string> perProcessStat = {
767 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
768 };
769
770 std::unordered_map<pid_t, std::string> perProcessStatus = {
771 {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
772 };
773
774 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
775 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
776 };
777
778 std::unordered_map<pid_t, std::string> perThreadStat = {
779 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
780 };
781
782 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
783 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
784 };
785
786 TemporaryDir procDir;
787 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
788 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
789 perThreadTimeInState));
790
791 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
792 collector.init();
793
794 ASSERT_TRUE(collector.enabled())
795 << "Files under the path `" << procDir.path << "` are inaccessible";
796 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process status file";
797 }
798
TEST(UidProcStatsCollectorTest,TestErrorOnProcessStatusFileWithNoUid)799 TEST(UidProcStatsCollectorTest, TestErrorOnProcessStatusFileWithNoUid) {
800 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
801 {1, {1}},
802 };
803
804 std::unordered_map<pid_t, std::string> perProcessStat = {
805 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
806 };
807
808 std::unordered_map<pid_t, std::string> perProcessStatus = {
809 {1, "Pid:\t1\nTgid:\t1\n"},
810 };
811
812 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
813 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
814 };
815
816 std::unordered_map<pid_t, std::string> perThreadStat = {
817 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
818 };
819
820 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
821 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
822 };
823
824 TemporaryDir procDir;
825 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
826 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
827 perThreadTimeInState));
828
829 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
830 collector.init();
831
832 ASSERT_TRUE(collector.enabled())
833 << "Files under the path `" << procDir.path << "` are inaccessible";
834 ASSERT_FALSE(collector.collect().ok())
835 << "No error returned for process status file without uid";
836 }
837
TEST(UidProcStatsCollectorTest,TestErrorOnProcessStatusFileWithNoTgid)838 TEST(UidProcStatsCollectorTest, TestErrorOnProcessStatusFileWithNoTgid) {
839 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
840 {1, {1}},
841 };
842
843 std::unordered_map<pid_t, std::string> perProcessStat = {
844 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
845 };
846
847 std::unordered_map<pid_t, std::string> perProcessStatus = {
848 {1, "Pid:\t1\nUid:\t1\n"},
849 };
850
851 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
852 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
853 };
854
855 std::unordered_map<pid_t, std::string> perThreadStat = {
856 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
857 };
858
859 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
860 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
861 };
862
863 TemporaryDir procDir;
864 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
865 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
866 perThreadTimeInState));
867
868 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
869 collector.init();
870
871 ASSERT_TRUE(collector.enabled())
872 << "Files under the path `" << procDir.path << "` are inaccessible";
873 ASSERT_FALSE(collector.collect().ok())
874 << "No error returned for process status file without tgid";
875 }
876
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedThreadStatFile)877 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadStatFile) {
878 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
879 {1, {1, 234}},
880 };
881
882 std::unordered_map<pid_t, std::string> perProcessStat = {
883 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
884 };
885
886 std::unordered_map<pid_t, std::string> perProcessStatus = {
887 {1, pidStatusStr(1, 0)},
888 };
889
890 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
891 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
892 };
893
894 std::unordered_map<pid_t, std::string> perThreadStat = {
895 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
896 {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
897 };
898
899 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
900 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
901 };
902
903 TemporaryDir procDir;
904 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
905 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
906 perThreadTimeInState));
907
908 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
909 collector.init();
910
911 ASSERT_TRUE(collector.enabled())
912 << "Files under the path `" << procDir.path << "` are inaccessible";
913 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file";
914 }
915
TEST(UidProcStatsCollectorTest,TestErrorOnCorruptedThreadTimeInStateFile)916 TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadTimeInStateFile) {
917 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
918 {1, {1, 234}},
919 };
920
921 std::unordered_map<pid_t, std::string> perProcessStat = {
922 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
923 };
924
925 std::unordered_map<pid_t, std::string> perProcessStatus = {
926 {1, pidStatusStr(1, 0)},
927 };
928
929 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
930 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
931 };
932
933 std::unordered_map<pid_t, std::string> perThreadStat = {
934 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
935 {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 500\n"},
936 };
937
938 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
939 {1, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 0\n"},
940 {234, "cpu0\n300000 0\n1700000 10\ncpu4\n710000 0\n1800000 CORRUPTED\n DATA"},
941 };
942
943 TemporaryDir procDir;
944 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
945 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
946 perThreadTimeInState));
947
948 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
949 collector.init();
950
951 ASSERT_TRUE(collector.enabled())
952 << "Files under the path `" << procDir.path << "` are inaccessible";
953 ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file";
954 }
955
TEST(UidProcStatsCollectorTest,TestHandlesSpaceInCommName)956 TEST(UidProcStatsCollectorTest, TestHandlesSpaceInCommName) {
957 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
958 {1, {1}},
959 };
960
961 std::unordered_map<pid_t, std::string> perProcessStat = {
962 {1,
963 "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"},
964 };
965
966 std::unordered_map<pid_t, std::string> perProcessStatus = {
967 {1, pidStatusStr(1, 0)},
968 };
969
970 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
971 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
972 };
973
974 std::unordered_map<pid_t, std::string> perThreadStat = {
975 {1,
976 "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"},
977 };
978
979 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
980 {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
981 };
982
983 std::unordered_map<uid_t, UidProcStats> expected = {
984 {0,
985 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
986 .cpuCycles = 340'000'000,
987 .totalMajorFaults = 200,
988 .totalTasksCount = 1,
989 .ioBlockedTasksCount = 0,
990 .totalRssKb = 1000,
991 .totalPssKb = 865,
992 .processStatsByPid = {
993 {1,
994 {/*comm=*/"random process name with space",
995 /*startTimeMillis=*/ticksToMillis(19),
996 /*cpuTimeMillis=*/ticksToMillis(20),
997 /*totalCpuCycles=*/340'000'000, /*totalMajorFaults=*/200,
998 /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
999 /*cpuCyclesByTid=*/{{1, 340'000'000}},
1000 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
1001 /*swapPssKb=*/200}}}}}};
1002 applyFeatureFilter(&expected);
1003
1004 TemporaryDir procDir;
1005 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
1006 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
1007 perThreadTimeInState));
1008
1009 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
1010 collector.init();
1011
1012 ASSERT_TRUE(collector.enabled())
1013 << "Files under the path `" << procDir.path << "` are inaccessible";
1014 ASSERT_RESULT_OK(collector.collect());
1015
1016 auto actual = collector.deltaStats();
1017
1018 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
1019 << "Proc pid contents doesn't match.\nExpected:\n"
1020 << toString(expected) << "\nActual:\n"
1021 << toString(actual);
1022 }
1023
TEST(UidProcStatsCollectorTest,TestHandlesTimeInStateFileDisabledWithNoFile)1024 TEST(UidProcStatsCollectorTest, TestHandlesTimeInStateFileDisabledWithNoFile) {
1025 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
1026 {1, {1}},
1027 };
1028
1029 std::unordered_map<pid_t, std::string> perProcessStat = {
1030 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1031 };
1032
1033 std::unordered_map<pid_t, std::string> perProcessStatus = {
1034 {1, pidStatusStr(1, 0)},
1035 };
1036
1037 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
1038 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
1039 };
1040
1041 std::unordered_map<pid_t, std::string> perThreadStat = {
1042 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1043 };
1044
1045 // No time_in_state file present in procfs
1046
1047 std::unordered_map<uid_t, UidProcStats> expected = {
1048 {0,
1049 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
1050 .cpuCycles = 0,
1051 .totalMajorFaults = 200,
1052 .totalTasksCount = 1,
1053 .ioBlockedTasksCount = 0,
1054 .totalRssKb = 1000,
1055 .totalPssKb = 865,
1056 .processStatsByPid = {
1057 {1,
1058 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
1059 /*cpuTimeMillis=*/ticksToMillis(20), /*totalCpuCycles=*/0,
1060 /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
1061 /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
1062 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
1063 /*swapPssKb=*/200}}}}}};
1064
1065 applyFeatureFilter(&expected);
1066
1067 TemporaryDir procDir;
1068 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
1069 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
1070 {}));
1071
1072 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
1073 collector.init();
1074
1075 ASSERT_TRUE(collector.enabled())
1076 << "Files under the path `" << procDir.path << "` are inaccessible";
1077 ASSERT_RESULT_OK(collector.collect());
1078
1079 auto actual = collector.deltaStats();
1080
1081 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
1082 << "Proc pid contents doesn't match.\nExpected:\n"
1083 << toString(expected) << "\nActual:\n"
1084 << toString(actual);
1085 }
1086
TEST(UidProcStatsCollectorTest,TestHandlesTimeInStateFileDisabledWithEmptyFile)1087 TEST(UidProcStatsCollectorTest, TestHandlesTimeInStateFileDisabledWithEmptyFile) {
1088 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
1089 {1, {1}},
1090 };
1091
1092 std::unordered_map<pid_t, std::string> perProcessStat = {
1093 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1094 };
1095
1096 std::unordered_map<pid_t, std::string> perProcessStatus = {
1097 {1, pidStatusStr(1, 0)},
1098 };
1099
1100 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
1101 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
1102 };
1103
1104 std::unordered_map<pid_t, std::string> perThreadStat = {
1105 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1106 };
1107
1108 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
1109 {1, ""},
1110 };
1111
1112 std::unordered_map<uid_t, UidProcStats> expected = {
1113 {0,
1114 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
1115 .cpuCycles = 0,
1116 .totalMajorFaults = 200,
1117 .totalTasksCount = 1,
1118 .ioBlockedTasksCount = 0,
1119 .totalRssKb = 1000,
1120 .totalPssKb = 865,
1121 .processStatsByPid = {
1122 {1,
1123 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
1124 /*cpuTimeMillis=*/ticksToMillis(20), /*totalCpuCycles=*/0,
1125 /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
1126 /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
1127 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
1128 /*swapPssKb=*/200}}}}}};
1129 applyFeatureFilter(&expected);
1130
1131 TemporaryDir procDir;
1132 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
1133 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
1134 {}));
1135
1136 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
1137 collector.init();
1138
1139 ASSERT_TRUE(collector.enabled())
1140 << "Files under the path `" << procDir.path << "` are inaccessible";
1141 ASSERT_RESULT_OK(collector.collect());
1142
1143 auto actual = collector.deltaStats();
1144
1145 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
1146 << "Proc pid contents doesn't match.\nExpected:\n"
1147 << toString(expected) << "\nActual:\n"
1148 << toString(actual);
1149 }
1150
TEST(UidProcStatsCollectorTest,TestHandlesTimeInStateFileDisabledWithZeroCpuCycles)1151 TEST(UidProcStatsCollectorTest, TestHandlesTimeInStateFileDisabledWithZeroCpuCycles) {
1152 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
1153 {1, {1}},
1154 };
1155
1156 std::unordered_map<pid_t, std::string> perProcessStat = {
1157 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1158 };
1159
1160 std::unordered_map<pid_t, std::string> perProcessStatus = {
1161 {1, pidStatusStr(1, 0)},
1162 };
1163
1164 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
1165 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
1166 };
1167
1168 std::unordered_map<pid_t, std::string> perThreadStat = {
1169 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1170 };
1171
1172 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
1173 {1, "cpu0\n300000 0\n1700000 0\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
1174 };
1175
1176 std::unordered_map<uid_t, UidProcStats> expected = {
1177 {0,
1178 UidProcStats{.cpuTimeMillis = ticksToMillis(20),
1179 .cpuCycles = 0,
1180 .totalMajorFaults = 200,
1181 .totalTasksCount = 1,
1182 .ioBlockedTasksCount = 0,
1183 .totalRssKb = 1000,
1184 .totalPssKb = 865,
1185 .processStatsByPid = {
1186 {1,
1187 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
1188 /*cpuTimeMillis=*/ticksToMillis(20), /*totalCpuCycles=*/0,
1189 /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
1190 /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
1191 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
1192 /*swapPssKb=*/200}}}}}};
1193 applyFeatureFilter(&expected);
1194
1195 TemporaryDir procDir;
1196 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
1197 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
1198 {}));
1199
1200 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
1201 collector.init();
1202
1203 ASSERT_TRUE(collector.enabled())
1204 << "Files under the path `" << procDir.path << "` are inaccessible";
1205 ASSERT_RESULT_OK(collector.collect());
1206
1207 auto actual = collector.deltaStats();
1208
1209 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
1210 << "Proc pid contents doesn't match.\nExpected:\n"
1211 << toString(expected) << "\nActual:\n"
1212 << toString(actual);
1213 }
1214
TEST(UidProcStatsCollectorTest,TestHandlesNoTimeInStateFileDuringCollection)1215 TEST(UidProcStatsCollectorTest, TestHandlesNoTimeInStateFileDuringCollection) {
1216 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
1217 {1, {1, 234}},
1218 };
1219
1220 std::unordered_map<pid_t, std::string> perProcessStat = {
1221 {1, "1 (init) S 0 0 0 0 0 0 0 0 210 0 15 15 0 0 0 0 2 0 19\n"},
1222 };
1223
1224 std::unordered_map<pid_t, std::string> perProcessStatus = {
1225 {1, pidStatusStr(1, 0)},
1226 };
1227
1228 std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
1229 {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
1230 };
1231
1232 std::unordered_map<pid_t, std::string> perThreadStat = {
1233 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 2 0 19\n"},
1234 {234, "1 (init) S 0 0 0 0 0 0 0 0 10 0 5 5 0 0 0 0 2 0 19\n"},
1235 };
1236
1237 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
1238 {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
1239 // No time_in_state file present for TID 234
1240 };
1241
1242 std::unordered_map<uid_t, UidProcStats> expected = {
1243 {0,
1244 UidProcStats{.cpuTimeMillis = ticksToMillis(30),
1245 .cpuCycles = 340'000'000,
1246 .totalMajorFaults = 210,
1247 .totalTasksCount = 2,
1248 .ioBlockedTasksCount = 0,
1249 .totalRssKb = 1000,
1250 .totalPssKb = 865,
1251 .processStatsByPid = {
1252 {1,
1253 {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
1254 /*cpuTimeMillis=*/ticksToMillis(30),
1255 /*totalCpuCycles=*/340'000'000, /*totalMajorFaults=*/210,
1256 /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
1257 /*cpuCyclesByTid=*/{{1, 340'000'000}},
1258 /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
1259 /*swapPssKb=*/200}}}}}};
1260 applyFeatureFilter(&expected);
1261
1262 TemporaryDir procDir;
1263 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
1264 perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
1265 perThreadTimeInState));
1266
1267 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
1268 collector.init();
1269
1270 ASSERT_TRUE(collector.enabled())
1271 << "Files under the path `" << procDir.path << "` are inaccessible";
1272 ASSERT_RESULT_OK(collector.collect());
1273
1274 auto actual = collector.deltaStats();
1275
1276 EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
1277 << "Proc pid contents doesn't match.\nExpected:\n"
1278 << toString(expected) << "\nActual:\n"
1279 << toString(actual);
1280 }
1281
TEST(UidProcStatsCollectorTest,TestCollectorStatusOnMissingSmapsRollupAndStatmFiles)1282 TEST(UidProcStatsCollectorTest, TestCollectorStatusOnMissingSmapsRollupAndStatmFiles) {
1283 std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
1284 {1, {1}},
1285 };
1286
1287 std::unordered_map<pid_t, std::string> perProcessStat = {
1288 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1289 };
1290
1291 std::unordered_map<pid_t, std::string> perProcessStatus = {
1292 {1, pidStatusStr(1, 0)},
1293 };
1294
1295 std::unordered_map<pid_t, std::string> perThreadStat = {
1296 {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
1297 };
1298
1299 std::unordered_map<pid_t, std::string> perThreadTimeInState = {
1300 {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
1301 };
1302
1303 TemporaryDir procDir;
1304 ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
1305 /*processSmapsRollup=*/{}, /*processStatm=*/{},
1306 perThreadStat, perThreadTimeInState));
1307
1308 UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
1309 collector.init();
1310
1311 ASSERT_EQ(!car_watchdog_memory_profiling(), collector.enabled())
1312 << "Collector status when memory profiling feature is "
1313 << (car_watchdog_memory_profiling() ? "enabled" : "disabled")
1314 << " and per-process smaps rollup / statm are missing";
1315 }
1316
TEST(UidProcStatsCollectorTest,TestUidProcStatsCollectorContentsFromDevice)1317 TEST(UidProcStatsCollectorTest, TestUidProcStatsCollectorContentsFromDevice) {
1318 UidProcStatsCollector collector;
1319 collector.init();
1320
1321 ASSERT_TRUE(collector.enabled()) << "/proc/[pid]/.* files are inaccessible";
1322 ASSERT_RESULT_OK(collector.collect());
1323
1324 const auto& processStats = collector.deltaStats();
1325
1326 // The below check should pass because there should be at least one process.
1327 EXPECT_GT(processStats.size(), static_cast<size_t>(0));
1328 }
1329
1330 } // namespace watchdog
1331 } // namespace automotive
1332 } // namespace android
1333