1 /*
2  * Copyright (C) 2021 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 #define LOG_TAG "pixelstats: MmMetrics"
18 
19 #include <aidl/android/frameworks/stats/IStats.h>
20 #include <android-base/file.h>
21 #include <android-base/parsedouble.h>
22 #include <android-base/parseint.h>
23 #include <android-base/properties.h>
24 #include <android-base/stringprintf.h>
25 #include <android-base/strings.h>
26 #include <android/binder_manager.h>
27 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
28 #include <pixelstats/MmMetricsReporter.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <time.h>
32 #include <unistd.h>
33 #include <utils/Log.h>
34 
35 #include <array>
36 #include <cinttypes>
37 #include <cstdint>
38 #include <numeric>
39 #include <optional>
40 #include <vector>
41 
42 #define SZ_4K 0x00001000
43 #define SZ_2M 0x00200000
44 
45 namespace android {
46 namespace hardware {
47 namespace google {
48 namespace pixel {
49 
50 using aidl::android::frameworks::stats::IStats;
51 using aidl::android::frameworks::stats::VendorAtom;
52 using aidl::android::frameworks::stats::VendorAtomValue;
53 using android::base::ReadFileToString;
54 using android::base::StartsWith;
55 using android::hardware::google::pixel::PixelAtoms::CmaStatus;
56 using android::hardware::google::pixel::PixelAtoms::CmaStatusExt;
57 using android::hardware::google::pixel::PixelAtoms::PixelMmMetricsPerDay;
58 using android::hardware::google::pixel::PixelAtoms::PixelMmMetricsPerHour;
59 
60 const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kMmMetricsPerHourInfo = {
61         {"nr_free_pages", PixelMmMetricsPerHour::kFreePagesFieldNumber, false},
62         {"nr_anon_pages", PixelMmMetricsPerHour::kAnonPagesFieldNumber, false},
63         {"nr_file_pages", PixelMmMetricsPerHour::kFilePagesFieldNumber, false},
64         {"nr_slab_reclaimable", PixelMmMetricsPerHour::kSlabReclaimableFieldNumber, false},
65         {"nr_slab_unreclaimable", PixelMmMetricsPerHour::kSlabUnreclaimableFieldNumber, false},
66         {"nr_zspages", PixelMmMetricsPerHour::kZspagesFieldNumber, false},
67         {"nr_unevictable", PixelMmMetricsPerHour::kUnevictableFieldNumber, false},
68         {"nr_shmem", PixelMmMetricsPerHour::kShmemPagesFieldNumber, false},
69         {"nr_page_table_pages", PixelMmMetricsPerHour::kPageTablePagesFieldNumber, false},
70         {"ION_heap", PixelMmMetricsPerHour::kDmabufKbFieldNumber, false},
71 };
72 
73 const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kMmMetricsPerDayInfo = {
74         {"workingset_refault", PixelMmMetricsPerDay::kWorkingsetRefaultFieldNumber, true},
75         {"pswpin", PixelMmMetricsPerDay::kPswpinFieldNumber, true},
76         {"pswpout", PixelMmMetricsPerDay::kPswpoutFieldNumber, true},
77         {"allocstall_dma", PixelMmMetricsPerDay::kAllocstallDmaFieldNumber, true},
78         {"allocstall_dma32", PixelMmMetricsPerDay::kAllocstallDma32FieldNumber, true},
79         {"allocstall_normal", PixelMmMetricsPerDay::kAllocstallNormalFieldNumber, true},
80         {"allocstall_movable", PixelMmMetricsPerDay::kAllocstallMovableFieldNumber, true},
81         {"pgalloc_dma", PixelMmMetricsPerDay::kPgallocDmaFieldNumber, true},
82         {"pgalloc_dma32", PixelMmMetricsPerDay::kPgallocDma32FieldNumber, true},
83         {"pgalloc_normal", PixelMmMetricsPerDay::kPgallocNormalFieldNumber, true},
84         {"pgalloc_movable", PixelMmMetricsPerDay::kPgallocMovableFieldNumber, true},
85         {"pgsteal_kswapd", PixelMmMetricsPerDay::kPgstealKswapdFieldNumber, true},
86         {"pgsteal_direct", PixelMmMetricsPerDay::kPgstealDirectFieldNumber, true},
87         {"pgscan_kswapd", PixelMmMetricsPerDay::kPgscanKswapdFieldNumber, true},
88         {"pgscan_direct", PixelMmMetricsPerDay::kPgscanDirectFieldNumber, true},
89         {"oom_kill", PixelMmMetricsPerDay::kOomKillFieldNumber, true},
90         {"pgalloc_costly_order", PixelMmMetricsPerDay::kPgallocHighFieldNumber, true},
91         {"pgcache_hit", PixelMmMetricsPerDay::kPgcacheHitFieldNumber, true},
92         {"pgcache_miss", PixelMmMetricsPerDay::kPgcacheMissFieldNumber, true},
93         {"workingset_refault_file", PixelMmMetricsPerDay::kWorkingsetRefaultFileFieldNumber, true},
94         {"workingset_refault_anon", PixelMmMetricsPerDay::kWorkingsetRefaultAnonFieldNumber, true},
95         {"compact_success", PixelMmMetricsPerDay::kCompactSuccessFieldNumber, true},
96         {"compact_fail", PixelMmMetricsPerDay::kCompactFailFieldNumber, true},
97         {"kswapd_low_wmark_hit_quickly", PixelMmMetricsPerDay::kKswapdLowWmarkHqFieldNumber, true},
98         {"kswapd_high_wmark_hit_quickly", PixelMmMetricsPerDay::kKswapdHighWmarkHqFieldNumber,
99          true},
100         {"thp_file_alloc", PixelMmMetricsPerDay::kThpFileAllocFieldNumber, true},
101         {"thp_zero_page_alloc", PixelMmMetricsPerDay::kThpZeroPageAllocFieldNumber, true},
102         {"thp_split_page", PixelMmMetricsPerDay::kThpSplitPageFieldNumber, true},
103         {"thp_migration_split", PixelMmMetricsPerDay::kThpMigrationSplitFieldNumber, true},
104         {"thp_deferred_split_page", PixelMmMetricsPerDay::kThpDeferredSplitPageFieldNumber, true},
105         {"pageoutrun", PixelMmMetricsPerDay::kKswapdPageoutRunFieldNumber, true},
106 };
107 
108 const std::vector<MmMetricsReporter::ProcStatMetricsInfo> MmMetricsReporter::kProcStatInfo = {
109         // sum of the cpu line: the cpu total time  (-1 means to sum up all)
110         {"cpu", -1, PixelMmMetricsPerDay::kCpuTotalTimeCsFieldNumber, true},
111 
112         // array[3] of the cpu line: the Idle time
113         {"cpu", 3, PixelMmMetricsPerDay::kCpuIdleTimeCsFieldNumber, true},
114 
115         // array[4] of the cpu line: the I/O wait time.
116         {"cpu", 4, PixelMmMetricsPerDay::kCpuIoWaitTimeCsFieldNumber, true},
117 };
118 
119 const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kCmaStatusInfo = {
120         {"alloc_pages_attempts", CmaStatus::kCmaAllocPagesAttemptsFieldNumber, true},
121         {"alloc_pages_failfast_attempts", CmaStatus::kCmaAllocPagesSoftAttemptsFieldNumber, true},
122         {"fail_pages", CmaStatus::kCmaFailPagesFieldNumber, true},
123         {"fail_failfast_pages", CmaStatus::kCmaFailSoftPagesFieldNumber, true},
124         {"migrated_pages", CmaStatus::kMigratedPagesFieldNumber, true},
125 };
126 
127 const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kCmaStatusExtInfo = {
128         {"latency_low", CmaStatusExt::kCmaAllocLatencyLowFieldNumber, false},
129         {"latency_mid", CmaStatusExt::kCmaAllocLatencyMidFieldNumber, false},
130         {"latency_high", CmaStatusExt::kCmaAllocLatencyHighFieldNumber, false},
131 };
132 
133 // Oom group range names
134 const std::array oom_group_range_names{
135         "[951,1000]", "[901,950]", "[851,900]", "[801,850]", "[751,800]",  "[701,750]",
136         "[651,700]",  "[601,650]", "[551,600]", "[501,550]", "[451,500]",  "[401,450]",
137         "[351,400]",  "[301,350]", "[251,300]", "[201,250]", "[200,200]",  "[151,199]",
138         "[101,150]",  "[51,100]",  "[1,50]",    "[0,0]",     "[-1000,-1]",
139 };
140 
file_exists(const char * const path)141 static bool file_exists(const char *const path) {
142     struct stat sbuf;
143 
144     return (stat(path, &sbuf) == 0);
145 }
146 
checkKernelMMMetricSupport()147 bool MmMetricsReporter::checkKernelMMMetricSupport() {
148     const char *const require_all[] = {
149             kVmstatPath,
150             kGpuTotalPages,
151             kPixelStatMm,
152     };
153     const char *const require_one_ion_total_pools_path[] = {
154             kIonTotalPoolsPath,
155             kIonTotalPoolsPathForLegacy,
156     };
157 
158     bool err_require_all = false;
159     for (auto &path : require_all) {
160         if (!file_exists(path)) {
161             ALOGE("MM Metrics not supported - %s not found.", path);
162             err_require_all = true;
163         }
164     }
165     if (err_require_all) {
166         ALOGE("MM Metrics not supported: Some required sysfs nodes not found.");
167     }
168 
169     bool err_require_one_ion_total_pools_path = true;
170     for (auto &path : require_one_ion_total_pools_path) {
171         if (file_exists(path)) {
172             err_require_one_ion_total_pools_path = false;
173             break;
174         }
175     }
176     if (err_require_one_ion_total_pools_path) {
177         ALOGI("MM Metrics not supported - No IonTotalPools paths were found.");
178     }
179 
180     return !err_require_all && !err_require_one_ion_total_pools_path;
181 }
182 
checkKernelOomUsageSupport()183 bool MmMetricsReporter::checkKernelOomUsageSupport() {
184     if (!file_exists(kProcVendorMmUsageByOom)) {
185         ALOGE("Oom score grouped memory usage metrics not supported"
186               " - %s not found.",
187               kProcVendorMmUsageByOom);
188         return false;
189     }
190     return true;
191 }
192 
checkKernelGcmaSupport()193 bool MmMetricsReporter::checkKernelGcmaSupport() {
194     std::string base_path(kGcmaBasePath);
195 
196     for (auto parr : {kGcmaHourlySimpleKnobs, kGcmaHourlyHistogramKnobs}) {
197         for (auto p : kGcmaHourlySimpleKnobs) {
198             if (!file_exists((base_path + '/' + p).c_str())) {
199                 ALOGE("kernel GCMA metrics not supported- %s not found.", p);
200                 return false;
201             }
202         }
203     }
204     return true;
205 }
206 
MmMetricsReporter()207 MmMetricsReporter::MmMetricsReporter()
208     : kVmstatPath("/proc/vmstat"),
209       kIonTotalPoolsPath("/sys/kernel/dma_heap/total_pools_kb"),
210       kIonTotalPoolsPathForLegacy("/sys/kernel/ion/total_pools_kb"),
211       kGpuTotalPages("/sys/kernel/pixel_stat/gpu/mem/total_page_count"),
212       kCompactDuration("/sys/kernel/pixel_stat/mm/compaction/mm_compaction_duration"),
213       kDirectReclaimBasePath("/sys/kernel/pixel_stat/mm/vmscan/direct_reclaim"),
214       kPixelStatMm("/sys/kernel/pixel_stat/mm"),
215       kMeminfoPath("/proc/meminfo"),
216       kProcStatPath("/proc/stat"),
217       kProcVendorMmUsageByOom("/proc/vendor_mm/memory_usage_by_oom_score"),
218       kGcmaBasePath("/sys/kernel/vendor_mm/gcma"),
219       prev_compaction_duration_(kNumCompactionDurationPrevMetrics, 0),
220       prev_direct_reclaim_(kNumDirectReclaimPrevMetrics, 0) {
221     ker_mm_metrics_support_ = checkKernelMMMetricSupport();
222     ker_oom_usage_support_ = checkKernelOomUsageSupport();
223     ker_gcma_support_ = checkKernelGcmaSupport();
224 }
225 
ReadFileToUint(const std::string & path,uint64_t * val)226 bool MmMetricsReporter::ReadFileToUint(const std::string &path, uint64_t *val) {
227     std::string file_contents;
228 
229     if (!ReadFileToString(path, &file_contents)) {
230         // Don't print this log if the file doesn't exist, since logs will be printed repeatedly.
231         if (errno != ENOENT) {
232             ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno));
233         }
234         return false;
235     } else {
236         file_contents = android::base::Trim(file_contents);
237         if (!android::base::ParseUint(file_contents, val)) {
238             ALOGI("Unable to convert %s to uint - %s", path.c_str(), strerror(errno));
239             return false;
240         }
241     }
242     return true;
243 }
244 
245 /*
246  * This function reads whole file and parses tokens separated by <delim> into
247  * long integers.  Useful for direct reclaim & compaction duration sysfs nodes.
248  * Data write is using all or none policy: It will not write partial data unless
249  * all data values are good.
250  *
251  * path: file to open/read
252  * data: where to store the results
253  * start_idx: index into data[] where to start saving the results
254  * delim: delimiters separating different longs
255  * skip: how many resulting longs to skip before saving
256  * nonnegtive: set to true to validate positive numbers
257  *
258  * Return value: number of longs actually stored on success.  negative
259  *               error codes on errors.
260  */
ReadFileToLongs(const std::string & path,std::vector<long> * data,int start_idx,const char * delim,int skip,bool nonnegative=false)261 static int ReadFileToLongs(const std::string &path, std::vector<long> *data, int start_idx,
262                            const char *delim, int skip, bool nonnegative = false) {
263     std::vector<long> out;
264     enum { err_read_file = -1, err_parse = -2 };
265     std::string file_contents;
266 
267     if (!ReadFileToString(path, &file_contents)) {
268         // Don't print this log if the file doesn't exist, since logs will be printed repeatedly.
269         if (errno != ENOENT) {
270             ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno));
271         }
272         return err_read_file;
273     }
274 
275     file_contents = android::base::Trim(file_contents);
276     std::vector<std::string> words = android::base::Tokenize(file_contents, delim);
277     if (words.size() == 0)
278         return 0;
279 
280     for (auto &w : words) {
281         if (skip) {
282             skip--;
283             continue;
284         }
285         long tmp;
286         if (!android::base::ParseInt(w, &tmp) || (nonnegative && tmp < 0))
287             return err_parse;
288         out.push_back(tmp);
289     }
290 
291     int min_size = std::max(static_cast<int>(out.size()) + start_idx, 0);
292     if (min_size > data->size())
293         data->resize(min_size);
294     std::copy(out.begin(), out.end(), data->begin() + start_idx);
295 
296     return out.size();
297 }
298 
299 /*
300  * This function calls ReadFileToLongs, and checks the expected number
301  * of long integers read.  Useful for direct reclaim & compaction duration
302  * sysfs nodes.
303  *
304  *  path: file to open/read
305  *  data: where to store the results
306  *  start_idx: index into data[] where to start saving the results
307  *  delim: delimiters separating different longs
308  *  skip: how many resulting longs to skip before saving
309  *  expected_num: number of expected longs to be read.
310  *  nonnegtive: set to true to validate positive numbers
311  *
312  *  Return value: true if successfully get expected number of long values.
313  *                otherwise false.
314  */
ReadFileToLongsCheck(const std::string & path,std::vector<long> * store,int start_idx,const char * delim,int skip,int expected_num,bool nonnegative=false)315 static inline bool ReadFileToLongsCheck(const std::string &path, std::vector<long> *store,
316                                         int start_idx, const char *delim, int skip,
317                                         int expected_num, bool nonnegative = false) {
318     int num = ReadFileToLongs(path, store, start_idx, delim, skip, nonnegative);
319 
320     if (num == expected_num)
321         return true;
322 
323     int last_idx = std::min(start_idx + expected_num, static_cast<int>(store->size()));
324     std::fill(store->begin() + start_idx, store->begin() + last_idx, -1);
325 
326     return false;
327 }
328 
reportVendorAtom(const std::shared_ptr<IStats> & stats_client,int atom_id,const std::vector<VendorAtomValue> & values,const std::string & atom_name)329 bool MmMetricsReporter::reportVendorAtom(const std::shared_ptr<IStats> &stats_client, int atom_id,
330                                          const std::vector<VendorAtomValue> &values,
331                                          const std::string &atom_name) {
332     // Send vendor atom to IStats HAL
333     VendorAtom event = {.reverseDomainName = "",
334                         .atomId = atom_id,
335                         .values = std::move(values)};
336     const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
337     if (!ret.isOk()) {
338         ALOGE("Unable to report %s to Stats service", atom_name.c_str());
339         return false;
340     }
341     return true;
342 }
343 
344 /**
345  * Parse sysfs node in Name/Value pair form, including /proc/vmstat and /proc/meminfo
346  * Name could optionally with a colon (:) suffix (will be removed to produce the output map),
347  * extra columns (e.g. 3rd column 'kb' for /proc/meminfo) will be discarded.
348  * Return value: a map containing the pairs of {field_string, data}.
349  */
readSysfsNameValue(const std::string & path)350 std::map<std::string, uint64_t> MmMetricsReporter::readSysfsNameValue(const std::string &path) {
351     std::string file_contents;
352     std::map<std::string, uint64_t> metrics;
353 
354     if (!ReadFileToString(path, &file_contents)) {
355         ALOGE("Unable to read vmstat from %s, err: %s", path.c_str(), strerror(errno));
356         return metrics;
357     }
358 
359     std::istringstream data(file_contents);
360     std::string line;
361     int line_num = 0;
362 
363     while (std::getline(data, line)) {
364         line_num++;
365         std::vector<std::string> words = android::base::Tokenize(line, " ");
366 
367         uint64_t i;
368         if (words.size() < 2 || !android::base::ParseUint(words[1], &i)) {
369             ALOGE("File %s corrupted at line %d", path.c_str(), line_num);
370             metrics.clear();
371             break;
372         }
373 
374         if (words[0][words[0].length() - 1] == ':')
375             words[0].pop_back();
376 
377         metrics[words[0]] = i;
378     }
379 
380     return metrics;
381 }
382 
383 /**
384  * Parse the output of /proc/stat or any sysfs node having the same output format.
385  * The map containing pairs of {field_name, array (vector) of values} will be returned.
386  */
readProcStat(const std::string & path)387 std::map<std::string, std::vector<uint64_t>> MmMetricsReporter::readProcStat(
388         const std::string &path) {
389     std::map<std::string, std::vector<uint64_t>> fields;
390     std::string content;
391     bool got_err = false;
392 
393     // Use ReadFileToString for convenient file reading
394     if (!android::base::ReadFileToString(path, &content)) {
395         ALOGE("Error: Unable to open %s", path.c_str());
396         return fields;  // Return empty map on error
397     }
398 
399     // Split the file content into lines
400     std::vector<std::string> lines = android::base::Split(content, "\n");
401 
402     for (const auto &line : lines) {
403         std::vector<std::string> tokens = android::base::Tokenize(line, " ");
404         if (tokens.empty()) {
405             continue;  // Skip empty lines
406         }
407 
408         const std::string &field_name = tokens[0];
409 
410         // Check for duplicates
411         if (fields.find(field_name) != fields.end()) {
412             ALOGE("Duplicate field found: %s", field_name.c_str());
413             got_err = true;
414             goto exit_loop;
415         }
416 
417         std::vector<uint64_t> values;
418         for (size_t i = 1; i < tokens.size(); ++i) {
419             uint64_t value;
420             if (!android::base::ParseUint(tokens[i], &value)) {
421                 ALOGE("Invalid field value format in line: %s", line.c_str());
422                 got_err = true;
423                 goto exit_loop;
424             }
425             values.push_back(value);
426         }
427         fields[field_name] = values;
428     }
429 
430 exit_loop:
431     if (got_err) {
432         fields.clear();
433     }
434     return fields;
435 }
436 
getIonTotalPools()437 uint64_t MmMetricsReporter::getIonTotalPools() {
438     uint64_t res;
439 
440     if (!ReadFileToUint(getSysfsPath(kIonTotalPoolsPathForLegacy), &res) || (res == 0)) {
441         if (!ReadFileToUint(getSysfsPath(kIonTotalPoolsPath), &res)) {
442             return 0;
443         }
444     }
445 
446     return res;
447 }
448 
449 /**
450  * Collect GPU memory from kGpuTotalPages and return the total number of 4K page.
451  */
getGpuMemory()452 uint64_t MmMetricsReporter::getGpuMemory() {
453     uint64_t gpu_size = 0;
454 
455     if (!ReadFileToUint(getSysfsPath(kGpuTotalPages), &gpu_size)) {
456         return 0;
457     }
458     return gpu_size;
459 }
460 
461 /**
462  * fillAtomValues() is used to copy Mm metrics to values
463  * metrics_info: This is a vector of MmMetricsInfo {field_string, atom_key, update_diff}
464  *               field_string is used to get the data from mm_metrics.
465  *               atom_key is the position where the data should be put into values.
466  *               update_diff will be true if this is an accumulated data.
467  *               metrics_info may have multiple entries with the same atom_key,
468  *               e.g. workingset_refault and workingset_refault_file.
469  * mm_metrics: This map contains pairs of {field_string, cur_value} collected
470  *             from /proc/vmstat or the sysfs for the pixel specific metrics.
471  *             e.g. {"nr_free_pages", 200000}
472  *             Some data in mm_metrics are accumulated, e.g. pswpin.
473  *             We upload the difference instead of the accumulated value
474  *             when update_diff of the field is true.
475  * prev_mm_metrics: The pointer to the metrics we collected last time.
476  *                  nullptr if all fields are snapshot values (i.e. won't need
477  *                  to upload diff, i.e. entry.update_diff == false for all fields.)
478  * atom_values: The atom values that will be reported later.
479  * return value: true on success, false on error.
480  */
fillAtomValues(const std::vector<MmMetricsInfo> & metrics_info,const std::map<std::string,uint64_t> & mm_metrics,std::map<std::string,uint64_t> * prev_mm_metrics,std::vector<VendorAtomValue> * atom_values)481 bool MmMetricsReporter::fillAtomValues(const std::vector<MmMetricsInfo> &metrics_info,
482                                        const std::map<std::string, uint64_t> &mm_metrics,
483                                        std::map<std::string, uint64_t> *prev_mm_metrics,
484                                        std::vector<VendorAtomValue> *atom_values) {
485     bool err = false;
486     VendorAtomValue tmp;
487     tmp.set<VendorAtomValue::longValue>(0);
488     // resize atom_values to add all fields defined in metrics_info
489     int max_idx = 0;
490     for (auto &entry : metrics_info) {
491         if (max_idx < entry.atom_key)
492             max_idx = entry.atom_key;
493     }
494     unsigned int size = max_idx - kVendorAtomOffset + 1;
495     if (atom_values->size() < size)
496         atom_values->resize(size, tmp);
497 
498     for (auto &entry : metrics_info) {
499         int atom_idx = entry.atom_key - kVendorAtomOffset;
500 
501         auto data = mm_metrics.find(entry.name);
502         if (data == mm_metrics.end())
503             continue;
504 
505         uint64_t cur_value = data->second;
506         uint64_t prev_value = 0;
507         if (prev_mm_metrics == nullptr && entry.update_diff) {
508             // Bug: We need previous saved metrics to calculate the difference.
509             ALOGE("FIX ME: shouldn't reach here: "
510                   "Diff upload required by prev_mm_metrics not provided.");
511             err = true;
512             continue;
513         } else if (entry.update_diff) {
514             // reaching here implies: prev_mm_metrics != nullptr
515             auto prev_data = prev_mm_metrics->find(entry.name);
516             if (prev_data != prev_mm_metrics->end()) {
517                 prev_value = prev_data->second;
518             }
519             // else: implies it's the 1st data: nothing to do, since prev_value already = 0
520         }
521 
522         tmp.set<VendorAtomValue::longValue>(cur_value - prev_value);
523         (*atom_values)[atom_idx] = tmp;
524     }
525     if (prev_mm_metrics && !err) {
526         (*prev_mm_metrics) = mm_metrics;
527     }
528     return !err;
529 }
530 
531 /*
532  * offset -1 means to get the sum of the whole mapped array
533  * otherwise get the array value at the offset.
534  * return value: true for success (got the value), else false
535  */
getValueFromParsedProcStat(const std::map<std::string,std::vector<uint64_t>> pstat,const std::string & name,int offset,uint64_t * output)536 bool MmMetricsReporter::getValueFromParsedProcStat(
537         const std::map<std::string, std::vector<uint64_t>> pstat, const std::string &name,
538         int offset, uint64_t *output) {
539     static bool log_once = false;
540 
541     if (offset < -1) {
542         if (!log_once) {
543             log_once = true;
544             ALOGE("Bug: bad offset %d for entry %s", offset, name.c_str());
545         }
546         return false;
547     }
548 
549     // the mapped array not found
550     auto itr = pstat.find(name);
551     if (itr == pstat.end()) {
552         return false;
553     }
554 
555     const std::vector<uint64_t> &values = itr->second;
556 
557     if (values.size() == 0) {
558         return false;
559     }
560 
561     if (offset >= 0 && offset >= values.size()) {
562         return false;
563     }
564 
565     if (offset != -1) {
566         *output = values.at(offset);
567         return true;
568     }
569 
570     *output = std::accumulate(values.begin(), values.end(), 0);
571     return true;
572 }
573 
574 /**
575  *  metrics_info: see struct  ProcStatMetricsInfo for detail
576  *
577  *  /proc/stat was already read and parsed by readProcStat().
578  *  The parsed results are stored in <cur_pstat>
579  *  The previous parsed results are stored in <prev_pstat> (in case the diff value is asked)
580  *
581  *  A typical /proc/stat line looks like
582  *      cpu  258 132 521 30 15 28 16
583  *  The parsed results are a map mapping the name (i.e. the 1st token in a /proc/stat line)
584  *  to an array of numbers.
585  *
586  *  Each element (entry) in metrics_info tells us where/how to find the corresponding
587  *  value for that entry.  e.g.
588  *   // name, offset,   atom_key,                                update_diff
589  *    {"cpu", -1,  PixelMmMetricsPerDay::kCpuTotalTimeFieldNumber, true      }
590  *  This is the entry "cpu total time".
591  *  We need to look at the "cpu" line from /proc/stat (or from the parsed result, i.e. map)
592  *  -1 is the offset for the value in the line.  Normally it is a zero-based
593  *  number, from that we know which value to get from the array.
594  *  -1 is special: it does not mean one specific offset but to sum-up everything in the array.
595  *
596  *  The final 'true' ask us to create a diff with the previously stored value
597  *  for this same entry (e.g. cpu total time).
598  *
599  *  PixelMmMetricsPerDay::kCpuTotalTimeFieldNumber (.atom_key) indicate the offset
600  *  in the atom field value array (i.e. <atom_values>) where we need to fill in the value.
601  */
fillProcStat(const std::vector<ProcStatMetricsInfo> & metrics_info,const std::map<std::string,std::vector<uint64_t>> & cur_pstat,std::map<std::string,std::vector<uint64_t>> * prev_pstat,std::vector<VendorAtomValue> * atom_values)602 bool MmMetricsReporter::fillProcStat(const std::vector<ProcStatMetricsInfo> &metrics_info,
603                                      const std::map<std::string, std::vector<uint64_t>> &cur_pstat,
604                                      std::map<std::string, std::vector<uint64_t>> *prev_pstat,
605                                      std::vector<VendorAtomValue> *atom_values) {
606     bool is_success = true;
607     for (const auto &entry : metrics_info) {
608         int atom_idx = entry.atom_key - kVendorAtomOffset;
609         uint64_t cur_value;
610         uint64_t prev_value = 0;
611 
612         if (atom_idx < 0) {
613             // Reaching here means the data definition (.atom_key) has a problem.
614             ALOGE("Bug: should not reach here: index to fill is negative for "
615                   "entry %s offset %d",
616                   entry.name.c_str(), entry.offset);
617             is_success = false;
618             break;
619         }
620 
621         if (prev_pstat == nullptr && entry.update_diff) {
622             // Reaching here means you need to provide prev_pstat or define false for .update_diff
623             ALOGE("Bug: should not reach here: asking for diff without providing "
624                   " the previous data for entry %s offset %d",
625                   entry.name.c_str(), entry.offset);
626             is_success = false;
627             break;
628         }
629 
630         // Find the field value from the current read
631         if (!getValueFromParsedProcStat(cur_pstat, entry.name, entry.offset, &cur_value)) {
632             // Metric not found
633             ALOGE("Metric '%s' not found in ProcStat", entry.name.c_str());
634             printf("Error: Metric '%s' not found in ProcStat", entry.name.c_str());
635             is_success = false;
636             break;
637         }
638 
639         // Find the field value from the previous read, if we need diff value
640         if (entry.update_diff) {
641             // prev_value won't change (0) if not found. So, no need to check return status.
642             getValueFromParsedProcStat(*prev_pstat, entry.name, entry.offset, &prev_value);
643         }
644 
645         // Fill the atom_values array
646         VendorAtomValue tmp;
647         tmp.set<VendorAtomValue::longValue>((int64_t)cur_value - prev_value);
648         (*atom_values)[atom_idx] = tmp;
649     }
650 
651     if (!is_success) {
652         prev_pstat->clear();
653         return false;
654     }
655 
656     // Update prev_pstat
657     if (prev_pstat != nullptr) {
658         *prev_pstat = cur_pstat;
659     }
660     return true;
661 }
662 
aggregatePixelMmMetricsPer5Min()663 void MmMetricsReporter::aggregatePixelMmMetricsPer5Min() {
664     aggregatePressureStall();
665 }
666 
logPixelMmMetricsPerHour(const std::shared_ptr<IStats> & stats_client)667 void MmMetricsReporter::logPixelMmMetricsPerHour(const std::shared_ptr<IStats> &stats_client) {
668     std::vector<VendorAtomValue> values = genPixelMmMetricsPerHour();
669 
670     if (values.size() != 0) {
671         // Send vendor atom to IStats HAL
672         reportVendorAtom(stats_client, PixelAtoms::Atom::kPixelMmMetricsPerHour, values,
673                          "PixelMmMetricsPerHour");
674     }
675 }
676 
logGcmaPerHour(const std::shared_ptr<IStats> & stats_client)677 void MmMetricsReporter::logGcmaPerHour(const std::shared_ptr<IStats> &stats_client) {
678     std::vector<VendorAtomValue> values = readAndGenGcmaPerHour();
679 
680     if (values.size() != 0) {
681         reportVendorAtom(stats_client, PixelAtoms::Atom::kMmGcmaSnapshot, values, "MmGcmaSnapshot");
682     }
683 }
684 
logMmProcessUsageByOomGroupSnapshot(const std::shared_ptr<IStats> & stats_client)685 void MmMetricsReporter::logMmProcessUsageByOomGroupSnapshot(
686         const std::shared_ptr<IStats> &stats_client) {
687     if (!OomUsageSupoorted())
688         return;
689 
690     std::vector<MmMetricsReporter::OomGroupMemUsage> ogusage;
691     if (!readMmProcessUsageByOomGroup(&ogusage))
692         return;
693 
694     for (const auto &m : ogusage) {
695         std::vector<VendorAtomValue> values = genMmProcessUsageByOomGroupSnapshotAtom(m);
696         reportVendorAtom(stats_client, PixelAtoms::Atom::kMmProcessUsageByOomGroupSnapshot, values,
697                          "MmProcessUsageByOomGroup");
698     }
699 }
700 
genPixelMmMetricsPerHour()701 std::vector<VendorAtomValue> MmMetricsReporter::genPixelMmMetricsPerHour() {
702     if (!MmMetricsSupported())
703         return std::vector<VendorAtomValue>();
704 
705     std::map<std::string, uint64_t> vmstat = readSysfsNameValue(getSysfsPath(kVmstatPath));
706     if (vmstat.size() == 0)
707         return std::vector<VendorAtomValue>();
708 
709     std::map<std::string, uint64_t> meminfo = readSysfsNameValue(getSysfsPath(kMeminfoPath));
710     if (meminfo.size() == 0)
711         return std::vector<VendorAtomValue>();
712 
713     uint64_t ion_total_pools = getIonTotalPools();
714     uint64_t gpu_memory = getGpuMemory();
715 
716     // allocate enough values[] entries for the metrics.
717     VendorAtomValue tmp;
718     tmp.set<VendorAtomValue::longValue>(0);
719     int last_value_index = PixelMmMetricsPerHour::kDmabufKbFieldNumber - kVendorAtomOffset;
720     std::vector<VendorAtomValue> values(last_value_index + 1, tmp);
721 
722     fillAtomValues(kMmMetricsPerHourInfo, vmstat, &prev_hour_vmstat_, &values);
723     fillAtomValues(kMmMetricsPerHourInfo, meminfo, nullptr, &values);
724     tmp.set<VendorAtomValue::longValue>(ion_total_pools);
725     values[PixelMmMetricsPerHour::kIonTotalPoolsFieldNumber - kVendorAtomOffset] = tmp;
726     tmp.set<VendorAtomValue::longValue>(gpu_memory);
727     values[PixelMmMetricsPerHour::kGpuMemoryFieldNumber - kVendorAtomOffset] = tmp;
728     fillPressureStallAtom(&values);
729 
730     return values;
731 }
732 
logPixelMmMetricsPerDay(const std::shared_ptr<IStats> & stats_client)733 void MmMetricsReporter::logPixelMmMetricsPerDay(const std::shared_ptr<IStats> &stats_client) {
734     std::vector<VendorAtomValue> values = genPixelMmMetricsPerDay();
735 
736     if (values.size() != 0) {
737         // Send vendor atom to IStats HAL
738         reportVendorAtom(stats_client, PixelAtoms::Atom::kPixelMmMetricsPerDay, values,
739                          "PixelMmMetricsPerDay");
740     }
741 }
742 
logGcmaPerDay(const std::shared_ptr<IStats> & stats_client)743 void MmMetricsReporter::logGcmaPerDay(const std::shared_ptr<IStats> &stats_client) {
744     std::vector<VendorAtomValue> values = readAndGenGcmaPerDay();
745 
746     if (values.size() != 0) {
747         reportVendorAtom(stats_client, PixelAtoms::Atom::kMmGcmaStats, values, "MmGcmaStats");
748     }
749 }
750 
genPixelMmMetricsPerDay()751 std::vector<VendorAtomValue> MmMetricsReporter::genPixelMmMetricsPerDay() {
752     if (!MmMetricsSupported())
753         return std::vector<VendorAtomValue>();
754 
755     std::map<std::string, uint64_t> vmstat = readSysfsNameValue(getSysfsPath(kVmstatPath));
756     if (vmstat.size() == 0)
757         return std::vector<VendorAtomValue>();
758 
759     std::map<std::string, std::vector<uint64_t>> procstat =
760             readProcStat(getSysfsPath(kProcStatPath));
761     if (procstat.size() == 0)
762         return std::vector<VendorAtomValue>();
763 
764     std::vector<long> direct_reclaim;
765     readDirectReclaimStat(&direct_reclaim);
766 
767     std::vector<long> compaction_duration;
768     readCompactionDurationStat(&compaction_duration);
769 
770     bool is_first_atom = (prev_day_vmstat_.size() == 0) ? true : false;
771 
772     // allocate enough values[] entries for the metrics.
773     VendorAtomValue tmp;
774     tmp.set<VendorAtomValue::longValue>(0);
775     int last_value_index = PixelMmMetricsPerDay::kKswapdPageoutRunFieldNumber - kVendorAtomOffset;
776     std::vector<VendorAtomValue> values(last_value_index + 1, tmp);
777 
778     if (!fillAtomValues(kMmMetricsPerDayInfo, vmstat, &prev_day_vmstat_, &values)) {
779         // resets previous read since we reject the current one: so that we will
780         // need two more reads to get a new diff.
781         prev_day_vmstat_.clear();
782         return std::vector<VendorAtomValue>();
783     }
784 
785     std::map<std::string, uint64_t> pixel_vmstat = readSysfsNameValue(
786             getSysfsPath(android::base::StringPrintf("%s/vmstat", kPixelStatMm).c_str()));
787     if (!fillAtomValues(kMmMetricsPerDayInfo, pixel_vmstat, &prev_day_pixel_vmstat_, &values)) {
788         // resets previous read since we reject the current one: so that we will
789         // need two more reads to get a new diff.
790         prev_day_vmstat_.clear();
791         return std::vector<VendorAtomValue>();
792     }
793     fillProcessStime(PixelMmMetricsPerDay::kKswapdStimeClksFieldNumber, "kswapd0",
794                      &prev_kswapd_pid_, &prev_kswapd_stime_, &values);
795     fillProcessStime(PixelMmMetricsPerDay::kKcompactdStimeClksFieldNumber, "kcompactd0",
796                      &prev_kcompactd_pid_, &prev_kcompactd_stime_, &values);
797     fillDirectReclaimStatAtom(direct_reclaim, &values);
798     fillCompactionDurationStatAtom(compaction_duration, &values);
799 
800     if (!fillProcStat(kProcStatInfo, procstat, &prev_procstat_, &values)) {
801         prev_procstat_.clear();
802         return std::vector<VendorAtomValue>();
803     }
804 
805     // Don't report the first atom to avoid big spike in accumulated values.
806     if (is_first_atom) {
807         values.clear();
808     }
809 
810     return values;
811 }
812 
813 /**
814  * Return pid if /proc/<pid>/comm is equal to name, or -1 if not found.
815  */
findPidByProcessName(const std::string & name)816 int MmMetricsReporter::findPidByProcessName(const std::string &name) {
817     std::unique_ptr<DIR, int (*)(DIR *)> dir(opendir("/proc"), closedir);
818     if (!dir)
819         return -1;
820 
821     int pid;
822     while (struct dirent *dp = readdir(dir.get())) {
823         if (dp->d_type != DT_DIR)
824             continue;
825 
826         if (!android::base::ParseInt(dp->d_name, &pid))
827             continue;
828 
829         // Avoid avc denial since pixelstats-vendor doesn't have the permission to access /proc/1
830         if (pid == 1)
831             continue;
832 
833         std::string file_contents;
834         std::string path = android::base::StringPrintf("/proc/%s/comm", dp->d_name);
835         if (!ReadFileToString(path, &file_contents))
836             continue;
837 
838         file_contents = android::base::Trim(file_contents);
839         if (file_contents.compare(name))
840             continue;
841 
842         return pid;
843     }
844     return -1;
845 }
846 
847 /**
848  * Get stime of a process from <path>, i.e. 15th field of <path> = /proc/<pid>/stat
849  * Custom path (base path) could be used to inject data for test codes.
850  */
getStimeByPathAndVerifyName(const std::string & path,const std::string & name)851 int64_t MmMetricsReporter::getStimeByPathAndVerifyName(const std::string &path,
852                                                        const std::string &name) {
853     const int stime_idx = 15;
854     const int name_idx = 2;
855     uint64_t stime;
856     int64_t ret;
857     std::string file_contents;
858     if (!ReadFileToString(path, &file_contents)) {
859         ALOGE("Unable to read %s, err: %s", path.c_str(), strerror(errno));
860         return -1;
861     }
862 
863     std::vector<std::string> data = android::base::Split(file_contents, " ");
864     if (data.size() < stime_idx) {
865         ALOGE("Unable to find stime from %s. size: %zu", path.c_str(), data.size());
866         return -1;
867     }
868 
869     std::string parenthesis_name = std::string("(") + name + ")";
870     if (parenthesis_name.compare(data[name_idx - 1]) != 0) {
871         ALOGE("Mismatched name for process stat: queried %s vs. found %s", parenthesis_name.c_str(),
872               data[name_idx - 1].c_str());
873         return -1;
874     }
875 
876     if (android::base::ParseUint(data[stime_idx - 1], &stime)) {
877         ret = static_cast<int64_t>(stime);
878         return ret < 0 ? -1 : ret;
879     } else {
880         ALOGE("Stime Uint parse fail for process info path %s", path.c_str());
881         return -1;
882     }
883 }
884 
885 // returns /proc/<pid> on success, empty string on failure.
886 // For test: use derived class to return custom path for test data injection.
getProcessStatPath(const std::string & name,int * prev_pid)887 std::string MmMetricsReporter::getProcessStatPath(const std::string &name, int *prev_pid) {
888     if (prev_pid == nullptr) {
889         ALOGE("Should not reach here: prev_pid == nullptr");
890         return "";
891     }
892 
893     int pid = findPidByProcessName(name);
894     if (pid <= 0) {
895         ALOGE("Unable to find pid for %s, err: %s", name.c_str(), strerror(errno));
896         return "";
897     }
898 
899     if (*prev_pid != -1 && pid != *prev_pid)
900         ALOGW("%s pid changed from %d to %d.", name.c_str(), *prev_pid, pid);
901     *prev_pid = pid;
902 
903     return android::base::StringPrintf("/proc/%d/stat", pid);
904 }
905 
906 /**
907  * Find stime of the process and copy it into atom_values
908  * atom_key: Currently, it can only be kKswapdTimeFieldNumber or kKcompactdTimeFieldNumber
909  * name: process name, "kswapd0" or "kcompactd0"
910  * prev_pid: The pid of the process. It would be the pid we found last time,
911  *      or -1 if not found.
912  * prev_stime: The stime of the process collected last time.
913  * atom_values: The atom we will report later.
914  */
fillProcessStime(int atom_key,const std::string & name,int * prev_pid,uint64_t * prev_stime,std::vector<VendorAtomValue> * atom_values)915 void MmMetricsReporter::fillProcessStime(int atom_key, const std::string &name, int *prev_pid,
916                                          uint64_t *prev_stime,
917                                          std::vector<VendorAtomValue> *atom_values) {
918     std::string path;
919     int64_t stime;
920     int64_t stimeDiff;
921 
922     // Find <pid> for executable <name>, and return "/proc/<pid>/stat" path.
923     // Give warning if prev_pid != current pid when prev_pid != -1, which means
924     // <name> at least once died and respawn.
925     path = getProcessStatPath(name, prev_pid);
926 
927     if ((stime = getStimeByPathAndVerifyName(path, name)) < 0) {
928         return;
929     }
930 
931     stimeDiff = stime - *prev_stime;
932     if (stimeDiff < 0) {
933         ALOGE("stime diff for %s < 0: not possible", name.c_str());
934         return;
935     }
936     *prev_stime = stime;
937 
938     int atom_idx = atom_key - kVendorAtomOffset;
939     int size = atom_idx + 1;
940     VendorAtomValue tmp;
941     tmp.set<VendorAtomValue::longValue>(stimeDiff);
942     if (atom_values->size() < size)
943         atom_values->resize(size, tmp);
944     (*atom_values)[atom_idx] = tmp;
945 }
946 
947 /**
948  * Collect CMA metrics from kPixelStatMm/cma/<cma_type>/<metric>
949  * cma_type: CMA heap name
950  * metrics_info: This is a vector of MmMetricsInfo {metric, atom_key, update_diff}.
951  *               Currently, we only collect CMA metrics defined in metrics_info
952  */
readCmaStat(const std::string & cma_type,const std::vector<MmMetricsReporter::MmMetricsInfo> & metrics_info)953 std::map<std::string, uint64_t> MmMetricsReporter::readCmaStat(
954         const std::string &cma_type,
955         const std::vector<MmMetricsReporter::MmMetricsInfo> &metrics_info) {
956     uint64_t file_contents;
957     std::map<std::string, uint64_t> cma_stat;
958     for (auto &entry : metrics_info) {
959         std::string path = android::base::StringPrintf("%s/cma/%s/%s", kPixelStatMm,
960                                                        cma_type.c_str(), entry.name.c_str());
961         if (!ReadFileToUint(getSysfsPath(path.c_str()), &file_contents))
962             continue;
963         cma_stat[entry.name] = file_contents;
964     }
965     return cma_stat;
966 }
967 
968 /**
969  * This function reads compaction duration sysfs node
970  * (/sys/kernel/pixel_stat/mm/compaction/mm_compaction_duration)
971  *
972  * store: vector to save compaction duration info
973  */
readCompactionDurationStat(std::vector<long> * store)974 void MmMetricsReporter::readCompactionDurationStat(std::vector<long> *store) {
975     std::string path(getSysfsPath(kCompactDuration));
976     constexpr int num_metrics = 6;
977 
978     store->resize(num_metrics);
979 
980     int start_idx = 0;
981     int expected_num = num_metrics;
982 
983     if (!ReadFileToLongsCheck(path, store, start_idx, " ", 1, expected_num, true)) {
984         ALOGI("Unable to read %s for the direct reclaim info.", path.c_str());
985     }
986 }
987 
988 /**
989  * This function fills atom values (values) from acquired compaction duration
990  * information from vector store
991  *
992  * store: the already collected (by readCompactionDurationStat()) compaction
993  *        duration information
994  * values: the atom value vector to be filled.
995  */
fillCompactionDurationStatAtom(const std::vector<long> & store,std::vector<VendorAtomValue> * values)996 void MmMetricsReporter::fillCompactionDurationStatAtom(const std::vector<long> &store,
997                                                        std::vector<VendorAtomValue> *values) {
998     // first metric index
999     constexpr int start_idx =
1000             PixelMmMetricsPerDay::kCompactionTotalTimeFieldNumber - kVendorAtomOffset;
1001     constexpr int num_metrics = 6;
1002 
1003     if (!MmMetricsSupported())
1004         return;
1005 
1006     int size = start_idx + num_metrics;
1007     if (values->size() < size)
1008         values->resize(size);
1009 
1010     for (int i = 0; i < num_metrics; i++) {
1011         VendorAtomValue tmp;
1012         if (store[i] == -1) {
1013             tmp.set<VendorAtomValue::longValue>(0);
1014         } else {
1015             tmp.set<VendorAtomValue::longValue>(store[i] - prev_compaction_duration_[i]);
1016             prev_compaction_duration_[i] = store[i];
1017         }
1018         (*values)[start_idx + i] = tmp;
1019     }
1020     prev_compaction_duration_ = store;
1021 }
1022 
1023 /**
1024  * This function reads direct reclaim sysfs node (4 files:
1025  * /sys/kernel/pixel_stat/mm/vmscan/direct_reclaim/<level>/latency_stat,
1026  * where <level> = native, top, visible, other.), and save total time and
1027  * 4 latency information per file. Total (1+4) x 4 = 20 metrics will be
1028  * saved.
1029  *
1030  * store: vector to save direct reclaim info
1031  */
readDirectReclaimStat(std::vector<long> * store)1032 void MmMetricsReporter::readDirectReclaimStat(std::vector<long> *store) {
1033     static const std::string base_path(kDirectReclaimBasePath);
1034     static const std::vector<std::string> dr_levels{"native", "visible", "top", "other"};
1035     static const std::string sysfs_name = "latency_stat";
1036     constexpr int num_metrics_per_file = 5;
1037     int num_file = dr_levels.size();
1038     int num_metrics = num_metrics_per_file * num_file;
1039 
1040     store->resize(num_metrics);
1041     int pass = -1;
1042     for (auto level : dr_levels) {
1043         ++pass;
1044         std::string path = getSysfsPath((base_path + '/' + level + '/' + sysfs_name).c_str());
1045         int start_idx = pass * num_metrics_per_file;
1046         int expected_num = num_metrics_per_file;
1047         if (!ReadFileToLongsCheck(path, store, start_idx, " ", 1, expected_num, true)) {
1048             ALOGI("Unable to read %s for the direct reclaim info.", path.c_str());
1049         }
1050     }
1051 }
1052 
1053 /**
1054  * This function fills atom values (values) from acquired direct reclaim
1055  * information from vector store
1056  *
1057  * store: the already collected (by readDirectReclaimStat()) direct reclaim
1058  *        information
1059  * values: the atom value vector to be filled.
1060  */
fillDirectReclaimStatAtom(const std::vector<long> & store,std::vector<VendorAtomValue> * values)1061 void MmMetricsReporter::fillDirectReclaimStatAtom(const std::vector<long> &store,
1062                                                   std::vector<VendorAtomValue> *values) {
1063     // first metric index
1064     constexpr int start_idx =
1065             PixelMmMetricsPerDay::kDirectReclaimNativeLatencyTotalTimeFieldNumber -
1066             kVendorAtomOffset;
1067 
1068     constexpr int num_metrics = 20; /* num_metrics_per_file * num_file */
1069 
1070     if (!MmMetricsSupported())
1071         return;
1072 
1073     int size = start_idx + num_metrics;
1074     if (values->size() < size)
1075         values->resize(size);
1076 
1077     for (int i = 0; i < num_metrics; i++) {
1078         VendorAtomValue tmp;
1079         tmp.set<VendorAtomValue::longValue>(store[i] - prev_direct_reclaim_[i]);
1080         (*values)[start_idx + i] = tmp;
1081     }
1082     prev_direct_reclaim_ = store;
1083 }
1084 
1085 /**
1086  * This function reads pressure (PSI) files (loop thru all 3 files: cpu, io, and
1087  * memory) and calls the parser to parse and store the metric values.
1088  * Note that each file have two lines (except cpu has one line only): one with
1089  * a leading "full", and the other with a leading "some", showing the category
1090  * for that line.
1091  * A category has 4 metrics, avg10, avg60, avg300, and total.
1092  * i.e. the moving average % of PSI in 10s, 60s, 300s time window plus lastly
1093  * the total stalled time, except that 'cpu' has no 'full' category.
1094  * In total, we have 3 x 2 x 4 - 4 = 24 - 4  = 20 metrics, arranged in
1095  * the order of
1096  *
1097  *    cpu_some_avg<xyz>
1098  *    cpu_some_total
1099  *    io_full_avg<xyz>
1100  *    io_full_total
1101  *    io_some_avg<xyz>
1102  *    io_some_total
1103  *    mem_full_avg<xyz>
1104  *    mem_full_total
1105  *    mem_some_avg<xyz>
1106  *    mem_some_total
1107  *
1108  *    where <xyz>=10, 60, 300 in the order as they appear.
1109  *
1110  *    Note that for those avg values (i.e.  <abc>_<def>_avg<xyz>), they
1111  *    are in percentage with 2-decimal digit accuracy.  We will use an
1112  *    integer in 2-decimal fixed point format to represent the values.
1113  *    i.e. value x 100, or to cope with floating point errors,
1114  *         floor(value x 100 + 0.5)
1115  *
1116  *    In fact, in newer kernels, "cpu" PSI has no "full" category.  Some
1117  *    old kernel has them all zeros, to keep backward compatibility.  The
1118  *    parse function called by this function is able to detect and ignore
1119  *    the "cpu, full" category.
1120  *
1121  *    sample pressure stall files:
1122  *    /proc/pressure # cat cpu
1123  *    some avg10=2.93 avg60=3.17 avg300=3.15 total=94628150260
1124  *    /proc/pressure # cat io
1125  *    some avg10=1.06 avg60=1.15 avg300=1.18 total=37709873805
1126  *    full avg10=1.06 avg60=1.10 avg300=1.11 total=36592322936
1127  *    /proc/pressure # cat memory
1128  *    some avg10=0.00 avg60=0.00 avg300=0.00 total=29705314
1129  *    full avg10=0.00 avg60=0.00 avg300=0.00 total=17234456
1130  *
1131  *    PSI information definitions could be found at
1132  *    https://www.kernel.org/doc/html/latest/accounting/psi.html
1133  *
1134  * basePath: the base path to the pressure stall information
1135  * store: pointer to the vector to store the 20 metrics in the mentioned
1136  *        order
1137  */
readPressureStall(const std::string & basePath,std::vector<long> * store)1138 void MmMetricsReporter::readPressureStall(const std::string &basePath, std::vector<long> *store) {
1139     constexpr int kTypeIdxCpu = 0;
1140 
1141     // Callers should have already prepared this, but we resize it here for safety
1142     store->resize(kPsiNumAllMetrics);
1143     std::fill(store->begin(), store->end(), -1);
1144 
1145     // To make the process unified, we prepend an imaginary "cpu + full"
1146     // type-category combination.  Now, each file (cpu, io, memnry) contains
1147     // two categories, i.e. "full" and "some".
1148     // Each category has <kPsiNumNames> merics and thus need that many entries
1149     // to store them, except that the first category (the imaginary one) do not
1150     // need any storage. So we set the save index for the 1st file ("cpu") to
1151     // -kPsiNumNames.
1152     int file_save_idx = -kPsiNumNames;
1153 
1154     // loop thru all pressure stall files: cpu, io, memory
1155     for (int type_idx = 0; type_idx < kPsiNumFiles;
1156          ++type_idx, file_save_idx += kPsiMetricsPerFile) {
1157         std::string file_contents;
1158         std::string path = getSysfsPath(basePath + '/' + kPsiTypes[type_idx]);
1159 
1160         if (!ReadFileToString(path, &file_contents)) {
1161             // Don't print this log if the file doesn't exist, since logs will be printed
1162             // repeatedly.
1163             if (errno != ENOENT)
1164                 ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno));
1165             goto err_out;
1166         }
1167         if (!MmMetricsReporter::parsePressureStallFileContent(type_idx == kTypeIdxCpu,
1168                                                               file_contents, store, file_save_idx))
1169             goto err_out;
1170     }
1171     return;
1172 
1173 err_out:
1174     std::fill(store->begin(), store->end(), -1);
1175 }
1176 
1177 /*
1178  * This function parses a pressure stall file, which contains two
1179  * lines, i.e. the "full", and "some" lines, except that the 'cpu' file
1180  * contains only one line ("some"). Refer to the function comments of
1181  * readPressureStall() for pressure stall file format.
1182  *
1183  * For old kernel, 'cpu' file might contain an extra line for "full", which
1184  * will be ignored.
1185  *
1186  * is_cpu: Is the data from the file 'cpu'
1187  * lines: the file content
1188  * store: the output vector to hold the parsed data.
1189  * file_save_idx: base index to start saving 'store' vector for this file.
1190  *
1191  * Return value: true on success, false otherwise.
1192  */
parsePressureStallFileContent(bool is_cpu,const std::string & lines,std::vector<long> * store,int file_save_idx)1193 bool MmMetricsReporter::parsePressureStallFileContent(bool is_cpu, const std::string &lines,
1194                                                       std::vector<long> *store, int file_save_idx) {
1195     constexpr int kNumOfWords = 5;  // expected number of words separated by spaces.
1196     constexpr int kCategoryFull = 0;
1197 
1198     std::istringstream data(lines);
1199     std::string line;
1200 
1201     while (std::getline(data, line)) {
1202         int category_idx = 0;
1203 
1204         line = android::base::Trim(line);
1205         std::vector<std::string> words = android::base::Tokenize(line, " ");
1206         if (words.size() != kNumOfWords) {
1207             ALOGE("PSI parse fail: num of words = %d != expected %d",
1208                   static_cast<int>(words.size()), kNumOfWords);
1209             return false;
1210         }
1211 
1212         // words[0] should be either "full" or "some", the category name.
1213         for (auto &cat : kPsiCategories) {
1214             if (words[0].compare(cat) == 0)
1215                 break;
1216             ++category_idx;
1217         }
1218         if (category_idx == kPsiNumCategories) {
1219             ALOGE("PSI parse fail: unknown category %s", words[0].c_str());
1220             return false;
1221         }
1222 
1223         // skip (cpu, full) combination.
1224         if (is_cpu && category_idx == kCategoryFull) {
1225             ALOGI("kernel: old PSI sysfs node.");
1226             continue;
1227         }
1228 
1229         // Now we have separated words in a vector, e.g.
1230         // ["some", "avg10=2.93", "avg60=3.17", "avg300=3.15",  total=94628150260"]
1231         // call parsePressureStallWords to parse them.
1232         int line_save_idx = file_save_idx + category_idx * kPsiNumNames;
1233         if (!parsePressureStallWords(words, store, line_save_idx))
1234             return false;
1235     }
1236     return true;
1237 }
1238 
1239 // This function parses the already split words, e.g.
1240 // ["some", "avg10=0.00", "avg60=0.00", "avg300=0.00", "total=29705314"],
1241 // from a line (category) in a pressure stall file.
1242 //
1243 // words: the split words in the form of "name=value"
1244 // store: the output vector
1245 // line_save_idx: the base start index to save in vector for this line (category)
1246 //
1247 // Return value: true on success, false otherwise.
parsePressureStallWords(const std::vector<std::string> & words,std::vector<long> * store,int line_save_idx)1248 bool MmMetricsReporter::parsePressureStallWords(const std::vector<std::string> &words,
1249                                                 std::vector<long> *store, int line_save_idx) {
1250     // Skip the first word, which is already parsed by the caller.
1251     // All others are value pairs in "name=value" form.
1252     // e.g. ["some", "avg10=0.00", "avg60=0.00", "avg300=0.00", "total=29705314"]
1253     // "some" is skipped.
1254     for (int i = 1; i < words.size(); ++i) {
1255         std::vector<std::string> metric = android::base::Tokenize(words[i], "=");
1256         if (metric.size() != 2) {
1257             ALOGE("%s: parse error (name=value) @ idx %d", __FUNCTION__, i);
1258             return false;
1259         }
1260         if (!MmMetricsReporter::savePressureMetrics(metric[0], metric[1], store, line_save_idx))
1261             return false;
1262     }
1263     return true;
1264 }
1265 
1266 // This function parses one value pair in "name=value" format, and depending on
1267 // the name, save to its proper location in the store vector.
1268 // name = "avg10" -> save to index base_save_idx.
1269 // name = "avg60" -> save to index base_save_idx + 1.
1270 // name = "avg300" -> save to index base_save_idx + 2.
1271 // name = "total" -> save to index base_save_idx + 3.
1272 //
1273 // name: the metrics name
1274 // value: the metrics value
1275 // store: the output vector
1276 // base_save_idx: the base save index
1277 //
1278 // Return value: true on success, false otherwise.
1279 //
savePressureMetrics(const std::string & name,const std::string & value,std::vector<long> * store,int base_save_idx)1280 bool MmMetricsReporter::savePressureMetrics(const std::string &name, const std::string &value,
1281                                             std::vector<long> *store, int base_save_idx) {
1282     int name_idx = 0;
1283     constexpr int kNameIdxTotal = 3;
1284 
1285     for (auto &mn : kPsiMetricNames) {
1286         if (name.compare(mn) == 0)
1287             break;
1288         ++name_idx;
1289     }
1290     if (name_idx == kPsiNumNames) {
1291         ALOGE("%s: parse error: unknown metric name.", __FUNCTION__);
1292         return false;
1293     }
1294 
1295     long out;
1296     if (name_idx == kNameIdxTotal) {
1297         // 'total' metrics
1298         unsigned long tmp;
1299         if (!android::base::ParseUint(value, &tmp))
1300             out = -1;
1301         else
1302             out = tmp;
1303     } else {
1304         // 'avg' metrics
1305         double d = -1.0;
1306         if (android::base::ParseDouble(value, &d))
1307             out = static_cast<long>(d * 100 + 0.5);
1308         else
1309             out = -1;
1310     }
1311 
1312     if (base_save_idx + name_idx >= store->size()) {
1313         // should never reach here
1314         ALOGE("out of bound access to store[] (src line %d) @ index %d", __LINE__,
1315               base_save_idx + name_idx);
1316         return false;
1317     } else {
1318         (*store)[base_save_idx + name_idx] = out;
1319     }
1320     return true;
1321 }
1322 
1323 /**
1324  * This function reads in the current pressure (PSI) information, and aggregates
1325  * it (except for the "total" information, which will overwrite
1326  * the previous value without aggregation.
1327  *
1328  * data are arranged in the following order, and must comply the order defined
1329  * in the proto:
1330  *
1331  *    // note: these 5 'total' metrics are not aggregated.
1332  *    cpu_some_total
1333  *    io_full_total
1334  *    io_some_total
1335  *    mem_full_total
1336  *    mem_some_total
1337  *
1338  *    //  9 aggregated metrics as above avg<xyz>_<aggregate>
1339  *    //  where <xyz> = 10, 60, 300; <aggregate> = min, max, sum
1340  *    cpu_some_avg10_min
1341  *    cpu_some_avg10_max
1342  *    cpu_some_avg10_sum
1343  *    cpu_some_avg60_min
1344  *    cpu_some_avg60_max
1345  *    cpu_some_avg60_sum
1346  *    cpu_some_avg300_min
1347  *    cpu_some_avg300_max
1348  *    cpu_some_avg300_sum
1349  *
1350  *    // similar 9 metrics as above avg<xyz>_<aggregate>
1351  *    io_full_avg<xyz>_<aggregate>
1352  *
1353  *    // similar 9 metrics as above avg<xyz>_<aggregate>
1354  *    io_some_avg<xyz>_<aggregate>
1355  *
1356  *    // similar 9 metrics as above avg<xyz>_<aggregate>
1357  *    mem_full_avg<xyz>_<aggregate>
1358  *
1359  *    // similar 9 metrics as above avg<xyz>_<aggregate>
1360  *    mem_some_avg<xyz>_<aggregate>
1361  *
1362  * In addition, it increases psi_data_set_count_ by 1 (in order to calculate
1363  * the average from the "_sum" aggregate.)
1364  */
aggregatePressureStall()1365 void MmMetricsReporter::aggregatePressureStall() {
1366     constexpr int kFirstTotalOffset = kPsiNumAvgs;
1367 
1368     if (!MmMetricsSupported())
1369         return;
1370 
1371     std::vector<long> psi(kPsiNumAllMetrics, -1);
1372     readPressureStall(kPsiBasePath, &psi);
1373 
1374     // Pre-check for possible later out of bound error, if readPressureStall()
1375     // decreases the vector size.
1376     // It's for safety only.  The condition should never be true.
1377     if (psi.size() != kPsiNumAllMetrics) {
1378         ALOGE("Wrong psi[] size %d != expected %d after read.", static_cast<int>(psi.size()),
1379               kPsiNumAllMetrics);
1380         return;
1381     }
1382 
1383     // check raw metrics and preventively handle errors: Although we don't expect read sysfs
1384     // node could fail.  Discard all current readings on any error.
1385     for (int i = 0; i < kPsiNumAllMetrics; ++i) {
1386         if (psi[i] == -1) {
1387             ALOGE("Bad data @ psi[%ld] = -1", psi[i]);
1388             goto err_out;
1389         }
1390     }
1391 
1392     // "total" metrics are accumulative: just replace the previous accumulation.
1393     for (int i = 0; i < kPsiNumAllTotals; ++i) {
1394         int psi_idx;
1395 
1396         psi_idx = i * kPsiNumNames + kFirstTotalOffset;
1397         if (psi_idx >= psi.size()) {
1398             // should never reach here
1399             ALOGE("out of bound access to psi[] (src line %d) @ index %d", __LINE__, psi_idx);
1400             goto err_out;
1401         } else {
1402             psi_total_[i] = psi[psi_idx];
1403         }
1404     }
1405 
1406     // "avg" metrics will be aggregated to min, max and sum
1407     // later on, the sum will be divided by psi_data_set_count_ to get the average.
1408     int aggr_idx;
1409     aggr_idx = 0;
1410     for (int psi_idx = 0; psi_idx < kPsiNumAllMetrics; ++psi_idx) {
1411         if (psi_idx % kPsiNumNames == kFirstTotalOffset)
1412             continue;  // skip 'total' metrics, already processed.
1413 
1414         if (aggr_idx + 3 > kPsiNumAllUploadAvgMetrics) {
1415             // should never reach here
1416             ALOGE("out of bound access to psi_aggregated_[] (src line %d) @ index %d ~ %d",
1417                   __LINE__, aggr_idx, aggr_idx + 2);
1418             return;  // give up avgs, but keep totals (so don't go err_out
1419         }
1420 
1421         long value = psi[psi_idx];
1422         if (psi_data_set_count_ == 0) {
1423             psi_aggregated_[aggr_idx++] = value;
1424             psi_aggregated_[aggr_idx++] = value;
1425             psi_aggregated_[aggr_idx++] = value;
1426         } else {
1427             psi_aggregated_[aggr_idx++] = std::min(value, psi_aggregated_[aggr_idx]);
1428             psi_aggregated_[aggr_idx++] = std::max(value, psi_aggregated_[aggr_idx]);
1429             psi_aggregated_[aggr_idx++] += value;
1430         }
1431     }
1432     ++psi_data_set_count_;
1433     return;
1434 
1435 err_out:
1436     for (int i = 0; i < kPsiNumAllTotals; ++i) psi_total_[i] = -1;
1437 }
1438 
1439 /**
1440  * This function fills atom values (values) from psi_aggregated_[]
1441  *
1442  * values: the atom value vector to be filled.
1443  */
fillPressureStallAtom(std::vector<VendorAtomValue> * values)1444 void MmMetricsReporter::fillPressureStallAtom(std::vector<VendorAtomValue> *values) {
1445     constexpr int avg_of_avg_offset = 2;
1446     constexpr int total_start_idx =
1447             PixelMmMetricsPerHour::kPsiCpuSomeTotalFieldNumber - kVendorAtomOffset;
1448     constexpr int avg_start_idx = total_start_idx + kPsiNumAllTotals;
1449 
1450     if (!MmMetricsSupported())
1451         return;
1452 
1453     VendorAtomValue tmp;
1454 
1455     // The caller should have setup the correct total size,
1456     // but we check and extend the size when it's too small for safety.
1457     unsigned int min_value_size = total_start_idx + kPsiNumAllUploadMetrics;
1458     if (values->size() < min_value_size)
1459         values->resize(min_value_size);
1460 
1461     // "total" metric
1462     int metric_idx = total_start_idx;
1463     for (int save = 0; save < kPsiNumAllTotals; ++save, ++metric_idx) {
1464         if (psi_data_set_count_ == 0)
1465             psi_total_[save] = -1;  // no data: invalidate the current total
1466 
1467         // A good difference needs a good previous value and a good current value.
1468         if (psi_total_[save] != -1 && prev_psi_total_[save] != -1)
1469             tmp.set<VendorAtomValue::longValue>(psi_total_[save] - prev_psi_total_[save]);
1470         else
1471             tmp.set<VendorAtomValue::longValue>(-1);
1472 
1473         prev_psi_total_[save] = psi_total_[save];
1474         if (metric_idx >= values->size()) {
1475             // should never reach here
1476             ALOGE("out of bound access to value[] for psi-total @ index %d", metric_idx);
1477             goto cleanup;
1478         } else {
1479             (*values)[metric_idx] = tmp;
1480         }
1481     }
1482 
1483     // "avg" metrics -> aggregate to min,  max, and avg of the original avg
1484     metric_idx = avg_start_idx;
1485     for (int save = 0; save < kPsiNumAllUploadAvgMetrics; ++save, ++metric_idx) {
1486         if (psi_data_set_count_) {
1487             if (save % kPsiNumOfAggregatedType == avg_of_avg_offset) {
1488                 // avg of avg
1489                 tmp.set<VendorAtomValue::intValue>(psi_aggregated_[save] / psi_data_set_count_);
1490             } else {
1491                 // min or max of avg
1492                 tmp.set<VendorAtomValue::intValue>(psi_aggregated_[save]);
1493             }
1494         } else {
1495             tmp.set<VendorAtomValue::intValue>(-1);
1496         }
1497         if (metric_idx >= values->size()) {
1498             // should never reach here
1499             ALOGE("out of bound access to value[] for psi-avg @ index %d", metric_idx);
1500             goto cleanup;
1501         } else {
1502             (*values)[metric_idx] = tmp;
1503         }
1504     }
1505 
1506 cleanup:
1507     psi_data_set_count_ = 0;
1508 }
1509 
1510 /**
1511  * This function is to collect CMA metrics and upload them.
1512  * The CMA metrics are collected by readCmaStat(), copied into atom values
1513  * by fillAtomValues(), and then uploaded by reportVendorAtom(). The collected
1514  * metrics will be stored in prev_cma_stat_ and prev_cma_stat_ext_ according
1515  * to its CmaType.
1516  *
1517  * stats_client: The Stats service
1518  * atom_id: The id of atom. It can be PixelAtoms::Atom::kCmaStatus or kCmaStatusExt
1519  * cma_type: The name of CMA heap.
1520  * cma_name_offset: The offset of the field cma_heap_name in CmaStatus or CmaStatusExt
1521  * type_idx: The id of the CMA heap. We add this id in atom values to identify
1522  *           the CMA status data.
1523  * metrics_info: This is a vector of MmMetricsInfo {metric, atom_key, update_diff}.
1524  *               We only collect metrics defined in metrics_info from CMA heap path.
1525  * all_prev_cma_stat: This is the CMA status collected last time.
1526  *                    It is a map containing pairs of {type_idx, cma_stat}, and cma_stat is
1527  *                    a map contains pairs of {metric, cur_value}.
1528  *                    e.g. {CmaType::FARAWIMG, {"alloc_pages_attempts", 100000}, {...}, ....}
1529  *                    is collected from kPixelStatMm/cma/farawimg/alloc_pages_attempts
1530  */
reportCmaStatusAtom(const std::shared_ptr<IStats> & stats_client,int atom_id,const std::string & cma_type,int cma_name_offset,const std::vector<MmMetricsInfo> & metrics_info,std::map<std::string,std::map<std::string,uint64_t>> * all_prev_cma_stat)1531 void MmMetricsReporter::reportCmaStatusAtom(
1532         const std::shared_ptr<IStats> &stats_client, int atom_id, const std::string &cma_type,
1533         int cma_name_offset, const std::vector<MmMetricsInfo> &metrics_info,
1534         std::map<std::string, std::map<std::string, uint64_t>> *all_prev_cma_stat) {
1535     std::map<std::string, uint64_t> cma_stat = readCmaStat(cma_type, metrics_info);
1536     if (!cma_stat.empty()) {
1537         std::vector<VendorAtomValue> values;
1538         VendorAtomValue tmp;
1539         // type is an enum value corresponding to the CMA heap name. Since CMA heap name
1540         // can be added/removed/modified, it would take effort to maintain the mapping table.
1541         // We would like to store CMA heap name directly, so just set type to 0.
1542         tmp.set<VendorAtomValue::intValue>(0);
1543         values.push_back(tmp);
1544 
1545         std::map<std::string, uint64_t> prev_cma_stat;
1546         auto entry = all_prev_cma_stat->find(cma_type);
1547         if (entry != all_prev_cma_stat->end())
1548             prev_cma_stat = entry->second;
1549 
1550         bool is_first_atom = (prev_cma_stat.size() == 0) ? true : false;
1551         fillAtomValues(metrics_info, cma_stat, &prev_cma_stat, &values);
1552 
1553         int size = cma_name_offset - kVendorAtomOffset + 1;
1554         if (values.size() < size) {
1555             values.resize(size, tmp);
1556         }
1557         tmp.set<VendorAtomValue::stringValue>(cma_type);
1558         values[cma_name_offset - kVendorAtomOffset] = tmp;
1559 
1560         (*all_prev_cma_stat)[cma_type] = prev_cma_stat;
1561         if (!is_first_atom)
1562             reportVendorAtom(stats_client, atom_id, values, "CmaStatus");
1563     }
1564 }
1565 
1566 /**
1567  * Find the CMA heap defined in kCmaTypeInfo, and then call reportCmaStatusAtom()
1568  * to collect the CMA metrics from kPixelStatMm/cma/<cma_type> and upload them.
1569  */
logCmaStatus(const std::shared_ptr<IStats> & stats_client)1570 void MmMetricsReporter::logCmaStatus(const std::shared_ptr<IStats> &stats_client) {
1571     if (!MmMetricsSupported())
1572         return;
1573 
1574     std::string cma_root = android::base::StringPrintf("%s/cma", kPixelStatMm);
1575     std::unique_ptr<DIR, int (*)(DIR *)> dir(opendir(cma_root.c_str()), closedir);
1576     if (!dir)
1577         return;
1578 
1579     while (struct dirent *dp = readdir(dir.get())) {
1580         if (dp->d_type != DT_DIR)
1581             continue;
1582 
1583         std::string cma_type(dp->d_name);
1584 
1585         reportCmaStatusAtom(stats_client, PixelAtoms::Atom::kCmaStatus, cma_type,
1586                             CmaStatus::kCmaHeapNameFieldNumber, kCmaStatusInfo, &prev_cma_stat_);
1587         reportCmaStatusAtom(stats_client, PixelAtoms::Atom::kCmaStatusExt, cma_type,
1588                             CmaStatusExt::kCmaHeapNameFieldNumber, kCmaStatusExtInfo,
1589                             &prev_cma_stat_ext_);
1590     }
1591 }
1592 
1593 /*
1594  * parse one line of proc fs "vendor_mm/memory_usage_by_oom_score"
1595  */
1596 std::optional<MmMetricsReporter::OomGroupMemUsage>
parseMmProcessUsageByOomGroupLine(const std::string & line)1597 MmMetricsReporter::parseMmProcessUsageByOomGroupLine(const std::string &line) {
1598     static_assert(OOM_NUM_OF_GROUPS == oom_group_range_names.size(),
1599                   "Error: Number of groups must match.");
1600 
1601     std::vector<std::string> tokens = android::base::Tokenize(line, " \t");
1602     if (tokens.size() < 7) {
1603         ALOGE("Error: Insufficient tokens on line: %s", line.c_str());
1604         return std::nullopt;
1605     }
1606 
1607     MmMetricsReporter::OomGroupMemUsage data;
1608 
1609     // Find the matching group range name and convert it to enumerate:int32_t
1610     auto it = std::find(oom_group_range_names.begin(), oom_group_range_names.end(), tokens[0]);
1611     if (it == oom_group_range_names.end()) {
1612         ALOGE("Error: Unknown group range: %s", tokens[0].c_str());
1613         return std::nullopt;
1614     }
1615     data.oom_group =
1616             static_cast<OomScoreAdjGroup>(std::distance(oom_group_range_names.begin(), it));
1617 
1618     bool success = android::base::ParseInt(tokens[1], &data.nr_task) &&
1619                    android::base::ParseInt(tokens[2], &data.file_rss_kb) &&
1620                    android::base::ParseInt(tokens[3], &data.anon_rss_kb) &&
1621                    android::base::ParseInt(tokens[4], &data.pgtable_kb) &&
1622                    android::base::ParseInt(tokens[5], &data.swap_ents_kb) &&
1623                    android::base::ParseInt(tokens[6], &data.shmem_rss_kb) && data.nr_task >= 0 &&
1624                    data.file_rss_kb >= 0 && data.anon_rss_kb >= 0 && data.pgtable_kb >= 0 &&
1625                    data.swap_ents_kb >= 0 && data.shmem_rss_kb >= 0;
1626 
1627     if (!success) {
1628         ALOGE("Error parsing UInt values on line: %s", line.c_str());
1629         return std::nullopt;
1630     }
1631 
1632     return data;
1633 }
1634 
1635 /*
1636  * read proc fs "vendor_mm/memory_usage_by_oom_score"
1637  */
readMmProcessUsageByOomGroup(std::vector<MmMetricsReporter::OomGroupMemUsage> * ogusage)1638 bool MmMetricsReporter::readMmProcessUsageByOomGroup(
1639         std::vector<MmMetricsReporter::OomGroupMemUsage> *ogusage) {
1640     ogusage->clear();
1641     oom_usage_uid_++;  // Unique ID per read
1642     std::string path = getSysfsPath(kProcVendorMmUsageByOom);
1643 
1644     std::string file_contents;
1645     if (!android::base::ReadFileToString(path, &file_contents)) {
1646         ALOGE("Error reading file: %s", path.c_str());
1647         goto error_out;
1648     }
1649 
1650     for (const auto &line : android::base::Split(file_contents, "\n")) {
1651         if (line.empty() || line[0] == '#')
1652             continue;  // Skip the header line or an empty line
1653         std::optional<MmMetricsReporter::OomGroupMemUsage> parsedData =
1654                 parseMmProcessUsageByOomGroupLine(line);
1655         if (parsedData.has_value())
1656             ogusage->push_back(parsedData.value());
1657     }
1658 
1659     if (ogusage->size() != OOM_NUM_OF_GROUPS) {
1660         ALOGE("Error file corrupted: number of oom_group %zu != expected %" PRId32, ogusage->size(),
1661               OOM_NUM_OF_GROUPS);
1662         goto error_out;
1663     }
1664 
1665     for (size_t i = 0; i < ogusage->size(); ++i) {
1666         if ((*ogusage)[i].oom_group != static_cast<int32_t>(i)) {
1667             goto error_out;  // Mismatch found
1668         }
1669     }
1670     return true;
1671 
1672 error_out:
1673     ogusage->clear();
1674     return false;
1675 }
1676 
1677 /*
1678  * generate one MmProcessUsageByOomGroupSnapshot atom
1679  * Note: number of atoms = number of oom groups
1680  */
genMmProcessUsageByOomGroupSnapshotAtom(const MmMetricsReporter::OomGroupMemUsage & data)1681 std::vector<VendorAtomValue> MmMetricsReporter::genMmProcessUsageByOomGroupSnapshotAtom(
1682         const MmMetricsReporter::OomGroupMemUsage &data) {
1683     std::vector<VendorAtomValue> values;
1684 
1685     values.push_back(VendorAtomValue(oom_usage_uid_));
1686     values.push_back(VendorAtomValue(static_cast<int32_t>(data.oom_group)));
1687     values.push_back(VendorAtomValue(data.nr_task));
1688     values.push_back(VendorAtomValue(data.file_rss_kb));
1689     values.push_back(VendorAtomValue(data.anon_rss_kb));
1690     values.push_back(VendorAtomValue(data.pgtable_kb));
1691     values.push_back(VendorAtomValue(data.swap_ents_kb));
1692     values.push_back(VendorAtomValue(data.shmem_rss_kb));
1693     return values;
1694 }
1695 
readAndGenGcmaPerHour()1696 std::vector<VendorAtomValue> MmMetricsReporter::readAndGenGcmaPerHour() {
1697     uint64_t val;
1698     std::string path = getSysfsPath(std::string(kGcmaBasePath) + '/' + kGcmaCached);
1699     std::vector<VendorAtomValue> values;
1700 
1701     if (!GcmaSupported())
1702         return values;
1703 
1704     if (!ReadFileToUint(path, &val)) {
1705         ALOGE("Error: GCMA.cached: file %s: parsed Uint failed.", path.c_str());
1706     } else if (static_cast<int64_t>(val) < 0) {
1707         ALOGE("Error: GCMA.cached: value overflow.");
1708     } else {
1709         values.push_back(VendorAtomValue(static_cast<int64_t>(val)));
1710     }
1711     return values;
1712 }
1713 
readAndGenGcmaPerDay()1714 std::vector<VendorAtomValue> MmMetricsReporter::readAndGenGcmaPerDay() {
1715     std::vector<VendorAtomValue> values;
1716     uint64_t val;
1717     std::vector<int64_t> repeatedLongValue;
1718     std::string path;
1719     std::string base_path(kGcmaBasePath);
1720 
1721     if (!GcmaSupported())
1722         return values;
1723 
1724     for (auto p : kGcmaHourlySimpleKnobs) {
1725         path = getSysfsPath(base_path + '/' + p);
1726         if (!ReadFileToUint(path, &val)) {
1727             ALOGE("Error: GCMA.%s: file %s: parsed Uint failed.", p, path.c_str());
1728             goto got_error;
1729         } else if (static_cast<int64_t>(val) < 0) {
1730             ALOGE("Error: GCMA.%s: value overflow.", p);
1731             goto got_error;
1732         }
1733         values.push_back(VendorAtomValue(static_cast<int64_t>(val)));
1734     }
1735 
1736     for (auto p : kGcmaHourlyHistogramKnobs) {
1737         path = getSysfsPath(base_path + '/' + p);
1738         if (!ReadFileToUint(path, &val)) {
1739             ALOGE("Error: GCMA.%s: file %s: parsed Uint failed.", p, path.c_str());
1740             goto got_error;
1741         } else if (static_cast<int64_t>(val) < 0) {
1742             ALOGE("Error: GCMA.%s: value overflow.", p);
1743             goto got_error;
1744         }
1745         repeatedLongValue.push_back(static_cast<int64_t>(val));
1746     }
1747     values.push_back(VendorAtomValue(std::optional<std::vector<int64_t>>(repeatedLongValue)));
1748     return values;
1749 
1750 got_error:
1751     values.clear();
1752     return values;
1753 }
1754 
1755 }  // namespace pixel
1756 }  // namespace google
1757 }  // namespace hardware
1758 }  // namespace android
1759