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