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