/* * 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; 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; } PowerSample power_sample = { .energy_counter = 0, .duration = 0, }; 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) { if (!energy_info_map_.count(power_rail_info_pair.second.virtual_power_rail_info ->linked_power_rails[i])) { LOG(ERROR) << " Could not find energy source " << power_rail_info_pair.second.virtual_power_rail_info ->linked_power_rails[i]; return false; } power_history.emplace_back(std::queue()); for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) { power_history[i].emplace(power_sample); } } } else { if (energy_info_map_.count(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(power_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] = { .power_history = power_history, .last_update_time = boot_clock::time_point::min(), .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; } 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()); const auto duration = curr_sample.duration - last_sample.duration; const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter; if (!last_sample.duration) { LOG(VERBOSE) << "Power rail " << power_rail.data() << ": all power samples have not been collected yet"; } else if (duration <= 0 || deltaEnergy < 0) { LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: duration = " << duration << ", deltaEnergy = " << deltaEnergy; return avg_power; } else { avg_power = static_cast(deltaEnergy) / static_cast(duration); LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << avg_power << ", duration = " << duration << ", deltaEnergy = " << deltaEnergy; } 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; } } // namespace implementation } // namespace thermal } // namespace hardware } // namespace android } // namespace aidl