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