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 "ProcStatCollector.h"
18
19 #include <android-base/file.h>
20 #include <android-base/stringprintf.h>
21 #include <gmock/gmock.h>
22
23 #include <inttypes.h>
24
25 #include <string>
26
27 namespace android {
28 namespace automotive {
29 namespace watchdog {
30
31 namespace {
32
33 using ::android::base::StringPrintf;
34 using ::android::base::WriteStringToFile;
35
36 const int64_t kMillisPerClockTick = 1000 / sysconf(_SC_CLK_TCK);
37
clockTicksToMillis(int64_t ticks)38 int64_t clockTicksToMillis(int64_t ticks) {
39 return ticks * kMillisPerClockTick;
40 }
41
toString(const ProcStatInfo & info)42 std::string toString(const ProcStatInfo& info) {
43 const auto& cpuStats = info.cpuStats;
44 std::stringstream kernelStartTimeEpochSeconds;
45 kernelStartTimeEpochSeconds << info.kernelStartTimeEpochSeconds;
46 return StringPrintf("KernelStartTimeEpochSeconds: %s \nCpu Stats:\nUserTimeMillis: %" PRIu64
47 " NiceTimeMillis: %" PRIu64
48 " SysTimeMillis: %" PRIu64 " IdleTimeMillis: %" PRIu64
49 " IoWaitTimeMillis: %" PRIu64 " IrqTimeMillis: %" PRIu64
50 " SoftIrqTimeMillis: %" PRIu64 " StealTimeMillis: %" PRIu64
51 " GuestTimeMillis: %" PRIu64 " GuestNiceTimeMillis: %" PRIu64
52 "\nNumber of running processes: %" PRIu32
53 "\nNumber of blocked processes: %" PRIu32
54 "\nNumber of context switches: %" PRIu64,
55 kernelStartTimeEpochSeconds.str().c_str(),
56 cpuStats.userTimeMillis, cpuStats.niceTimeMillis,
57 cpuStats.sysTimeMillis, cpuStats.idleTimeMillis,
58 cpuStats.ioWaitTimeMillis, cpuStats.irqTimeMillis,
59 cpuStats.softIrqTimeMillis, cpuStats.stealTimeMillis,
60 cpuStats.guestTimeMillis, cpuStats.guestNiceTimeMillis,
61 info.runnableProcessCount, info.ioBlockedProcessCount,
62 info.contextSwitchesCount);
63 }
64
65 } // namespace
66
TEST(ProcStatCollectorTest,TestValidStatFile)67 TEST(ProcStatCollectorTest, TestValidStatFile) {
68 constexpr char firstSnapshot[] =
69 "cpu 6200 5700 1700 3100 1100 5200 3900 0 0 0\n"
70 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
71 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
72 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
73 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
74 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
75 "0 0\n"
76 /* Skipped most of the intr line as it is not important for testing the ProcStat parsing
77 * logic.
78 */
79 "ctxt 579020168\n"
80 "btime 1579718450\n"
81 "processes 113804\n"
82 "procs_running 17\n"
83 "procs_blocked 5\n"
84 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
85 ProcStatInfo expectedFirstDelta;
86 expectedFirstDelta.kernelStartTimeEpochSeconds = 1579718450;
87 expectedFirstDelta.cpuStats = {
88 .userTimeMillis = clockTicksToMillis(6200),
89 .niceTimeMillis = clockTicksToMillis(5700),
90 .sysTimeMillis = clockTicksToMillis(1700),
91 .idleTimeMillis = clockTicksToMillis(3100),
92 .ioWaitTimeMillis = clockTicksToMillis(1100),
93 .irqTimeMillis = clockTicksToMillis(5200),
94 .softIrqTimeMillis = clockTicksToMillis(3900),
95 .stealTimeMillis = clockTicksToMillis(0),
96 .guestTimeMillis = clockTicksToMillis(0),
97 .guestNiceTimeMillis = clockTicksToMillis(0),
98 };
99 expectedFirstDelta.contextSwitchesCount = 579020168;
100 expectedFirstDelta.runnableProcessCount = 17;
101 expectedFirstDelta.ioBlockedProcessCount = 5;
102
103 TemporaryFile tf;
104 ASSERT_NE(tf.fd, -1);
105 ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
106
107 ProcStatCollector collector(tf.path);
108 collector.init();
109
110 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
111 ASSERT_RESULT_OK(collector.collect());
112
113 const auto& actualFirstDelta = collector.deltaStats();
114 EXPECT_EQ(expectedFirstDelta, actualFirstDelta) << "First snapshot doesn't match.\nExpected:\n"
115 << toString(expectedFirstDelta) << "\nActual:\n"
116 << toString(actualFirstDelta);
117
118 constexpr char secondSnapshot[] =
119 "cpu 16200 8700 2000 4100 2200 6200 5900 0 0 0\n"
120 "cpu0 4400 3400 700 890 800 4500 3100 0 0 0\n"
121 "cpu1 5900 3380 610 960 100 670 2000 0 0 0\n"
122 "cpu2 2900 1000 450 1400 800 600 460 0 0 0\n"
123 "cpu3 3000 920 240 850 500 430 340 0 0 0\n"
124 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
125 "0 0\n"
126 "ctxt 810020192\n"
127 "btime 1579718450\n"
128 "processes 113804\n"
129 "procs_running 10\n"
130 "procs_blocked 2\n"
131 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
132 ProcStatInfo expectedSecondDelta;
133 expectedSecondDelta.cpuStats = {
134 .userTimeMillis = clockTicksToMillis(10000),
135 .niceTimeMillis = clockTicksToMillis(3000),
136 .sysTimeMillis = clockTicksToMillis(300),
137 .idleTimeMillis = clockTicksToMillis(1000),
138 .ioWaitTimeMillis = clockTicksToMillis(1100),
139 .irqTimeMillis = clockTicksToMillis(1000),
140 .softIrqTimeMillis = clockTicksToMillis(2000),
141 .stealTimeMillis = clockTicksToMillis(0),
142 .guestTimeMillis = clockTicksToMillis(0),
143 .guestNiceTimeMillis = clockTicksToMillis(0),
144 };
145 expectedSecondDelta.kernelStartTimeEpochSeconds = 1579718450;
146 expectedSecondDelta.contextSwitchesCount = 231000024;
147 expectedSecondDelta.runnableProcessCount = 10;
148 expectedSecondDelta.ioBlockedProcessCount = 2;
149
150 ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
151 ASSERT_RESULT_OK(collector.collect());
152
153 const auto& actualSecondDelta = collector.deltaStats();
154 EXPECT_EQ(expectedSecondDelta, actualSecondDelta)
155 << "Second snapshot doesnt't match.\nExpected:\n"
156 << toString(expectedSecondDelta) << "\nActual:\n"
157 << toString(actualSecondDelta);
158 }
159
TEST(ProcStatCollectorTest,TestErrorOnCorruptedStatFile)160 TEST(ProcStatCollectorTest, TestErrorOnCorruptedStatFile) {
161 constexpr char contents[] =
162 "cpu 6200 5700 1700 3100 CORRUPTED DATA\n"
163 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
164 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
165 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
166 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
167 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
168 "0 0\n"
169 "ctxt 579020168\n"
170 "btime 1579718450\n"
171 "processes 113804\n"
172 "procs_running 17\n"
173 "procs_blocked 5\n"
174 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
175 TemporaryFile tf;
176 ASSERT_NE(tf.fd, -1);
177 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
178
179 ProcStatCollector collector(tf.path);
180 collector.init();
181
182 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
183 EXPECT_FALSE(collector.collect().ok()) << "No error returned for corrupted file";
184 }
185
TEST(ProcStatCollectorTest,TestErrorOnMissingCpuLine)186 TEST(ProcStatCollectorTest, TestErrorOnMissingCpuLine) {
187 constexpr char contents[] =
188 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
189 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
190 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
191 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
192 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
193 "0 0\n"
194 "ctxt 579020168\n"
195 "btime 1579718450\n"
196 "processes 113804\n"
197 "procs_running 17\n"
198 "procs_blocked 5\n"
199 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
200 TemporaryFile tf;
201 ASSERT_NE(tf.fd, -1);
202 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
203
204 ProcStatCollector collector(tf.path);
205 collector.init();
206
207 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
208 EXPECT_FALSE(collector.collect().ok()) << "No error returned due to missing cpu line";
209 }
210
TEST(ProcStatCollectorTest,TestErrorOnMissingCtxtLine)211 TEST(ProcStatCollectorTest, TestErrorOnMissingCtxtLine) {
212 constexpr char contents[] =
213 "cpu 16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
214 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
215 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
216 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
217 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
218 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
219 "0 0\n"
220 "btime 1579718450\n"
221 "processes 113804\n"
222 "procs_running 17\n"
223 "procs_blocked 5\n"
224 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
225 TemporaryFile tf;
226 ASSERT_NE(tf.fd, -1);
227 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
228
229 ProcStatCollector collector(tf.path);
230 collector.init();
231
232 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
233 EXPECT_FALSE(collector.collect().ok()) << "No error returned due to missing ctxt line";
234 }
235
TEST(ProcStatCollectorTest,TestErrorOnMissingProcsRunningLine)236 TEST(ProcStatCollectorTest, TestErrorOnMissingProcsRunningLine) {
237 constexpr char contents[] =
238 "cpu 16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
239 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
240 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
241 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
242 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
243 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
244 "0 0\n"
245 "ctxt 579020168\n"
246 "btime 1579718450\n"
247 "processes 113804\n"
248 "procs_blocked 5\n"
249 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
250 TemporaryFile tf;
251 ASSERT_NE(tf.fd, -1);
252 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
253
254 ProcStatCollector collector(tf.path);
255 collector.init();
256
257 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
258 EXPECT_FALSE(collector.collect().ok()) << "No error returned due to missing procs_running line";
259 }
260
TEST(ProcStatCollectorTest,TestErrorOnMissingProcsBlockedLine)261 TEST(ProcStatCollectorTest, TestErrorOnMissingProcsBlockedLine) {
262 constexpr char contents[] =
263 "cpu 16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
264 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
265 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
266 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
267 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
268 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
269 "0 0\n"
270 "ctxt 579020168\n"
271 "btime 1579718450\n"
272 "processes 113804\n"
273 "procs_running 17\n"
274 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
275 TemporaryFile tf;
276 ASSERT_NE(tf.fd, -1);
277 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
278
279 ProcStatCollector collector(tf.path);
280 collector.init();
281
282 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
283 EXPECT_FALSE(collector.collect().ok()) << "No error returned due to missing procs_blocked line";
284 }
285
TEST(ProcStatCollectorTest,TestErrorOnUnknownProcsLine)286 TEST(ProcStatCollectorTest, TestErrorOnUnknownProcsLine) {
287 constexpr char contents[] =
288 "cpu 16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
289 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
290 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
291 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
292 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
293 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
294 "0 0\n"
295 "ctxt 579020168\n"
296 "btime 1579718450\n"
297 "processes 113804\n"
298 "procs_running 17\n"
299 "procs_blocked 5\n"
300 "procs_sleeping 15\n"
301 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
302 TemporaryFile tf;
303 ASSERT_NE(tf.fd, -1);
304 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
305
306 ProcStatCollector collector(tf.path);
307 collector.init();
308
309 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
310 EXPECT_FALSE(collector.collect().ok()) << "No error returned due to unknown procs line";
311 }
312
TEST(ProcStatCollectorTest,TestProcStatContentsFromDevice)313 TEST(ProcStatCollectorTest, TestProcStatContentsFromDevice) {
314 ProcStatCollector collector;
315 collector.init();
316
317 ASSERT_TRUE(collector.enabled()) << kProcStatPath << " file is inaccessible";
318 ASSERT_RESULT_OK(collector.collect());
319
320 const auto& info = collector.deltaStats();
321 /* The below checks should pass because the /proc/stats file should have
322 * 1. The Kernel start time.
323 * 2. The CPU time spent since bootup.
324 * 3. There should be at least one running process.
325 */
326 EXPECT_GT(info.kernelStartTimeEpochSeconds, static_cast<time_t>(0));
327 EXPECT_GT(info.totalCpuTimeMillis(), 0);
328 EXPECT_GT(info.totalProcessCount(), static_cast<uint32_t>(0));
329 }
330
TEST(ProcStatCollectorTest,TestReadKernelStartTimeOnce)331 TEST(ProcStatCollectorTest, TestReadKernelStartTimeOnce) {
332 constexpr char contents[] =
333 "cpu 16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
334 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
335 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
336 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
337 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
338 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
339 "0 0\n"
340 "ctxt 579020168\n"
341 "btime 1579718450\n"
342 "processes 113804\n"
343 "procs_running 17\n"
344 "procs_blocked 5\n"
345 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
346 TemporaryFile tf;
347 ASSERT_NE(tf.fd, -1);
348 ASSERT_TRUE(WriteStringToFile(contents, tf.path));
349
350 ProcStatCollector collector(tf.path);
351 collector.init();
352
353 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
354 ASSERT_RESULT_OK(collector.collect());
355
356 const auto& firstLatestStats = collector.latestStats();
357
358 constexpr char contents_with_new_btime[] =
359 "cpu 16200 8700 2000 4100 1250 6200 5900 0 0 0\n"
360 "cpu0 2400 2900 600 690 340 4300 2100 0 0 0\n"
361 "cpu1 1900 2380 510 760 51 370 1500 0 0 0\n"
362 "cpu2 900 400 400 1000 600 400 160 0 0 0\n"
363 "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
364 "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
365 "0 0\n"
366 "ctxt 579020168\n"
367 "btime 6482659380\n"
368 "processes 113804\n"
369 "procs_running 17\n"
370 "procs_blocked 5\n"
371 "softirq 33275060 934664 11958403 5111 516325 200333 0 341482 10651335 0 8667407\n";
372 ASSERT_TRUE(WriteStringToFile(contents_with_new_btime, tf.path));
373
374 ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
375 ASSERT_RESULT_OK(collector.collect());
376
377 const auto& secondLatestStats = collector.latestStats();
378
379 ASSERT_TRUE(firstLatestStats.kernelStartTimeEpochSeconds ==
380 secondLatestStats.kernelStartTimeEpochSeconds)
381 << "kernel start time is read more than once";
382 }
383
384 } // namespace watchdog
385 } // namespace automotive
386 } // namespace android
387