/* * Copyright (C) 2022 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 ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL) #include "power_files.h" #include #include #include #include #include #include namespace aidl { namespace android { namespace hardware { namespace thermal { namespace implementation { constexpr std::string_view kDeviceType("iio:device"); constexpr std::string_view kIioRootDir("/sys/bus/iio/devices"); constexpr std::string_view kEnergyValueNode("energy_value"); using ::android::base::ReadFileToString; using ::android::base::StringPrintf; namespace { bool calculateAvgPower(std::string_view power_rail, const PowerSample &last_sample, const PowerSample &curr_sample, float *avg_power) { *avg_power = NAN; if (curr_sample.duration == last_sample.duration) { LOG(VERBOSE) << "Power rail " << power_rail.data() << ": has not collected min 2 samples yet"; return true; } else if (curr_sample.duration < last_sample.duration || curr_sample.energy_counter < last_sample.energy_counter) { LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: last_sample=" << last_sample.energy_counter << "(T=" << last_sample.duration << ")" << ", curr_sample=" << curr_sample.energy_counter << "(T=" << curr_sample.duration << ")"; return false; } const auto duration = curr_sample.duration - last_sample.duration; const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter; *avg_power = static_cast(deltaEnergy) / static_cast(duration); LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << *avg_power << ", duration = " << duration << ", deltaEnergy = " << deltaEnergy; return true; } } // namespace bool PowerFiles::registerPowerRailsToWatch(const Json::Value &config) { if (!ParsePowerRailInfo(config, &power_rail_info_map_)) { LOG(ERROR) << "Failed to parse power rail info config"; return false; } if (!power_rail_info_map_.size()) { LOG(INFO) << " No power rail info config found"; return true; } if (!findEnergySourceToWatch()) { LOG(ERROR) << "Cannot find energy source"; return false; } if (!energy_info_map_.size() && !updateEnergyValues()) { LOG(ERROR) << "Faield to update energy info"; return false; } for (const auto &power_rail_info_pair : power_rail_info_map_) { std::vector> power_history; if (!power_rail_info_pair.second.power_sample_count || power_rail_info_pair.second.power_sample_delay == std::chrono::milliseconds::max()) { continue; } if (power_rail_info_pair.second.virtual_power_rail_info != nullptr && power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size()) { for (size_t i = 0; i < power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size(); ++i) { std::string power_rail = power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails[i]; if (!energy_info_map_.count(power_rail)) { LOG(ERROR) << " Could not find energy source " << power_rail; return false; } const auto curr_sample = energy_info_map_.at(power_rail); power_history.emplace_back(std::queue()); for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) { power_history[i].emplace(curr_sample); } } } else { if (energy_info_map_.count(power_rail_info_pair.first)) { const auto curr_sample = energy_info_map_.at(power_rail_info_pair.first); power_history.emplace_back(std::queue()); for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) { power_history[0].emplace(curr_sample); } } else { LOG(ERROR) << "Could not find energy source " << power_rail_info_pair.first; return false; } } if (power_history.size()) { power_status_map_[power_rail_info_pair.first] = { .last_update_time = boot_clock::time_point::min(), .power_history = power_history, .last_updated_avg_power = NAN, }; } else { LOG(ERROR) << "power history size is zero"; return false; } LOG(INFO) << "Successfully to register power rail " << power_rail_info_pair.first; } power_status_log_ = {.prev_log_time = boot_clock::now(), .prev_energy_info_map = energy_info_map_}; return true; } bool PowerFiles::findEnergySourceToWatch(void) { std::string devicePath; if (energy_path_set_.size()) { return true; } std::unique_ptr dir(opendir(kIioRootDir.data()), closedir); if (!dir) { PLOG(ERROR) << "Error opening directory" << kIioRootDir; return false; } // Find any iio:devices that support energy_value while (struct dirent *ent = readdir(dir.get())) { std::string devTypeDir = ent->d_name; if (devTypeDir.find(kDeviceType) != std::string::npos) { devicePath = StringPrintf("%s/%s", kIioRootDir.data(), devTypeDir.data()); std::string deviceEnergyContent; if (!ReadFileToString(StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()), &deviceEnergyContent)) { } else if (deviceEnergyContent.size()) { energy_path_set_.emplace( StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data())); } } } if (!energy_path_set_.size()) { return false; } return true; } bool PowerFiles::updateEnergyValues(void) { std::string deviceEnergyContent; std::string deviceEnergyContents; std::string line; ATRACE_CALL(); for (const auto &path : energy_path_set_) { if (!::android::base::ReadFileToString(path, &deviceEnergyContent)) { LOG(ERROR) << "Failed to read energy content from " << path; return false; } else { deviceEnergyContents.append(deviceEnergyContent); } } std::istringstream energyData(deviceEnergyContents); while (std::getline(energyData, line)) { /* Read rail energy */ uint64_t energy_counter = 0; uint64_t duration = 0; /* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */ auto start_pos = line.find("T="); auto end_pos = line.find(')'); if (start_pos != std::string::npos) { duration = strtoul(line.substr(start_pos + 2, end_pos - start_pos - 2).c_str(), NULL, 10); } else { continue; } start_pos = line.find(")["); end_pos = line.find(']'); std::string railName; if (start_pos != std::string::npos) { railName = line.substr(start_pos + 2, end_pos - start_pos - 2); } else { continue; } start_pos = line.find("],"); if (start_pos != std::string::npos) { energy_counter = strtoul(line.substr(start_pos + 2).c_str(), NULL, 10); } else { continue; } energy_info_map_[railName] = { .energy_counter = energy_counter, .duration = duration, }; } return true; } float PowerFiles::updateAveragePower(std::string_view power_rail, std::queue *power_history) { float avg_power = NAN; if (!energy_info_map_.count(power_rail.data())) { LOG(ERROR) << " Could not find power rail " << power_rail.data(); return avg_power; } const auto last_sample = power_history->front(); const auto curr_sample = energy_info_map_.at(power_rail.data()); if (calculateAvgPower(power_rail, last_sample, curr_sample, &avg_power)) { power_history->pop(); power_history->push(curr_sample); } return avg_power; } float PowerFiles::updatePowerRail(std::string_view power_rail) { float avg_power = NAN; if (!power_rail_info_map_.count(power_rail.data())) { return avg_power; } if (!power_status_map_.count(power_rail.data())) { return avg_power; } const auto &power_rail_info = power_rail_info_map_.at(power_rail.data()); auto &power_status = power_status_map_.at(power_rail.data()); boot_clock::time_point now = boot_clock::now(); auto time_elapsed_ms = std::chrono::duration_cast( now - power_status.last_update_time); if (power_status.last_update_time != boot_clock::time_point::min() && time_elapsed_ms < power_rail_info.power_sample_delay) { return power_status.last_updated_avg_power; } if (!energy_info_map_.size() && !updateEnergyValues()) { LOG(ERROR) << "Failed to update energy values"; return avg_power; } if (power_rail_info.virtual_power_rail_info == nullptr) { avg_power = updateAveragePower(power_rail, &power_status.power_history[0]); } else { const auto offset = power_rail_info.virtual_power_rail_info->offset; float avg_power_val = 0.0; for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size(); i++) { float coefficient = power_rail_info.virtual_power_rail_info->coefficients[i]; float avg_power_number = updateAveragePower( power_rail_info.virtual_power_rail_info->linked_power_rails[i], &power_status.power_history[i]); switch (power_rail_info.virtual_power_rail_info->formula) { case FormulaOption::COUNT_THRESHOLD: if ((coefficient < 0 && avg_power_number < -coefficient) || (coefficient >= 0 && avg_power_number >= coefficient)) avg_power_val += 1; break; case FormulaOption::WEIGHTED_AVG: avg_power_val += avg_power_number * coefficient; break; case FormulaOption::MAXIMUM: if (i == 0) avg_power_val = std::numeric_limits::lowest(); if (avg_power_number * coefficient > avg_power_val) avg_power_val = avg_power_number * coefficient; break; case FormulaOption::MINIMUM: if (i == 0) avg_power_val = std::numeric_limits::max(); if (avg_power_number * coefficient < avg_power_val) avg_power_val = avg_power_number * coefficient; break; default: break; } } if (avg_power_val >= 0) { avg_power_val = avg_power_val + offset; } avg_power = avg_power_val; } if (avg_power < 0) { avg_power = NAN; } power_status.last_updated_avg_power = avg_power; power_status.last_update_time = now; return avg_power; } bool PowerFiles::refreshPowerStatus(void) { if (!updateEnergyValues()) { LOG(ERROR) << "Failed to update energy values"; return false; } for (const auto &power_status_pair : power_status_map_) { updatePowerRail(power_status_pair.first); } return true; } void PowerFiles::logPowerStatus(const boot_clock::time_point &now) { // calculate energy and print uint8_t power_rail_log_cnt = 0; uint64_t max_duration = 0; float tot_power = 0.0; std::string out; for (const auto &energy_info_pair : energy_info_map_) { const auto &rail = energy_info_pair.first; if (!power_status_log_.prev_energy_info_map.count(rail)) { continue; } const auto &last_sample = power_status_log_.prev_energy_info_map.at(rail); const auto &curr_sample = energy_info_pair.second; float avg_power = NAN; if (calculateAvgPower(rail, last_sample, curr_sample, &avg_power) && !std::isnan(avg_power)) { // start of new line if (power_rail_log_cnt % kMaxPowerLogPerLine == 0) { if (power_rail_log_cnt != 0) { out.append("\n"); } out.append("Power rails "); } out.append(StringPrintf("[%s: %0.2f mW] ", rail.c_str(), avg_power)); power_rail_log_cnt++; tot_power += avg_power; max_duration = std::max(max_duration, curr_sample.duration - last_sample.duration); } } if (power_rail_log_cnt) { LOG(INFO) << StringPrintf("Power rails total power: %0.2f mW for %" PRId64 " ms", tot_power, max_duration); LOG(INFO) << out; } power_status_log_ = {.prev_log_time = now, .prev_energy_info_map = energy_info_map_}; } } // namespace implementation } // namespace thermal } // namespace hardware } // namespace android } // namespace aidl