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