1 /*
2  * Copyright (C) 2024 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 <gtest/gtest.h>
18 #include <pixelstats/MmMetricsReporter.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 
22 #include "MmMetricsGoldenAtomFieldTypes.h"
23 #include "MmMetricsGoldenResults.h"
24 #include "MockMmMetricsReporter.h"
25 #include "VendorAtomIntValueUtil.h"
26 
27 #ifndef ARRAY_SIZE
28 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
29 #endif
30 
31 namespace android {
32 namespace hardware {
33 namespace google {
34 namespace pixel {
35 
36 using mm_metrics_atom_field_test_golden_results::MmMetricsGcmaPerDayHistogram_field_types;
37 using mm_metrics_atom_field_test_golden_results::MmMetricsGcmaPerDaySimple_field_types;
38 using mm_metrics_atom_field_test_golden_results::MmMetricsGcmaPerHour_field_types;
39 using mm_metrics_atom_field_test_golden_results::MmMetricsOomGroupMemUsage_field_types;
40 using mm_metrics_atom_field_test_golden_results::PixelMmMetricsPerDay_field_types;
41 using mm_metrics_atom_field_test_golden_results::PixelMmMetricsPerHour_field_types;
42 
43 using mm_metrics_reporter_test_golden_result::MmMetricsGcmaPerDayHistogram_golden;
44 using mm_metrics_reporter_test_golden_result::MmMetricsGcmaPerDaySimple_golden;
45 using mm_metrics_reporter_test_golden_result::MmMetricsGcmaPerHour_golden;
46 using mm_metrics_reporter_test_golden_result::MmMetricsOomGroupMemUsage_golden;
47 using mm_metrics_reporter_test_golden_result::PixelMmMetricsPerDay_golden;
48 using mm_metrics_reporter_test_golden_result::PixelMmMetricsPerHour_golden;
49 
50 const char *data_base_path = "/data/local/tmp/test/pixelstats_mm_test/data";
51 
TEST(MmMetricsReporterTest,MmMetricsPerHourAtomFieldOffsetTypeTest)52 TEST(MmMetricsReporterTest, MmMetricsPerHourAtomFieldOffsetTypeTest) {
53     int i = -1;
54     uint64_t golden_result;
55     int field_type;
56     std::vector<VendorAtomValue> values;
57     MockMmMetricsReporter mreport;
58     const std::string data_path0 = std::string(data_base_path) + "/test_data_0";
59     const std::string data_path1 = std::string(data_base_path) + "/test_data_1";
60 
61     // Assert failure means the test case itself has a bug.
62     ASSERT_EQ(ARRAY_SIZE(PixelMmMetricsPerHour_golden),
63               ARRAY_SIZE(PixelMmMetricsPerHour_field_types));
64 
65     /**
66      * In test code we use setBasePath() to read different data sets for simulating
67      * different timing reads of a sysfs node.
68      */
69 
70     /**
71      * aggregatePixelMmMetricsPer5Min() aggregates PSI into max, min, and avg.
72      * For the regular code, it will be called 12 times per hour (i.e. once per 5min)
73      * For test code we do 6 times: enough for testing.
74      * e.g. here average  = (3 x data0 + 3 x data1) / 6 == avg of data 0, 1
75      * The following sequence simulate regular code obtaining sysfs nodes into
76      * values[] array (i.e. atom), ready to be sent to the server
77      */
78     mreport.setBasePath(data_path0);
79     mreport.aggregatePixelMmMetricsPer5Min();
80     mreport.aggregatePixelMmMetricsPer5Min();
81     mreport.aggregatePixelMmMetricsPer5Min();
82     mreport.setBasePath(data_path1);
83     mreport.aggregatePixelMmMetricsPer5Min();
84     mreport.aggregatePixelMmMetricsPer5Min();
85     mreport.aggregatePixelMmMetricsPer5Min();
86 
87     // other fields from data set #0
88     mreport.setBasePath(data_path0);
89     values = mreport.genPixelMmMetricsPerHour();
90 
91     // Validate the atom: compare with golden results
92     EXPECT_EQ(values.size(), ARRAY_SIZE(PixelMmMetricsPerHour_field_types));
93     for (auto const &v : values) {
94         i++;
95         golden_result = PixelMmMetricsPerHour_golden[i];
96         field_type = PixelMmMetricsPerHour_field_types[i];
97         if (golden_result == -1)
98             continue;  // no need to test (e.g. deprecated field)
99 
100         EXPECT_EQ(static_cast<int>(v.getTag()), field_type) << "type mismatch at offset " << i;
101         EXPECT_EQ(getVendorAtomIntValue(v), golden_result) << "value mismatch at offset " << i;
102     }
103 }
104 
TEST(MmMetricsReporterTest,MmMetricsPerDayAtomFieldOffsetTypeTest)105 TEST(MmMetricsReporterTest, MmMetricsPerDayAtomFieldOffsetTypeTest) {
106     int i = -1;
107     uint64_t golden_result;
108     int field_type;
109     std::vector<VendorAtomValue> values;
110     MockMmMetricsReporter mreport;
111     const std::string data_path0 = std::string(data_base_path) + "/test_data_0";
112     const std::string data_path1 = std::string(data_base_path) + "/test_data_1";
113 
114     // Assert failure means the test case itself has a bug.
115     ASSERT_EQ(ARRAY_SIZE(PixelMmMetricsPerDay_golden),
116               ARRAY_SIZE(PixelMmMetricsPerDay_field_types));
117 
118     mreport.setBasePath(data_path0);
119     values = mreport.genPixelMmMetricsPerDay();
120 
121     // PixelMmMetricsPerDay calculatd the difference of consecutive readings.
122     // So, it will not send values[] at the 1st read. (i.e. empty for the 1st read)
123     EXPECT_EQ(values.size(), 0);
124     values.clear();
125 
126     mreport.setBasePath(data_path1);
127     values = mreport.genPixelMmMetricsPerDay();
128 
129     // Per Day metrics (diffs) should be calculated, values[] will be non-empty now.
130     // number of data should be the same as the number of fields in the atom.
131     EXPECT_EQ(values.size(), ARRAY_SIZE(PixelMmMetricsPerDay_field_types));
132     for (auto const &v : values) {
133         i++;
134         EXPECT_LT(i, ARRAY_SIZE(PixelMmMetricsPerDay_field_types));
135 
136         golden_result = PixelMmMetricsPerDay_golden[i];
137         field_type = PixelMmMetricsPerDay_field_types[i];
138         if (golden_result == -1)
139             continue;  // no need to test (e.g. deprecated field)
140 
141         EXPECT_EQ(static_cast<int>(v.getTag()), field_type) << "type mismatch at offset " << i;
142         EXPECT_EQ(getVendorAtomIntValue(v), golden_result) << "value mismatch at offset " << i;
143     }
144 }
145 
TEST(MmMetricsReporterTest,MmMetricsOomGroupMemUsageSuccess)146 TEST(MmMetricsReporterTest, MmMetricsOomGroupMemUsageSuccess) {
147     constexpr int kNumTests = 2;
148     MockMmMetricsReporter mreport;
149     const std::string data_path[kNumTests] = {
150             std::string(data_base_path) + "/test_data_0",
151             std::string(data_base_path) + "/test_data_1",
152     };
153     std::vector<MmMetricsReporter::OomGroupMemUsage> ogusage;
154     int32_t og_metric_uid[kNumTests];
155     auto &golden = MmMetricsOomGroupMemUsage_golden;
156     auto &gold_ftype = MmMetricsOomGroupMemUsage_field_types;
157 
158     constexpr int kNumFields = ARRAY_SIZE(MmMetricsOomGroupMemUsage_field_types);
159     constexpr int kNumLines = ARRAY_SIZE(golden[0]);
160 
161     ASSERT_LT(kNumLines, 100);
162 
163     // Check testcase consistency (if fail, the test case itself has some bug)
164     ASSERT_EQ(ARRAY_SIZE(golden), kNumTests);
165     ASSERT_EQ(ARRAY_SIZE(golden[1]), kNumLines);
166     ASSERT_EQ(ARRAY_SIZE(MmMetricsOomGroupMemUsage_field_types), kNumFields);
167 
168     for (int i = 0; i < kNumTests; i++) {
169         for (int j = 0; j < kNumLines; j++) {
170             // golden result does not have UID field, which is date/time based unique ID.
171             ASSERT_EQ(ARRAY_SIZE(golden[i][j]), kNumFields - 1);
172         }
173     }
174 
175     for (int test_iteration = 0; test_iteration < kNumTests; ++test_iteration) {
176         // setup
177         mreport.setBasePath(data_path[test_iteration]);
178 
179         // --- start test ---
180         ASSERT_TRUE(mreport.readMmProcessUsageByOomGroup(&ogusage));
181         ASSERT_EQ(ogusage.size(), kNumLines);
182 
183         int line = 0;
184         for (const auto &u : ogusage) {
185             std::vector<VendorAtomValue> values =
186                     mreport.genMmProcessUsageByOomGroupSnapshotAtom(u);
187             int32_t &uid = og_metric_uid[test_iteration];
188 
189             // check size
190             ASSERT_EQ(values.size(), kNumFields)
191                     << "Size mismatch: test# " << test_iteration << " line " << line;
192 
193             if (line == 0) {
194                 uid = getVendorAtomIntValue(values[0]);
195             } else {
196                 // check UID
197                 EXPECT_EQ(getVendorAtomIntValue(values[0]), uid)
198                         << "value mismatch: test# " << test_iteration << " line " << line
199                         << " field 0";
200             }
201 
202             for (int field = 1; field < kNumFields; ++field) {
203                 // check types
204                 EXPECT_EQ(static_cast<int>(values[field].getTag()), gold_ftype[field])
205                         << "type mismatch: test# " << test_iteration << " line " << line
206                         << " field " << field;
207 
208                 if (static_cast<int>(values[field].getTag()) != gold_ftype[field])
209                     continue;  // no checking values when the type is already wrong.
210 
211                 // check values
212                 EXPECT_EQ(getVendorAtomIntValue(values[field]),
213                           golden[test_iteration][line][field - 1])
214                         << "value mismatch: test# " << test_iteration << " line " << line
215                         << " field " << field;
216             }
217             line++;
218         }
219         // --- end test ---
220     }
221 
222     // metric_uid must be unique
223     EXPECT_NE(og_metric_uid[0], og_metric_uid[1]);
224 }
225 
TEST(MmMetricsReporterTest,MmMetricsOomGroupMemUsageFailFileNotFound)226 TEST(MmMetricsReporterTest, MmMetricsOomGroupMemUsageFailFileNotFound) {
227     constexpr int kNumTests = 2;
228     MockMmMetricsReporter mreport;
229     const std::string data_path = std::string(data_base_path) + "/nonexisting_dir";
230     std::vector<MmMetricsReporter::OomGroupMemUsage> ogusage;
231     int32_t uid;
232 
233     // setup
234     mreport.setBasePath(data_path);
235 
236     // --- start test ---
237     ASSERT_FALSE(mreport.readMmProcessUsageByOomGroup(&ogusage));
238     ASSERT_EQ(ogusage.size(), 0);
239 }
240 
file_exists(const char * const path)241 static bool file_exists(const char *const path) {
242     struct stat sbuf;
243 
244     return (stat(path, &sbuf) == 0);
245 }
246 
TEST(MmMetricsReporterTest,MmMetricsOomGroupMemUsageMultipleFailCases)247 TEST(MmMetricsReporterTest, MmMetricsOomGroupMemUsageMultipleFailCases) {
248     constexpr int kNumTests = 8;
249     MockMmMetricsReporter mreport;
250     const std::string data_path[kNumTests] = {
251             std::string(data_base_path) + "/test_data_oom_usage_fail/1",
252             std::string(data_base_path) + "/test_data_oom_usage_fail/2",
253             std::string(data_base_path) + "/test_data_oom_usage_fail/3",
254             std::string(data_base_path) + "/test_data_oom_usage_fail/4",
255             std::string(data_base_path) + "/test_data_oom_usage_fail/5",
256             std::string(data_base_path) + "/test_data_oom_usage_fail/6",
257             std::string(data_base_path) + "/test_data_oom_usage_fail/7",
258             std::string(data_base_path) + "/test_data_oom_usage_fail/8",
259     };
260     const char *file = "oom_mm_usage";
261     std::vector<MmMetricsReporter::OomGroupMemUsage> ogusage;
262 
263     for (int test_iteration = 0; test_iteration < kNumTests; ++test_iteration) {
264         // setup
265         mreport.setBasePath(data_path[test_iteration]);
266 
267         // check file exist, otherwise it is testing "file not found" rather than the desired test
268         ASSERT_TRUE(file_exists((data_path[test_iteration] + "/" + file).c_str()));
269 
270         // --- start test ---
271         ASSERT_FALSE(mreport.readMmProcessUsageByOomGroup(&ogusage))
272                 << "Iteration " << test_iteration << ": test fail.";
273         ASSERT_EQ(ogusage.size(), 0) << "Iteration " << test_iteration << ": test fail.";
274     }
275 }
276 
TEST(MmMetricsReporterTest,MmMetricsGcmaPerHourSuccess)277 TEST(MmMetricsReporterTest, MmMetricsGcmaPerHourSuccess) {
278     MockMmMetricsReporter mreport;
279     const std::string data_path = std::string(data_base_path) + "/test_data_0";
280     auto &golden = MmMetricsGcmaPerHour_golden;
281     auto &gold_ftype = MmMetricsGcmaPerHour_field_types;
282 
283     constexpr int kNumFields = ARRAY_SIZE(gold_ftype);
284     constexpr int kNumLines = ARRAY_SIZE(golden);
285 
286     // Check testcase consistency (if fail, the test case itself has some bug)
287     ASSERT_EQ(kNumFields, kNumLines);
288 
289     // setup
290     mreport.setBasePath(data_path);
291 
292     // --- start test ---
293     std::vector<VendorAtomValue> values = mreport.readAndGenGcmaPerHour();
294 
295     // check size
296     ASSERT_EQ(values.size(), kNumLines);
297 
298     for (int field = 0; field < kNumFields; ++field) {
299         // check type
300         EXPECT_EQ(static_cast<int>(values[field].getTag()), gold_ftype[field])
301                 << "type mismatch @ field #" << field;
302 
303         if (static_cast<int>(values[field].getTag()) != gold_ftype[field])
304             continue;  // no checking the value when the type is wrong.
305 
306         // check value
307         EXPECT_EQ(getVendorAtomIntValue(values[field]), golden[field])
308                 << "value mismatch @ field #" << field;
309     }
310 }
311 
TEST(MmMetricsReporterTest,MmMetricsGcmaPerDaySuccess)312 TEST(MmMetricsReporterTest, MmMetricsGcmaPerDaySuccess) {
313     MockMmMetricsReporter mreport;
314     const std::string data_path = std::string(data_base_path) + "/test_data_0";
315     auto &golden_simple = MmMetricsGcmaPerDaySimple_golden;
316     auto &golden_histogram = MmMetricsGcmaPerDayHistogram_golden;
317 
318     auto &gold_simple_ftype = MmMetricsGcmaPerDaySimple_field_types;
319     auto &gold_histogram_ftype = MmMetricsGcmaPerDayHistogram_field_types;
320 
321     constexpr int kNumSimpleValues = 4;
322     constexpr int kNumHistogramValues = 4;
323     // total field num in atom values need to count the histogram array as one.
324     constexpr int kNumAtomValues = kNumSimpleValues + 1;
325 
326     // Check testcase consistency (if fail, the test case itself has some bug)
327     ASSERT_EQ(ARRAY_SIZE(golden_simple), kNumSimpleValues);
328     ASSERT_EQ(ARRAY_SIZE(golden_histogram), kNumHistogramValues);
329     ASSERT_EQ(ARRAY_SIZE(gold_simple_ftype), kNumSimpleValues + 1);  // count the last array type
330     ASSERT_EQ(ARRAY_SIZE(gold_histogram_ftype), kNumHistogramValues);
331 
332     // setup
333     mreport.setBasePath(data_path);
334 
335     // --- start test ---
336     std::vector<VendorAtomValue> values = mreport.readAndGenGcmaPerDay();
337 
338     /*
339      * check size +1:
340      * Histogram in the form of a vector in the last element of 'Simple' value array.
341      */
342     ASSERT_EQ(values.size(), kNumAtomValues);
343 
344     // check 'simple' values
345     for (int field = 0; field < kNumSimpleValues; ++field) {
346         // check type
347         EXPECT_EQ(static_cast<int>(values[field].getTag()), gold_simple_ftype[field])
348                 << "type mismatch @ field #" << field;
349 
350         if (static_cast<int>(values[field].getTag()) != gold_simple_ftype[field])
351             continue;  // no checking the value when the type is wrong.
352 
353         if (field == kNumAtomValues - 1)
354             continue;  // same as break.  The last one is an array, compare type only here.
355 
356         EXPECT_EQ(getVendorAtomIntValue(values[field]), golden_simple[field])
357                 << "value mismatch @ field #" << field;
358     }
359 
360     // check array validity
361     auto &arrAtomValue = values[kNumAtomValues - 1];
362     const std::optional<std::vector<int64_t>> &repeatedLongValue =
363             arrAtomValue.get<VendorAtomValue::repeatedLongValue>();
364     ASSERT_TRUE(repeatedLongValue.has_value());
365 
366     // check array size
367     ASSERT_EQ(repeatedLongValue.value().size(), kNumHistogramValues);
368 
369     // check array values
370     for (int field = 0; field < kNumHistogramValues; ++field) {
371         EXPECT_EQ(repeatedLongValue.value()[field], golden_histogram[field]);
372     }
373 }
374 
375 }  // namespace pixel
376 }  // namespace google
377 }  // namespace hardware
378 }  // namespace android
379