| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "pixelstats: MmMetrics" |
| |
| #include <aidl/android/frameworks/stats/IStats.h> |
| #include <android-base/file.h> |
| #include <android-base/parsedouble.h> |
| #include <android-base/parseint.h> |
| #include <android-base/properties.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <android/binder_manager.h> |
| #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h> |
| #include <pixelstats/MmMetricsReporter.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <utils/Log.h> |
| |
| #include <numeric> |
| |
| #define SZ_4K 0x00001000 |
| #define SZ_2M 0x00200000 |
| |
| namespace android { |
| namespace hardware { |
| namespace google { |
| namespace pixel { |
| |
| using aidl::android::frameworks::stats::IStats; |
| using aidl::android::frameworks::stats::VendorAtom; |
| using aidl::android::frameworks::stats::VendorAtomValue; |
| using android::base::ReadFileToString; |
| using android::base::StartsWith; |
| using android::hardware::google::pixel::PixelAtoms::CmaStatus; |
| using android::hardware::google::pixel::PixelAtoms::CmaStatusExt; |
| using android::hardware::google::pixel::PixelAtoms::PixelMmMetricsPerDay; |
| using android::hardware::google::pixel::PixelAtoms::PixelMmMetricsPerHour; |
| |
| const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kMmMetricsPerHourInfo = { |
| {"nr_free_pages", PixelMmMetricsPerHour::kFreePagesFieldNumber, false}, |
| {"nr_anon_pages", PixelMmMetricsPerHour::kAnonPagesFieldNumber, false}, |
| {"nr_file_pages", PixelMmMetricsPerHour::kFilePagesFieldNumber, false}, |
| {"nr_slab_reclaimable", PixelMmMetricsPerHour::kSlabReclaimableFieldNumber, false}, |
| {"nr_slab_unreclaimable", PixelMmMetricsPerHour::kSlabUnreclaimableFieldNumber, false}, |
| {"nr_zspages", PixelMmMetricsPerHour::kZspagesFieldNumber, false}, |
| {"nr_unevictable", PixelMmMetricsPerHour::kUnevictableFieldNumber, false}, |
| {"nr_shmem", PixelMmMetricsPerHour::kShmemPagesFieldNumber, false}, |
| {"nr_page_table_pages", PixelMmMetricsPerHour::kPageTablePagesFieldNumber, false}, |
| {"ION_heap", PixelMmMetricsPerHour::kDmabufKbFieldNumber, false}, |
| }; |
| |
| const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kMmMetricsPerDayInfo = { |
| {"workingset_refault", PixelMmMetricsPerDay::kWorkingsetRefaultFieldNumber, true}, |
| {"pswpin", PixelMmMetricsPerDay::kPswpinFieldNumber, true}, |
| {"pswpout", PixelMmMetricsPerDay::kPswpoutFieldNumber, true}, |
| {"allocstall_dma", PixelMmMetricsPerDay::kAllocstallDmaFieldNumber, true}, |
| {"allocstall_dma32", PixelMmMetricsPerDay::kAllocstallDma32FieldNumber, true}, |
| {"allocstall_normal", PixelMmMetricsPerDay::kAllocstallNormalFieldNumber, true}, |
| {"allocstall_movable", PixelMmMetricsPerDay::kAllocstallMovableFieldNumber, true}, |
| {"pgalloc_dma", PixelMmMetricsPerDay::kPgallocDmaFieldNumber, true}, |
| {"pgalloc_dma32", PixelMmMetricsPerDay::kPgallocDma32FieldNumber, true}, |
| {"pgalloc_normal", PixelMmMetricsPerDay::kPgallocNormalFieldNumber, true}, |
| {"pgalloc_movable", PixelMmMetricsPerDay::kPgallocMovableFieldNumber, true}, |
| {"pgsteal_kswapd", PixelMmMetricsPerDay::kPgstealKswapdFieldNumber, true}, |
| {"pgsteal_direct", PixelMmMetricsPerDay::kPgstealDirectFieldNumber, true}, |
| {"pgscan_kswapd", PixelMmMetricsPerDay::kPgscanKswapdFieldNumber, true}, |
| {"pgscan_direct", PixelMmMetricsPerDay::kPgscanDirectFieldNumber, true}, |
| {"oom_kill", PixelMmMetricsPerDay::kOomKillFieldNumber, true}, |
| {"pgalloc_costly_order", PixelMmMetricsPerDay::kPgallocHighFieldNumber, true}, |
| {"pgcache_hit", PixelMmMetricsPerDay::kPgcacheHitFieldNumber, true}, |
| {"pgcache_miss", PixelMmMetricsPerDay::kPgcacheMissFieldNumber, true}, |
| {"workingset_refault_file", PixelMmMetricsPerDay::kWorkingsetRefaultFileFieldNumber, true}, |
| {"workingset_refault_anon", PixelMmMetricsPerDay::kWorkingsetRefaultAnonFieldNumber, true}, |
| {"compact_success", PixelMmMetricsPerDay::kCompactSuccessFieldNumber, true}, |
| {"compact_fail", PixelMmMetricsPerDay::kCompactFailFieldNumber, true}, |
| {"kswapd_low_wmark_hit_quickly", PixelMmMetricsPerDay::kKswapdLowWmarkHqFieldNumber, true}, |
| {"kswapd_high_wmark_hit_quickly", PixelMmMetricsPerDay::kKswapdHighWmarkHqFieldNumber, |
| true}, |
| {"thp_file_alloc", PixelMmMetricsPerDay::kThpFileAllocFieldNumber, true}, |
| {"thp_zero_page_alloc", PixelMmMetricsPerDay::kThpZeroPageAllocFieldNumber, true}, |
| {"thp_split_page", PixelMmMetricsPerDay::kThpSplitPageFieldNumber, true}, |
| {"thp_migration_split", PixelMmMetricsPerDay::kThpMigrationSplitFieldNumber, true}, |
| {"thp_deferred_split_page", PixelMmMetricsPerDay::kThpDeferredSplitPageFieldNumber, true}, |
| {"pageoutrun", PixelMmMetricsPerDay::kKswapdPageoutRunFieldNumber, true}, |
| }; |
| |
| const std::vector<MmMetricsReporter::ProcStatMetricsInfo> MmMetricsReporter::kProcStatInfo = { |
| // sum of the cpu line: the cpu total time (-1 means to sum up all) |
| {"cpu", -1, PixelMmMetricsPerDay::kCpuTotalTimeCsFieldNumber, true}, |
| |
| // array[3] of the cpu line: the Idle time |
| {"cpu", 3, PixelMmMetricsPerDay::kCpuIdleTimeCsFieldNumber, true}, |
| |
| // array[4] of the cpu line: the I/O wait time. |
| {"cpu", 4, PixelMmMetricsPerDay::kCpuIoWaitTimeCsFieldNumber, true}, |
| }; |
| |
| const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kCmaStatusInfo = { |
| {"alloc_pages_attempts", CmaStatus::kCmaAllocPagesAttemptsFieldNumber, true}, |
| {"alloc_pages_failfast_attempts", CmaStatus::kCmaAllocPagesSoftAttemptsFieldNumber, true}, |
| {"fail_pages", CmaStatus::kCmaFailPagesFieldNumber, true}, |
| {"fail_failfast_pages", CmaStatus::kCmaFailSoftPagesFieldNumber, true}, |
| {"migrated_pages", CmaStatus::kMigratedPagesFieldNumber, true}, |
| }; |
| |
| const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kCmaStatusExtInfo = { |
| {"latency_low", CmaStatusExt::kCmaAllocLatencyLowFieldNumber, false}, |
| {"latency_mid", CmaStatusExt::kCmaAllocLatencyMidFieldNumber, false}, |
| {"latency_high", CmaStatusExt::kCmaAllocLatencyHighFieldNumber, false}, |
| }; |
| |
| static bool file_exists(const char *path) { |
| struct stat sbuf; |
| |
| return (stat(path, &sbuf) == 0); |
| } |
| |
| bool MmMetricsReporter::checkKernelMMMetricSupport() { |
| const char *const require_all[] = { |
| kVmstatPath, |
| kGpuTotalPages, |
| kPixelStatMm, |
| }; |
| const char *const require_one_ion_total_pools_path[] = { |
| kIonTotalPoolsPath, |
| kIonTotalPoolsPathForLegacy, |
| }; |
| |
| bool err_require_all = false; |
| for (auto &path : require_all) { |
| if (!file_exists(path)) { |
| ALOGE("MM Metrics not supported - %s not found.", path); |
| err_require_all = true; |
| } |
| } |
| if (err_require_all) { |
| ALOGE("MM Metrics not supported: Some required sysfs nodes not found."); |
| } |
| |
| bool err_require_one_ion_total_pools_path = true; |
| for (auto &path : require_one_ion_total_pools_path) { |
| if (file_exists(path)) { |
| err_require_one_ion_total_pools_path = false; |
| break; |
| } |
| } |
| if (err_require_one_ion_total_pools_path) { |
| ALOGI("MM Metrics not supported - No IonTotalPools paths were found."); |
| } |
| |
| return !err_require_all && !err_require_one_ion_total_pools_path; |
| } |
| |
| MmMetricsReporter::MmMetricsReporter() |
| : kVmstatPath("/proc/vmstat"), |
| kIonTotalPoolsPath("/sys/kernel/dma_heap/total_pools_kb"), |
| kIonTotalPoolsPathForLegacy("/sys/kernel/ion/total_pools_kb"), |
| kGpuTotalPages("/sys/kernel/pixel_stat/gpu/mem/total_page_count"), |
| kCompactDuration("/sys/kernel/pixel_stat/mm/compaction/mm_compaction_duration"), |
| kDirectReclaimBasePath("/sys/kernel/pixel_stat/mm/vmscan/direct_reclaim"), |
| kPixelStatMm("/sys/kernel/pixel_stat/mm"), |
| kMeminfoPath("/proc/meminfo"), |
| kProcStatPath("/proc/stat"), |
| prev_compaction_duration_(kNumCompactionDurationPrevMetrics, 0), |
| prev_direct_reclaim_(kNumDirectReclaimPrevMetrics, 0) { |
| ker_mm_metrics_support_ = checkKernelMMMetricSupport(); |
| } |
| |
| bool MmMetricsReporter::ReadFileToUint(const std::string &path, uint64_t *val) { |
| std::string file_contents; |
| |
| if (!ReadFileToString(path, &file_contents)) { |
| // Don't print this log if the file doesn't exist, since logs will be printed repeatedly. |
| if (errno != ENOENT) { |
| ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno)); |
| } |
| return false; |
| } else { |
| file_contents = android::base::Trim(file_contents); |
| if (!android::base::ParseUint(file_contents, val)) { |
| ALOGI("Unable to convert %s to uint - %s", path.c_str(), strerror(errno)); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* |
| * This function reads whole file and parses tokens separated by <delim> into |
| * long integers. Useful for direct reclaim & compaction duration sysfs nodes. |
| * Data write is using all or none policy: It will not write partial data unless |
| * all data values are good. |
| * |
| * path: file to open/read |
| * data: where to store the results |
| * start_idx: index into data[] where to start saving the results |
| * delim: delimiters separating different longs |
| * skip: how many resulting longs to skip before saving |
| * nonnegtive: set to true to validate positive numbers |
| * |
| * Return value: number of longs actually stored on success. negative |
| * error codes on errors. |
| */ |
| static int ReadFileToLongs(const std::string &path, std::vector<long> *data, int start_idx, |
| const char *delim, int skip, bool nonnegative = false) { |
| std::vector<long> out; |
| enum { err_read_file = -1, err_parse = -2 }; |
| std::string file_contents; |
| |
| if (!ReadFileToString(path, &file_contents)) { |
| // Don't print this log if the file doesn't exist, since logs will be printed repeatedly. |
| if (errno != ENOENT) { |
| ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno)); |
| } |
| return err_read_file; |
| } |
| |
| file_contents = android::base::Trim(file_contents); |
| std::vector<std::string> words = android::base::Tokenize(file_contents, delim); |
| if (words.size() == 0) |
| return 0; |
| |
| for (auto &w : words) { |
| if (skip) { |
| skip--; |
| continue; |
| } |
| long tmp; |
| if (!android::base::ParseInt(w, &tmp) || (nonnegative && tmp < 0)) |
| return err_parse; |
| out.push_back(tmp); |
| } |
| |
| int min_size = std::max(static_cast<int>(out.size()) + start_idx, 0); |
| if (min_size > data->size()) |
| data->resize(min_size); |
| std::copy(out.begin(), out.end(), data->begin() + start_idx); |
| |
| return out.size(); |
| } |
| |
| /* |
| * This function calls ReadFileToLongs, and checks the expected number |
| * of long integers read. Useful for direct reclaim & compaction duration |
| * sysfs nodes. |
| * |
| * path: file to open/read |
| * data: where to store the results |
| * start_idx: index into data[] where to start saving the results |
| * delim: delimiters separating different longs |
| * skip: how many resulting longs to skip before saving |
| * expected_num: number of expected longs to be read. |
| * nonnegtive: set to true to validate positive numbers |
| * |
| * Return value: true if successfully get expected number of long values. |
| * otherwise false. |
| */ |
| static inline bool ReadFileToLongsCheck(const std::string &path, std::vector<long> *store, |
| int start_idx, const char *delim, int skip, |
| int expected_num, bool nonnegative = false) { |
| int num = ReadFileToLongs(path, store, start_idx, delim, skip, nonnegative); |
| |
| if (num == expected_num) |
| return true; |
| |
| int last_idx = std::min(start_idx + expected_num, static_cast<int>(store->size())); |
| std::fill(store->begin() + start_idx, store->begin() + last_idx, -1); |
| |
| return false; |
| } |
| |
| bool MmMetricsReporter::reportVendorAtom(const std::shared_ptr<IStats> &stats_client, int atom_id, |
| const std::vector<VendorAtomValue> &values, |
| const std::string &atom_name) { |
| // Send vendor atom to IStats HAL |
| VendorAtom event = {.reverseDomainName = "", |
| .atomId = atom_id, |
| .values = std::move(values)}; |
| const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); |
| if (!ret.isOk()) { |
| ALOGE("Unable to report %s to Stats service", atom_name.c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Parse sysfs node in Name/Value pair form, including /proc/vmstat and /proc/meminfo |
| * Name could optionally with a colon (:) suffix (will be removed to produce the output map), |
| * extra columns (e.g. 3rd column 'kb' for /proc/meminfo) will be discarded. |
| * Return value: a map containing the pairs of {field_string, data}. |
| */ |
| std::map<std::string, uint64_t> MmMetricsReporter::readSysfsNameValue(const std::string &path) { |
| std::string file_contents; |
| std::map<std::string, uint64_t> metrics; |
| |
| if (!ReadFileToString(path, &file_contents)) { |
| ALOGE("Unable to read vmstat from %s, err: %s", path.c_str(), strerror(errno)); |
| return metrics; |
| } |
| |
| std::istringstream data(file_contents); |
| std::string line; |
| int line_num = 0; |
| |
| while (std::getline(data, line)) { |
| line_num++; |
| std::vector<std::string> words = android::base::Tokenize(line, " "); |
| |
| uint64_t i; |
| if (words.size() < 2 || !android::base::ParseUint(words[1], &i)) { |
| ALOGE("File %s corrupted at line %d", path.c_str(), line_num); |
| metrics.clear(); |
| break; |
| } |
| |
| if (words[0][words[0].length() - 1] == ':') |
| words[0].pop_back(); |
| |
| metrics[words[0]] = i; |
| } |
| |
| return metrics; |
| } |
| |
| /** |
| * Parse the output of /proc/stat or any sysfs node having the same output format. |
| * The map containing pairs of {field_name, array (vector) of values} will be returned. |
| */ |
| std::map<std::string, std::vector<uint64_t>> MmMetricsReporter::readProcStat( |
| const std::string &path) { |
| std::map<std::string, std::vector<uint64_t>> fields; |
| std::string content; |
| bool got_err = false; |
| |
| // Use ReadFileToString for convenient file reading |
| if (!android::base::ReadFileToString(path, &content)) { |
| ALOGE("Error: Unable to open %s", path.c_str()); |
| return fields; // Return empty map on error |
| } |
| |
| // Split the file content into lines |
| std::vector<std::string> lines = android::base::Split(content, "\n"); |
| |
| for (const auto &line : lines) { |
| std::vector<std::string> tokens = android::base::Tokenize(line, " "); |
| if (tokens.empty()) { |
| continue; // Skip empty lines |
| } |
| |
| const std::string &field_name = tokens[0]; |
| |
| // Check for duplicates |
| if (fields.find(field_name) != fields.end()) { |
| ALOGE("Duplicate field found: %s", field_name.c_str()); |
| got_err = true; |
| goto exit_loop; |
| } |
| |
| std::vector<uint64_t> values; |
| for (size_t i = 1; i < tokens.size(); ++i) { |
| uint64_t value; |
| if (!android::base::ParseUint(tokens[i], &value)) { |
| ALOGE("Invalid field value format in line: %s", line.c_str()); |
| got_err = true; |
| goto exit_loop; |
| } |
| values.push_back(value); |
| } |
| fields[field_name] = values; |
| } |
| |
| exit_loop: |
| if (got_err) { |
| fields.clear(); |
| } |
| return fields; |
| } |
| |
| uint64_t MmMetricsReporter::getIonTotalPools() { |
| uint64_t res; |
| |
| if (!ReadFileToUint(getSysfsPath(kIonTotalPoolsPathForLegacy), &res) || (res == 0)) { |
| if (!ReadFileToUint(getSysfsPath(kIonTotalPoolsPath), &res)) { |
| return 0; |
| } |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Collect GPU memory from kGpuTotalPages and return the total number of 4K page. |
| */ |
| uint64_t MmMetricsReporter::getGpuMemory() { |
| uint64_t gpu_size = 0; |
| |
| if (!ReadFileToUint(getSysfsPath(kGpuTotalPages), &gpu_size)) { |
| return 0; |
| } |
| return gpu_size; |
| } |
| |
| /** |
| * fillAtomValues() is used to copy Mm metrics to values |
| * metrics_info: This is a vector of MmMetricsInfo {field_string, atom_key, update_diff} |
| * field_string is used to get the data from mm_metrics. |
| * atom_key is the position where the data should be put into values. |
| * update_diff will be true if this is an accumulated data. |
| * metrics_info may have multiple entries with the same atom_key, |
| * e.g. workingset_refault and workingset_refault_file. |
| * mm_metrics: This map contains pairs of {field_string, cur_value} collected |
| * from /proc/vmstat or the sysfs for the pixel specific metrics. |
| * e.g. {"nr_free_pages", 200000} |
| * Some data in mm_metrics are accumulated, e.g. pswpin. |
| * We upload the difference instead of the accumulated value |
| * when update_diff of the field is true. |
| * prev_mm_metrics: The pointer to the metrics we collected last time. |
| * nullptr if all fields are snapshot values (i.e. won't need |
| * to upload diff, i.e. entry.update_diff == false for all fields.) |
| * atom_values: The atom values that will be reported later. |
| * return value: true on success, false on error. |
| */ |
| bool MmMetricsReporter::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) { |
| bool err = false; |
| VendorAtomValue tmp; |
| tmp.set<VendorAtomValue::longValue>(0); |
| // resize atom_values to add all fields defined in metrics_info |
| int max_idx = 0; |
| for (auto &entry : metrics_info) { |
| if (max_idx < entry.atom_key) |
| max_idx = entry.atom_key; |
| } |
| unsigned int size = max_idx - kVendorAtomOffset + 1; |
| if (atom_values->size() < size) |
| atom_values->resize(size, tmp); |
| |
| for (auto &entry : metrics_info) { |
| int atom_idx = entry.atom_key - kVendorAtomOffset; |
| |
| auto data = mm_metrics.find(entry.name); |
| if (data == mm_metrics.end()) |
| continue; |
| |
| uint64_t cur_value = data->second; |
| uint64_t prev_value = 0; |
| if (prev_mm_metrics == nullptr && entry.update_diff) { |
| // Bug: We need previous saved metrics to calculate the difference. |
| ALOGE("FIX ME: shouldn't reach here: " |
| "Diff upload required by prev_mm_metrics not provided."); |
| err = true; |
| continue; |
| } else if (entry.update_diff) { |
| // reaching here implies: prev_mm_metrics != nullptr |
| auto prev_data = prev_mm_metrics->find(entry.name); |
| if (prev_data != prev_mm_metrics->end()) { |
| prev_value = prev_data->second; |
| } |
| // else: implies it's the 1st data: nothing to do, since prev_value already = 0 |
| } |
| |
| tmp.set<VendorAtomValue::longValue>(cur_value - prev_value); |
| (*atom_values)[atom_idx] = tmp; |
| } |
| if (prev_mm_metrics && !err) { |
| (*prev_mm_metrics) = mm_metrics; |
| } |
| return !err; |
| } |
| |
| /* |
| * offset -1 means to get the sum of the whole mapped array |
| * otherwise get the array value at the offset. |
| * return value: true for success (got the value), else false |
| */ |
| bool MmMetricsReporter::getValueFromParsedProcStat( |
| const std::map<std::string, std::vector<uint64_t>> pstat, const std::string &name, |
| int offset, uint64_t *output) { |
| static bool log_once = false; |
| |
| if (offset < -1) { |
| if (!log_once) { |
| log_once = true; |
| ALOGE("Bug: bad offset %d for entry %s", offset, name.c_str()); |
| } |
| return false; |
| } |
| |
| // the mapped array not found |
| auto itr = pstat.find(name); |
| if (itr == pstat.end()) { |
| return false; |
| } |
| |
| const std::vector<uint64_t> &values = itr->second; |
| |
| if (values.size() == 0) { |
| return false; |
| } |
| |
| if (offset >= 0 && offset >= values.size()) { |
| return false; |
| } |
| |
| if (offset != -1) { |
| *output = values.at(offset); |
| return true; |
| } |
| |
| *output = std::accumulate(values.begin(), values.end(), 0); |
| return true; |
| } |
| |
| /** |
| * metrics_info: see struct ProcStatMetricsInfo for detail |
| * |
| * /proc/stat was already read and parsed by readProcStat(). |
| * The parsed results are stored in <cur_pstat> |
| * The previous parsed results are stored in <prev_pstat> (in case the diff value is asked) |
| * |
| * A typical /proc/stat line looks like |
| * cpu 258 132 521 30 15 28 16 |
| * The parsed results are a map mapping the name (i.e. the 1st token in a /proc/stat line) |
| * to an array of numbers. |
| * |
| * Each element (entry) in metrics_info tells us where/how to find the corresponding |
| * value for that entry. e.g. |
| * // name, offset, atom_key, update_diff |
| * {"cpu", -1, PixelMmMetricsPerDay::kCpuTotalTimeFieldNumber, true } |
| * This is the entry "cpu total time". |
| * We need to look at the "cpu" line from /proc/stat (or from the parsed result, i.e. map) |
| * -1 is the offset for the value in the line. Normally it is a zero-based |
| * number, from that we know which value to get from the array. |
| * -1 is special: it does not mean one specific offset but to sum-up everything in the array. |
| * |
| * The final 'true' ask us to create a diff with the previously stored value |
| * for this same entry (e.g. cpu total time). |
| * |
| * PixelMmMetricsPerDay::kCpuTotalTimeFieldNumber (.atom_key) indicate the offset |
| * in the atom field value array (i.e. <atom_values>) where we need to fill in the value. |
| */ |
| bool MmMetricsReporter::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) { |
| bool is_success = true; |
| for (const auto &entry : metrics_info) { |
| int atom_idx = entry.atom_key - kVendorAtomOffset; |
| uint64_t cur_value; |
| uint64_t prev_value = 0; |
| |
| if (atom_idx < 0) { |
| // Reaching here means the data definition (.atom_key) has a problem. |
| ALOGE("Bug: should not reach here: index to fill is negative for " |
| "entry %s offset %d", |
| entry.name.c_str(), entry.offset); |
| is_success = false; |
| break; |
| } |
| |
| if (prev_pstat == nullptr && entry.update_diff) { |
| // Reaching here means you need to provide prev_pstat or define false for .update_diff |
| ALOGE("Bug: should not reach here: asking for diff without providing " |
| " the previous data for entry %s offset %d", |
| entry.name.c_str(), entry.offset); |
| is_success = false; |
| break; |
| } |
| |
| // Find the field value from the current read |
| if (!getValueFromParsedProcStat(cur_pstat, entry.name, entry.offset, &cur_value)) { |
| // Metric not found |
| ALOGE("Metric '%s' not found in ProcStat", entry.name.c_str()); |
| printf("Error: Metric '%s' not found in ProcStat", entry.name.c_str()); |
| is_success = false; |
| break; |
| } |
| |
| // Find the field value from the previous read, if we need diff value |
| if (entry.update_diff) { |
| // prev_value won't change (0) if not found. So, no need to check return status. |
| getValueFromParsedProcStat(*prev_pstat, entry.name, entry.offset, &prev_value); |
| } |
| |
| // Fill the atom_values array |
| VendorAtomValue tmp; |
| tmp.set<VendorAtomValue::longValue>((int64_t)cur_value - prev_value); |
| (*atom_values)[atom_idx] = tmp; |
| } |
| |
| if (!is_success) { |
| prev_pstat->clear(); |
| return false; |
| } |
| |
| // Update prev_pstat |
| if (prev_pstat != nullptr) { |
| *prev_pstat = cur_pstat; |
| } |
| return true; |
| } |
| |
| void MmMetricsReporter::aggregatePixelMmMetricsPer5Min() { |
| aggregatePressureStall(); |
| } |
| |
| void MmMetricsReporter::logPixelMmMetricsPerHour(const std::shared_ptr<IStats> &stats_client) { |
| std::vector<VendorAtomValue> values = genPixelMmMetricsPerHour(); |
| |
| if (values.size() != 0) { |
| // Send vendor atom to IStats HAL |
| reportVendorAtom(stats_client, PixelAtoms::Atom::kPixelMmMetricsPerHour, values, |
| "PixelMmMetricsPerHour"); |
| } |
| } |
| |
| std::vector<VendorAtomValue> MmMetricsReporter::genPixelMmMetricsPerHour() { |
| if (!MmMetricsSupported()) |
| return std::vector<VendorAtomValue>(); |
| |
| std::map<std::string, uint64_t> vmstat = readSysfsNameValue(getSysfsPath(kVmstatPath)); |
| if (vmstat.size() == 0) |
| return std::vector<VendorAtomValue>(); |
| |
| std::map<std::string, uint64_t> meminfo = readSysfsNameValue(getSysfsPath(kMeminfoPath)); |
| if (meminfo.size() == 0) |
| return std::vector<VendorAtomValue>(); |
| |
| uint64_t ion_total_pools = getIonTotalPools(); |
| uint64_t gpu_memory = getGpuMemory(); |
| |
| // allocate enough values[] entries for the metrics. |
| VendorAtomValue tmp; |
| tmp.set<VendorAtomValue::longValue>(0); |
| int last_value_index = PixelMmMetricsPerHour::kDmabufKbFieldNumber - kVendorAtomOffset; |
| std::vector<VendorAtomValue> values(last_value_index + 1, tmp); |
| |
| fillAtomValues(kMmMetricsPerHourInfo, vmstat, &prev_hour_vmstat_, &values); |
| fillAtomValues(kMmMetricsPerHourInfo, meminfo, nullptr, &values); |
| tmp.set<VendorAtomValue::longValue>(ion_total_pools); |
| values[PixelMmMetricsPerHour::kIonTotalPoolsFieldNumber - kVendorAtomOffset] = tmp; |
| tmp.set<VendorAtomValue::longValue>(gpu_memory); |
| values[PixelMmMetricsPerHour::kGpuMemoryFieldNumber - kVendorAtomOffset] = tmp; |
| fillPressureStallAtom(&values); |
| |
| return values; |
| } |
| |
| void MmMetricsReporter::logPixelMmMetricsPerDay(const std::shared_ptr<IStats> &stats_client) { |
| std::vector<VendorAtomValue> values = genPixelMmMetricsPerDay(); |
| |
| if (values.size() != 0) { |
| // Send vendor atom to IStats HAL |
| reportVendorAtom(stats_client, PixelAtoms::Atom::kPixelMmMetricsPerDay, values, |
| "PixelMmMetricsPerDay"); |
| } |
| } |
| |
| std::vector<VendorAtomValue> MmMetricsReporter::genPixelMmMetricsPerDay() { |
| if (!MmMetricsSupported()) |
| return std::vector<VendorAtomValue>(); |
| |
| std::map<std::string, uint64_t> vmstat = readSysfsNameValue(getSysfsPath(kVmstatPath)); |
| if (vmstat.size() == 0) |
| return std::vector<VendorAtomValue>(); |
| |
| std::map<std::string, std::vector<uint64_t>> procstat = |
| readProcStat(getSysfsPath(kProcStatPath)); |
| if (procstat.size() == 0) |
| return std::vector<VendorAtomValue>(); |
| |
| std::vector<long> direct_reclaim; |
| readDirectReclaimStat(&direct_reclaim); |
| |
| std::vector<long> compaction_duration; |
| readCompactionDurationStat(&compaction_duration); |
| |
| bool is_first_atom = (prev_day_vmstat_.size() == 0) ? true : false; |
| |
| // allocate enough values[] entries for the metrics. |
| VendorAtomValue tmp; |
| tmp.set<VendorAtomValue::longValue>(0); |
| int last_value_index = PixelMmMetricsPerDay::kKswapdPageoutRunFieldNumber - kVendorAtomOffset; |
| std::vector<VendorAtomValue> values(last_value_index + 1, tmp); |
| |
| if (!fillAtomValues(kMmMetricsPerDayInfo, vmstat, &prev_day_vmstat_, &values)) { |
| // resets previous read since we reject the current one: so that we will |
| // need two more reads to get a new diff. |
| prev_day_vmstat_.clear(); |
| return std::vector<VendorAtomValue>(); |
| } |
| |
| std::map<std::string, uint64_t> pixel_vmstat = readSysfsNameValue( |
| getSysfsPath(android::base::StringPrintf("%s/vmstat", kPixelStatMm).c_str())); |
| if (!fillAtomValues(kMmMetricsPerDayInfo, pixel_vmstat, &prev_day_pixel_vmstat_, &values)) { |
| // resets previous read since we reject the current one: so that we will |
| // need two more reads to get a new diff. |
| prev_day_vmstat_.clear(); |
| return std::vector<VendorAtomValue>(); |
| } |
| fillProcessStime(PixelMmMetricsPerDay::kKswapdStimeClksFieldNumber, "kswapd0", |
| &prev_kswapd_pid_, &prev_kswapd_stime_, &values); |
| fillProcessStime(PixelMmMetricsPerDay::kKcompactdStimeClksFieldNumber, "kcompactd0", |
| &prev_kcompactd_pid_, &prev_kcompactd_stime_, &values); |
| fillDirectReclaimStatAtom(direct_reclaim, &values); |
| fillCompactionDurationStatAtom(compaction_duration, &values); |
| |
| if (!fillProcStat(kProcStatInfo, procstat, &prev_procstat_, &values)) { |
| prev_procstat_.clear(); |
| return std::vector<VendorAtomValue>(); |
| } |
| |
| // Don't report the first atom to avoid big spike in accumulated values. |
| if (is_first_atom) { |
| values.clear(); |
| } |
| |
| return values; |
| } |
| |
| /** |
| * Return pid if /proc/<pid>/comm is equal to name, or -1 if not found. |
| */ |
| int MmMetricsReporter::findPidByProcessName(const std::string &name) { |
| std::unique_ptr<DIR, int (*)(DIR *)> dir(opendir("/proc"), closedir); |
| if (!dir) |
| return -1; |
| |
| int pid; |
| while (struct dirent *dp = readdir(dir.get())) { |
| if (dp->d_type != DT_DIR) |
| continue; |
| |
| if (!android::base::ParseInt(dp->d_name, &pid)) |
| continue; |
| |
| // Avoid avc denial since pixelstats-vendor doesn't have the permission to access /proc/1 |
| if (pid == 1) |
| continue; |
| |
| std::string file_contents; |
| std::string path = android::base::StringPrintf("/proc/%s/comm", dp->d_name); |
| if (!ReadFileToString(path, &file_contents)) |
| continue; |
| |
| file_contents = android::base::Trim(file_contents); |
| if (file_contents.compare(name)) |
| continue; |
| |
| return pid; |
| } |
| return -1; |
| } |
| |
| /** |
| * Get stime of a process from <path>, i.e. 15th field of <path> = /proc/<pid>/stat |
| * Custom path (base path) could be used to inject data for test codes. |
| */ |
| int64_t MmMetricsReporter::getStimeByPathAndVerifyName(const std::string &path, |
| const std::string &name) { |
| const int stime_idx = 15; |
| const int name_idx = 2; |
| uint64_t stime; |
| int64_t ret; |
| std::string file_contents; |
| if (!ReadFileToString(path, &file_contents)) { |
| ALOGE("Unable to read %s, err: %s", path.c_str(), strerror(errno)); |
| return -1; |
| } |
| |
| std::vector<std::string> data = android::base::Split(file_contents, " "); |
| if (data.size() < stime_idx) { |
| ALOGE("Unable to find stime from %s. size: %zu", path.c_str(), data.size()); |
| return -1; |
| } |
| |
| std::string parenthesis_name = std::string("(") + name + ")"; |
| if (parenthesis_name.compare(data[name_idx - 1]) != 0) { |
| ALOGE("Mismatched name for process stat: queried %s vs. found %s", parenthesis_name.c_str(), |
| data[name_idx - 1].c_str()); |
| return -1; |
| } |
| |
| if (android::base::ParseUint(data[stime_idx - 1], &stime)) { |
| ret = static_cast<int64_t>(stime); |
| return ret < 0 ? -1 : ret; |
| } else { |
| ALOGE("Stime Uint parse fail for process info path %s", path.c_str()); |
| return -1; |
| } |
| } |
| |
| // returns /proc/<pid> on success, empty string on failure. |
| // For test: use derived class to return custom path for test data injection. |
| std::string MmMetricsReporter::getProcessStatPath(const std::string &name, int *prev_pid) { |
| if (prev_pid == nullptr) { |
| ALOGE("Should not reach here: prev_pid == nullptr"); |
| return ""; |
| } |
| |
| int pid = findPidByProcessName(name); |
| if (pid <= 0) { |
| ALOGE("Unable to find pid for %s, err: %s", name.c_str(), strerror(errno)); |
| return ""; |
| } |
| |
| if (*prev_pid != -1 && pid != *prev_pid) |
| ALOGW("%s pid changed from %d to %d.", name.c_str(), *prev_pid, pid); |
| *prev_pid = pid; |
| |
| return android::base::StringPrintf("/proc/%d/stat", pid); |
| } |
| |
| /** |
| * Find stime of the process and copy it into atom_values |
| * atom_key: Currently, it can only be kKswapdTimeFieldNumber or kKcompactdTimeFieldNumber |
| * name: process name, "kswapd0" or "kcompactd0" |
| * prev_pid: The pid of the process. It would be the pid we found last time, |
| * or -1 if not found. |
| * prev_stime: The stime of the process collected last time. |
| * atom_values: The atom we will report later. |
| */ |
| void MmMetricsReporter::fillProcessStime(int atom_key, const std::string &name, int *prev_pid, |
| uint64_t *prev_stime, |
| std::vector<VendorAtomValue> *atom_values) { |
| std::string path; |
| int64_t stime; |
| int64_t stimeDiff; |
| |
| // Find <pid> for executable <name>, and return "/proc/<pid>/stat" path. |
| // Give warning if prev_pid != current pid when prev_pid != -1, which means |
| // <name> at least once died and respawn. |
| path = getProcessStatPath(name, prev_pid); |
| |
| if ((stime = getStimeByPathAndVerifyName(path, name)) < 0) { |
| return; |
| } |
| |
| stimeDiff = stime - *prev_stime; |
| if (stimeDiff < 0) { |
| ALOGE("stime diff for %s < 0: not possible", name.c_str()); |
| return; |
| } |
| *prev_stime = stime; |
| |
| int atom_idx = atom_key - kVendorAtomOffset; |
| int size = atom_idx + 1; |
| VendorAtomValue tmp; |
| tmp.set<VendorAtomValue::longValue>(stimeDiff); |
| if (atom_values->size() < size) |
| atom_values->resize(size, tmp); |
| (*atom_values)[atom_idx] = tmp; |
| } |
| |
| /** |
| * Collect CMA metrics from kPixelStatMm/cma/<cma_type>/<metric> |
| * cma_type: CMA heap name |
| * metrics_info: This is a vector of MmMetricsInfo {metric, atom_key, update_diff}. |
| * Currently, we only collect CMA metrics defined in metrics_info |
| */ |
| std::map<std::string, uint64_t> MmMetricsReporter::readCmaStat( |
| const std::string &cma_type, |
| const std::vector<MmMetricsReporter::MmMetricsInfo> &metrics_info) { |
| uint64_t file_contents; |
| std::map<std::string, uint64_t> cma_stat; |
| for (auto &entry : metrics_info) { |
| std::string path = android::base::StringPrintf("%s/cma/%s/%s", kPixelStatMm, |
| cma_type.c_str(), entry.name.c_str()); |
| if (!ReadFileToUint(getSysfsPath(path.c_str()), &file_contents)) |
| continue; |
| cma_stat[entry.name] = file_contents; |
| } |
| return cma_stat; |
| } |
| |
| /** |
| * This function reads compaction duration sysfs node |
| * (/sys/kernel/pixel_stat/mm/compaction/mm_compaction_duration) |
| * |
| * store: vector to save compaction duration info |
| */ |
| void MmMetricsReporter::readCompactionDurationStat(std::vector<long> *store) { |
| std::string path(getSysfsPath(kCompactDuration)); |
| constexpr int num_metrics = 6; |
| |
| store->resize(num_metrics); |
| |
| int start_idx = 0; |
| int expected_num = num_metrics; |
| |
| if (!ReadFileToLongsCheck(path, store, start_idx, " ", 1, expected_num, true)) { |
| ALOGI("Unable to read %s for the direct reclaim info.", path.c_str()); |
| } |
| } |
| |
| /** |
| * This function fills atom values (values) from acquired compaction duration |
| * information from vector store |
| * |
| * store: the already collected (by readCompactionDurationStat()) compaction |
| * duration information |
| * values: the atom value vector to be filled. |
| */ |
| void MmMetricsReporter::fillCompactionDurationStatAtom(const std::vector<long> &store, |
| std::vector<VendorAtomValue> *values) { |
| // first metric index |
| constexpr int start_idx = |
| PixelMmMetricsPerDay::kCompactionTotalTimeFieldNumber - kVendorAtomOffset; |
| constexpr int num_metrics = 6; |
| |
| if (!MmMetricsSupported()) |
| return; |
| |
| int size = start_idx + num_metrics; |
| if (values->size() < size) |
| values->resize(size); |
| |
| for (int i = 0; i < num_metrics; i++) { |
| VendorAtomValue tmp; |
| if (store[i] == -1) { |
| tmp.set<VendorAtomValue::longValue>(0); |
| } else { |
| tmp.set<VendorAtomValue::longValue>(store[i] - prev_compaction_duration_[i]); |
| prev_compaction_duration_[i] = store[i]; |
| } |
| (*values)[start_idx + i] = tmp; |
| } |
| prev_compaction_duration_ = store; |
| } |
| |
| /** |
| * This function reads direct reclaim sysfs node (4 files: |
| * /sys/kernel/pixel_stat/mm/vmscan/direct_reclaim/<level>/latency_stat, |
| * where <level> = native, top, visible, other.), and save total time and |
| * 4 latency information per file. Total (1+4) x 4 = 20 metrics will be |
| * saved. |
| * |
| * store: vector to save direct reclaim info |
| */ |
| void MmMetricsReporter::readDirectReclaimStat(std::vector<long> *store) { |
| static const std::string base_path(kDirectReclaimBasePath); |
| static const std::vector<std::string> dr_levels{"native", "visible", "top", "other"}; |
| static const std::string sysfs_name = "latency_stat"; |
| constexpr int num_metrics_per_file = 5; |
| int num_file = dr_levels.size(); |
| int num_metrics = num_metrics_per_file * num_file; |
| |
| store->resize(num_metrics); |
| int pass = -1; |
| for (auto level : dr_levels) { |
| ++pass; |
| std::string path = getSysfsPath((base_path + '/' + level + '/' + sysfs_name).c_str()); |
| int start_idx = pass * num_metrics_per_file; |
| int expected_num = num_metrics_per_file; |
| if (!ReadFileToLongsCheck(path, store, start_idx, " ", 1, expected_num, true)) { |
| ALOGI("Unable to read %s for the direct reclaim info.", path.c_str()); |
| } |
| } |
| } |
| |
| /** |
| * This function fills atom values (values) from acquired direct reclaim |
| * information from vector store |
| * |
| * store: the already collected (by readDirectReclaimStat()) direct reclaim |
| * information |
| * values: the atom value vector to be filled. |
| */ |
| void MmMetricsReporter::fillDirectReclaimStatAtom(const std::vector<long> &store, |
| std::vector<VendorAtomValue> *values) { |
| // first metric index |
| constexpr int start_idx = |
| PixelMmMetricsPerDay::kDirectReclaimNativeLatencyTotalTimeFieldNumber - |
| kVendorAtomOffset; |
| |
| constexpr int num_metrics = 20; /* num_metrics_per_file * num_file */ |
| |
| if (!MmMetricsSupported()) |
| return; |
| |
| int size = start_idx + num_metrics; |
| if (values->size() < size) |
| values->resize(size); |
| |
| for (int i = 0; i < num_metrics; i++) { |
| VendorAtomValue tmp; |
| tmp.set<VendorAtomValue::longValue>(store[i] - prev_direct_reclaim_[i]); |
| (*values)[start_idx + i] = tmp; |
| } |
| prev_direct_reclaim_ = store; |
| } |
| |
| /** |
| * This function reads pressure (PSI) files (loop thru all 3 files: cpu, io, and |
| * memory) and calls the parser to parse and store the metric values. |
| * Note that each file have two lines (except cpu has one line only): one with |
| * a leading "full", and the other with a leading "some", showing the category |
| * for that line. |
| * A category has 4 metrics, avg10, avg60, avg300, and total. |
| * i.e. the moving average % of PSI in 10s, 60s, 300s time window plus lastly |
| * the total stalled time, except that 'cpu' has no 'full' category. |
| * In total, we have 3 x 2 x 4 - 4 = 24 - 4 = 20 metrics, arranged in |
| * the order of |
| * |
| * cpu_some_avg<xyz> |
| * cpu_some_total |
| * io_full_avg<xyz> |
| * io_full_total |
| * io_some_avg<xyz> |
| * io_some_total |
| * mem_full_avg<xyz> |
| * mem_full_total |
| * mem_some_avg<xyz> |
| * mem_some_total |
| * |
| * where <xyz>=10, 60, 300 in the order as they appear. |
| * |
| * Note that for those avg values (i.e. <abc>_<def>_avg<xyz>), they |
| * are in percentage with 2-decimal digit accuracy. We will use an |
| * integer in 2-decimal fixed point format to represent the values. |
| * i.e. value x 100, or to cope with floating point errors, |
| * floor(value x 100 + 0.5) |
| * |
| * In fact, in newer kernels, "cpu" PSI has no "full" category. Some |
| * old kernel has them all zeros, to keep backward compatibility. The |
| * parse function called by this function is able to detect and ignore |
| * the "cpu, full" category. |
| * |
| * sample pressure stall files: |
| * /proc/pressure # cat cpu |
| * some avg10=2.93 avg60=3.17 avg300=3.15 total=94628150260 |
| * /proc/pressure # cat io |
| * some avg10=1.06 avg60=1.15 avg300=1.18 total=37709873805 |
| * full avg10=1.06 avg60=1.10 avg300=1.11 total=36592322936 |
| * /proc/pressure # cat memory |
| * some avg10=0.00 avg60=0.00 avg300=0.00 total=29705314 |
| * full avg10=0.00 avg60=0.00 avg300=0.00 total=17234456 |
| * |
| * PSI information definitions could be found at |
| * https://www.kernel.org/doc/html/latest/accounting/psi.html |
| * |
| * basePath: the base path to the pressure stall information |
| * store: pointer to the vector to store the 20 metrics in the mentioned |
| * order |
| */ |
| void MmMetricsReporter::readPressureStall(const std::string &basePath, std::vector<long> *store) { |
| constexpr int kTypeIdxCpu = 0; |
| |
| // Callers should have already prepared this, but we resize it here for safety |
| store->resize(kPsiNumAllMetrics); |
| std::fill(store->begin(), store->end(), -1); |
| |
| // To make the process unified, we prepend an imaginary "cpu + full" |
| // type-category combination. Now, each file (cpu, io, memnry) contains |
| // two categories, i.e. "full" and "some". |
| // Each category has <kPsiNumNames> merics and thus need that many entries |
| // to store them, except that the first category (the imaginary one) do not |
| // need any storage. So we set the save index for the 1st file ("cpu") to |
| // -kPsiNumNames. |
| int file_save_idx = -kPsiNumNames; |
| |
| // loop thru all pressure stall files: cpu, io, memory |
| for (int type_idx = 0; type_idx < kPsiNumFiles; |
| ++type_idx, file_save_idx += kPsiMetricsPerFile) { |
| std::string file_contents; |
| std::string path = getSysfsPath(basePath + '/' + kPsiTypes[type_idx]); |
| |
| if (!ReadFileToString(path, &file_contents)) { |
| // Don't print this log if the file doesn't exist, since logs will be printed |
| // repeatedly. |
| if (errno != ENOENT) |
| ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno)); |
| goto err_out; |
| } |
| if (!MmMetricsReporter::parsePressureStallFileContent(type_idx == kTypeIdxCpu, |
| file_contents, store, file_save_idx)) |
| goto err_out; |
| } |
| return; |
| |
| err_out: |
| std::fill(store->begin(), store->end(), -1); |
| } |
| |
| /* |
| * This function parses a pressure stall file, which contains two |
| * lines, i.e. the "full", and "some" lines, except that the 'cpu' file |
| * contains only one line ("some"). Refer to the function comments of |
| * readPressureStall() for pressure stall file format. |
| * |
| * For old kernel, 'cpu' file might contain an extra line for "full", which |
| * will be ignored. |
| * |
| * is_cpu: Is the data from the file 'cpu' |
| * lines: the file content |
| * store: the output vector to hold the parsed data. |
| * file_save_idx: base index to start saving 'store' vector for this file. |
| * |
| * Return value: true on success, false otherwise. |
| */ |
| bool MmMetricsReporter::parsePressureStallFileContent(bool is_cpu, const std::string &lines, |
| std::vector<long> *store, int file_save_idx) { |
| constexpr int kNumOfWords = 5; // expected number of words separated by spaces. |
| constexpr int kCategoryFull = 0; |
| |
| std::istringstream data(lines); |
| std::string line; |
| |
| while (std::getline(data, line)) { |
| int category_idx = 0; |
| |
| line = android::base::Trim(line); |
| std::vector<std::string> words = android::base::Tokenize(line, " "); |
| if (words.size() != kNumOfWords) { |
| ALOGE("PSI parse fail: num of words = %d != expected %d", |
| static_cast<int>(words.size()), kNumOfWords); |
| return false; |
| } |
| |
| // words[0] should be either "full" or "some", the category name. |
| for (auto &cat : kPsiCategories) { |
| if (words[0].compare(cat) == 0) |
| break; |
| ++category_idx; |
| } |
| if (category_idx == kPsiNumCategories) { |
| ALOGE("PSI parse fail: unknown category %s", words[0].c_str()); |
| return false; |
| } |
| |
| // skip (cpu, full) combination. |
| if (is_cpu && category_idx == kCategoryFull) { |
| ALOGI("kernel: old PSI sysfs node."); |
| continue; |
| } |
| |
| // Now we have separated words in a vector, e.g. |
| // ["some", "avg10=2.93", "avg60=3.17", "avg300=3.15", total=94628150260"] |
| // call parsePressureStallWords to parse them. |
| int line_save_idx = file_save_idx + category_idx * kPsiNumNames; |
| if (!parsePressureStallWords(words, store, line_save_idx)) |
| return false; |
| } |
| return true; |
| } |
| |
| // This function parses the already split words, e.g. |
| // ["some", "avg10=0.00", "avg60=0.00", "avg300=0.00", "total=29705314"], |
| // from a line (category) in a pressure stall file. |
| // |
| // words: the split words in the form of "name=value" |
| // store: the output vector |
| // line_save_idx: the base start index to save in vector for this line (category) |
| // |
| // Return value: true on success, false otherwise. |
| bool MmMetricsReporter::parsePressureStallWords(const std::vector<std::string> &words, |
| std::vector<long> *store, int line_save_idx) { |
| // Skip the first word, which is already parsed by the caller. |
| // All others are value pairs in "name=value" form. |
| // e.g. ["some", "avg10=0.00", "avg60=0.00", "avg300=0.00", "total=29705314"] |
| // "some" is skipped. |
| for (int i = 1; i < words.size(); ++i) { |
| std::vector<std::string> metric = android::base::Tokenize(words[i], "="); |
| if (metric.size() != 2) { |
| ALOGE("%s: parse error (name=value) @ idx %d", __FUNCTION__, i); |
| return false; |
| } |
| if (!MmMetricsReporter::savePressureMetrics(metric[0], metric[1], store, line_save_idx)) |
| return false; |
| } |
| return true; |
| } |
| |
| // This function parses one value pair in "name=value" format, and depending on |
| // the name, save to its proper location in the store vector. |
| // name = "avg10" -> save to index base_save_idx. |
| // name = "avg60" -> save to index base_save_idx + 1. |
| // name = "avg300" -> save to index base_save_idx + 2. |
| // name = "total" -> save to index base_save_idx + 3. |
| // |
| // name: the metrics name |
| // value: the metrics value |
| // store: the output vector |
| // base_save_idx: the base save index |
| // |
| // Return value: true on success, false otherwise. |
| // |
| bool MmMetricsReporter::savePressureMetrics(const std::string &name, const std::string &value, |
| std::vector<long> *store, int base_save_idx) { |
| int name_idx = 0; |
| constexpr int kNameIdxTotal = 3; |
| |
| for (auto &mn : kPsiMetricNames) { |
| if (name.compare(mn) == 0) |
| break; |
| ++name_idx; |
| } |
| if (name_idx == kPsiNumNames) { |
| ALOGE("%s: parse error: unknown metric name.", __FUNCTION__); |
| return false; |
| } |
| |
| long out; |
| if (name_idx == kNameIdxTotal) { |
| // 'total' metrics |
| unsigned long tmp; |
| if (!android::base::ParseUint(value, &tmp)) |
| out = -1; |
| else |
| out = tmp; |
| } else { |
| // 'avg' metrics |
| double d = -1.0; |
| if (android::base::ParseDouble(value, &d)) |
| out = static_cast<long>(d * 100 + 0.5); |
| else |
| out = -1; |
| } |
| |
| if (base_save_idx + name_idx >= store->size()) { |
| // should never reach here |
| ALOGE("out of bound access to store[] (src line %d) @ index %d", __LINE__, |
| base_save_idx + name_idx); |
| return false; |
| } else { |
| (*store)[base_save_idx + name_idx] = out; |
| } |
| return true; |
| } |
| |
| /** |
| * This function reads in the current pressure (PSI) information, and aggregates |
| * it (except for the "total" information, which will overwrite |
| * the previous value without aggregation. |
| * |
| * data are arranged in the following order, and must comply the order defined |
| * in the proto: |
| * |
| * // note: these 5 'total' metrics are not aggregated. |
| * cpu_some_total |
| * io_full_total |
| * io_some_total |
| * mem_full_total |
| * mem_some_total |
| * |
| * // 9 aggregated metrics as above avg<xyz>_<aggregate> |
| * // where <xyz> = 10, 60, 300; <aggregate> = min, max, sum |
| * cpu_some_avg10_min |
| * cpu_some_avg10_max |
| * cpu_some_avg10_sum |
| * cpu_some_avg60_min |
| * cpu_some_avg60_max |
| * cpu_some_avg60_sum |
| * cpu_some_avg300_min |
| * cpu_some_avg300_max |
| * cpu_some_avg300_sum |
| * |
| * // similar 9 metrics as above avg<xyz>_<aggregate> |
| * io_full_avg<xyz>_<aggregate> |
| * |
| * // similar 9 metrics as above avg<xyz>_<aggregate> |
| * io_some_avg<xyz>_<aggregate> |
| * |
| * // similar 9 metrics as above avg<xyz>_<aggregate> |
| * mem_full_avg<xyz>_<aggregate> |
| * |
| * // similar 9 metrics as above avg<xyz>_<aggregate> |
| * mem_some_avg<xyz>_<aggregate> |
| * |
| * In addition, it increases psi_data_set_count_ by 1 (in order to calculate |
| * the average from the "_sum" aggregate.) |
| */ |
| void MmMetricsReporter::aggregatePressureStall() { |
| constexpr int kFirstTotalOffset = kPsiNumAvgs; |
| |
| if (!MmMetricsSupported()) |
| return; |
| |
| std::vector<long> psi(kPsiNumAllMetrics, -1); |
| readPressureStall(kPsiBasePath, &psi); |
| |
| // Pre-check for possible later out of bound error, if readPressureStall() |
| // decreases the vector size. |
| // It's for safety only. The condition should never be true. |
| if (psi.size() != kPsiNumAllMetrics) { |
| ALOGE("Wrong psi[] size %d != expected %d after read.", static_cast<int>(psi.size()), |
| kPsiNumAllMetrics); |
| return; |
| } |
| |
| // check raw metrics and preventively handle errors: Although we don't expect read sysfs |
| // node could fail. Discard all current readings on any error. |
| for (int i = 0; i < kPsiNumAllMetrics; ++i) { |
| if (psi[i] == -1) { |
| ALOGE("Bad data @ psi[%ld] = -1", psi[i]); |
| goto err_out; |
| } |
| } |
| |
| // "total" metrics are accumulative: just replace the previous accumulation. |
| for (int i = 0; i < kPsiNumAllTotals; ++i) { |
| int psi_idx; |
| |
| psi_idx = i * kPsiNumNames + kFirstTotalOffset; |
| if (psi_idx >= psi.size()) { |
| // should never reach here |
| ALOGE("out of bound access to psi[] (src line %d) @ index %d", __LINE__, psi_idx); |
| goto err_out; |
| } else { |
| psi_total_[i] = psi[psi_idx]; |
| } |
| } |
| |
| // "avg" metrics will be aggregated to min, max and sum |
| // later on, the sum will be divided by psi_data_set_count_ to get the average. |
| int aggr_idx; |
| aggr_idx = 0; |
| for (int psi_idx = 0; psi_idx < kPsiNumAllMetrics; ++psi_idx) { |
| if (psi_idx % kPsiNumNames == kFirstTotalOffset) |
| continue; // skip 'total' metrics, already processed. |
| |
| if (aggr_idx + 3 > kPsiNumAllUploadAvgMetrics) { |
| // should never reach here |
| ALOGE("out of bound access to psi_aggregated_[] (src line %d) @ index %d ~ %d", |
| __LINE__, aggr_idx, aggr_idx + 2); |
| return; // give up avgs, but keep totals (so don't go err_out |
| } |
| |
| long value = psi[psi_idx]; |
| if (psi_data_set_count_ == 0) { |
| psi_aggregated_[aggr_idx++] = value; |
| psi_aggregated_[aggr_idx++] = value; |
| psi_aggregated_[aggr_idx++] = value; |
| } else { |
| psi_aggregated_[aggr_idx++] = std::min(value, psi_aggregated_[aggr_idx]); |
| psi_aggregated_[aggr_idx++] = std::max(value, psi_aggregated_[aggr_idx]); |
| psi_aggregated_[aggr_idx++] += value; |
| } |
| } |
| ++psi_data_set_count_; |
| return; |
| |
| err_out: |
| for (int i = 0; i < kPsiNumAllTotals; ++i) psi_total_[i] = -1; |
| } |
| |
| /** |
| * This function fills atom values (values) from psi_aggregated_[] |
| * |
| * values: the atom value vector to be filled. |
| */ |
| void MmMetricsReporter::fillPressureStallAtom(std::vector<VendorAtomValue> *values) { |
| constexpr int avg_of_avg_offset = 2; |
| constexpr int total_start_idx = |
| PixelMmMetricsPerHour::kPsiCpuSomeTotalFieldNumber - kVendorAtomOffset; |
| constexpr int avg_start_idx = total_start_idx + kPsiNumAllTotals; |
| |
| if (!MmMetricsSupported()) |
| return; |
| |
| VendorAtomValue tmp; |
| |
| // The caller should have setup the correct total size, |
| // but we check and extend the size when it's too small for safety. |
| unsigned int min_value_size = total_start_idx + kPsiNumAllUploadMetrics; |
| if (values->size() < min_value_size) |
| values->resize(min_value_size); |
| |
| // "total" metric |
| int metric_idx = total_start_idx; |
| for (int save = 0; save < kPsiNumAllTotals; ++save, ++metric_idx) { |
| if (psi_data_set_count_ == 0) |
| psi_total_[save] = -1; // no data: invalidate the current total |
| |
| // A good difference needs a good previous value and a good current value. |
| if (psi_total_[save] != -1 && prev_psi_total_[save] != -1) |
| tmp.set<VendorAtomValue::longValue>(psi_total_[save] - prev_psi_total_[save]); |
| else |
| tmp.set<VendorAtomValue::longValue>(-1); |
| |
| prev_psi_total_[save] = psi_total_[save]; |
| if (metric_idx >= values->size()) { |
| // should never reach here |
| ALOGE("out of bound access to value[] for psi-total @ index %d", metric_idx); |
| goto cleanup; |
| } else { |
| (*values)[metric_idx] = tmp; |
| } |
| } |
| |
| // "avg" metrics -> aggregate to min, max, and avg of the original avg |
| metric_idx = avg_start_idx; |
| for (int save = 0; save < kPsiNumAllUploadAvgMetrics; ++save, ++metric_idx) { |
| if (psi_data_set_count_) { |
| if (save % kPsiNumOfAggregatedType == avg_of_avg_offset) { |
| // avg of avg |
| tmp.set<VendorAtomValue::intValue>(psi_aggregated_[save] / psi_data_set_count_); |
| } else { |
| // min or max of avg |
| tmp.set<VendorAtomValue::intValue>(psi_aggregated_[save]); |
| } |
| } else { |
| tmp.set<VendorAtomValue::intValue>(-1); |
| } |
| if (metric_idx >= values->size()) { |
| // should never reach here |
| ALOGE("out of bound access to value[] for psi-avg @ index %d", metric_idx); |
| goto cleanup; |
| } else { |
| (*values)[metric_idx] = tmp; |
| } |
| } |
| |
| cleanup: |
| psi_data_set_count_ = 0; |
| } |
| |
| /** |
| * This function is to collect CMA metrics and upload them. |
| * The CMA metrics are collected by readCmaStat(), copied into atom values |
| * by fillAtomValues(), and then uploaded by reportVendorAtom(). The collected |
| * metrics will be stored in prev_cma_stat_ and prev_cma_stat_ext_ according |
| * to its CmaType. |
| * |
| * stats_client: The Stats service |
| * atom_id: The id of atom. It can be PixelAtoms::Atom::kCmaStatus or kCmaStatusExt |
| * cma_type: The name of CMA heap. |
| * cma_name_offset: The offset of the field cma_heap_name in CmaStatus or CmaStatusExt |
| * type_idx: The id of the CMA heap. We add this id in atom values to identify |
| * the CMA status data. |
| * metrics_info: This is a vector of MmMetricsInfo {metric, atom_key, update_diff}. |
| * We only collect metrics defined in metrics_info from CMA heap path. |
| * all_prev_cma_stat: This is the CMA status collected last time. |
| * It is a map containing pairs of {type_idx, cma_stat}, and cma_stat is |
| * a map contains pairs of {metric, cur_value}. |
| * e.g. {CmaType::FARAWIMG, {"alloc_pages_attempts", 100000}, {...}, ....} |
| * is collected from kPixelStatMm/cma/farawimg/alloc_pages_attempts |
| */ |
| void MmMetricsReporter::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) { |
| std::map<std::string, uint64_t> cma_stat = readCmaStat(cma_type, metrics_info); |
| if (!cma_stat.empty()) { |
| std::vector<VendorAtomValue> values; |
| VendorAtomValue tmp; |
| // type is an enum value corresponding to the CMA heap name. Since CMA heap name |
| // can be added/removed/modified, it would take effort to maintain the mapping table. |
| // We would like to store CMA heap name directly, so just set type to 0. |
| tmp.set<VendorAtomValue::intValue>(0); |
| values.push_back(tmp); |
| |
| std::map<std::string, uint64_t> prev_cma_stat; |
| auto entry = all_prev_cma_stat->find(cma_type); |
| if (entry != all_prev_cma_stat->end()) |
| prev_cma_stat = entry->second; |
| |
| bool is_first_atom = (prev_cma_stat.size() == 0) ? true : false; |
| fillAtomValues(metrics_info, cma_stat, &prev_cma_stat, &values); |
| |
| int size = cma_name_offset - kVendorAtomOffset + 1; |
| if (values.size() < size) { |
| values.resize(size, tmp); |
| } |
| tmp.set<VendorAtomValue::stringValue>(cma_type); |
| values[cma_name_offset - kVendorAtomOffset] = tmp; |
| |
| (*all_prev_cma_stat)[cma_type] = prev_cma_stat; |
| if (!is_first_atom) |
| reportVendorAtom(stats_client, atom_id, values, "CmaStatus"); |
| } |
| } |
| |
| /** |
| * Find the CMA heap defined in kCmaTypeInfo, and then call reportCmaStatusAtom() |
| * to collect the CMA metrics from kPixelStatMm/cma/<cma_type> and upload them. |
| */ |
| void MmMetricsReporter::logCmaStatus(const std::shared_ptr<IStats> &stats_client) { |
| if (!MmMetricsSupported()) |
| return; |
| |
| std::string cma_root = android::base::StringPrintf("%s/cma", kPixelStatMm); |
| std::unique_ptr<DIR, int (*)(DIR *)> dir(opendir(cma_root.c_str()), closedir); |
| if (!dir) |
| return; |
| |
| while (struct dirent *dp = readdir(dir.get())) { |
| if (dp->d_type != DT_DIR) |
| continue; |
| |
| std::string cma_type(dp->d_name); |
| |
| reportCmaStatusAtom(stats_client, PixelAtoms::Atom::kCmaStatus, cma_type, |
| CmaStatus::kCmaHeapNameFieldNumber, kCmaStatusInfo, &prev_cma_stat_); |
| reportCmaStatusAtom(stats_client, PixelAtoms::Atom::kCmaStatusExt, cma_type, |
| CmaStatusExt::kCmaHeapNameFieldNumber, kCmaStatusExtInfo, |
| &prev_cma_stat_ext_); |
| } |
| } |
| |
| } // namespace pixel |
| } // namespace google |
| } // namespace hardware |
| } // namespace android |