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