You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
17 KiB

/*
* 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.
*/
#include <dirent.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "power_files.h"
namespace android {
namespace hardware {
namespace thermal {
namespace V2_0 {
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;
void PowerFiles::setPowerDataToDefault(std::string_view sensor_name) {
std::unique_lock<std::shared_mutex> _lock(throttling_release_map_mutex_);
if (!throttling_release_map_.count(sensor_name.data()) ||
!power_status_map_.count(sensor_name.data())) {
return;
}
auto &cdev_release_map = throttling_release_map_.at(sensor_name.data());
PowerSample power_sample = {};
for (auto &power_status_pair : power_status_map_.at(sensor_name.data())) {
for (size_t i = 0; i < power_status_pair.second.power_history.size(); ++i) {
for (size_t j = 0; j < power_status_pair.second.power_history[i].size(); ++j) {
power_status_pair.second.power_history[i].pop();
power_status_pair.second.power_history[i].emplace(power_sample);
}
}
power_status_pair.second.last_updated_avg_power = NAN;
}
for (auto &cdev_release_pair : cdev_release_map) {
cdev_release_pair.second.release_step = 0;
}
}
int PowerFiles::getReleaseStep(std::string_view sensor_name, std::string_view cdev_name) {
int release_step = 0;
std::shared_lock<std::shared_mutex> _lock(throttling_release_map_mutex_);
if (throttling_release_map_.count(sensor_name.data()) &&
throttling_release_map_[sensor_name.data()].count(cdev_name.data())) {
release_step = throttling_release_map_[sensor_name.data()][cdev_name.data()].release_step;
}
return release_step;
}
bool PowerFiles::registerPowerRailsToWatch(std::string_view sensor_name, std::string_view cdev_name,
const BindedCdevInfo &binded_cdev_info,
const CdevInfo &cdev_info,
const PowerRailInfo &power_rail_info) {
std::vector<std::queue<PowerSample>> power_history;
PowerSample power_sample = {
.energy_counter = 0,
.duration = 0,
};
if (throttling_release_map_.count(sensor_name.data()) &&
throttling_release_map_[sensor_name.data()].count(binded_cdev_info.power_rail)) {
return true;
}
if (!energy_info_map_.size() && !updateEnergyValues()) {
LOG(ERROR) << "Faield to update energy info";
return false;
}
if (power_rail_info.virtual_power_rail_info != nullptr &&
power_rail_info.virtual_power_rail_info->linked_power_rails.size()) {
for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
++i) {
if (energy_info_map_.count(
power_rail_info.virtual_power_rail_info->linked_power_rails[i])) {
power_history.emplace_back(std::queue<PowerSample>());
for (int j = 0; j < power_rail_info.power_sample_count; j++) {
power_history[i].emplace(power_sample);
}
}
}
} else {
if (energy_info_map_.count(power_rail_info.rail)) {
power_history.emplace_back(std::queue<PowerSample>());
for (int j = 0; j < power_rail_info.power_sample_count; j++) {
power_history[0].emplace(power_sample);
}
}
}
if (power_history.size()) {
throttling_release_map_[sensor_name.data()][cdev_name.data()] = {
.release_step = 0,
.max_release_step = cdev_info.max_state,
};
power_status_map_[sensor_name.data()][binded_cdev_info.power_rail] = {
.power_history = power_history,
.time_remaining = power_rail_info.power_sample_delay,
.last_updated_avg_power = NAN,
};
} else {
return false;
}
LOG(INFO) << "Sensor " << sensor_name.data() << " successfully registers power rail "
<< binded_cdev_info.power_rail << " for cooling device " << cdev_name.data();
return true;
}
bool PowerFiles::findEnergySourceToWatch(void) {
std::string devicePath;
if (energy_path_set_.size()) {
return true;
}
std::unique_ptr<DIR, decltype(&closedir)> 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;
}
void PowerFiles::clearEnergyInfoMap(void) {
energy_info_map_.clear();
}
bool PowerFiles::updateEnergyValues(void) {
std::string deviceEnergyContent;
std::string deviceEnergyContents;
std::string line;
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);
clearEnergyInfoMap();
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;
}
bool PowerFiles::getAveragePower(std::string_view power_rail,
std::queue<PowerSample> *power_history, bool power_sample_update,
float *avg_power) {
const auto curr_sample = energy_info_map_.at(power_rail.data());
bool ret = true;
const auto last_sample = power_history->front();
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() << ": the last energy timestamp is zero";
} else if (duration <= 0 || deltaEnergy < 0) {
LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: duration = " << duration
<< ", deltaEnergy = " << deltaEnergy;
ret = false;
} else {
*avg_power = static_cast<float>(deltaEnergy) / static_cast<float>(duration);
LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << *avg_power
<< ", duration = " << duration << ", deltaEnergy = " << deltaEnergy;
}
if (power_sample_update) {
power_history->pop();
power_history->push(curr_sample);
}
return ret;
}
bool PowerFiles::computeAveragePower(const PowerRailInfo &power_rail_info,
PowerStatus *power_status, bool power_sample_update,
float *avg_power) {
bool ret = true;
float avg_power_val = -1;
float offset = power_rail_info.virtual_power_rail_info->offset;
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 = -1;
if (!getAveragePower(power_rail_info.virtual_power_rail_info->linked_power_rails[i],
&power_status->power_history[i], power_sample_update,
&avg_power_number)) {
ret = false;
continue;
} else if (avg_power_number < 0) {
continue;
}
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<float>::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<float>::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;
return ret;
}
bool PowerFiles::throttlingReleaseUpdate(std::string_view sensor_name, std::string_view cdev_name,
const ThrottlingSeverity severity,
const std::chrono::milliseconds time_elapsed_ms,
const BindedCdevInfo &binded_cdev_info,
const PowerRailInfo &power_rail_info,
bool power_sample_update, bool severity_changed) {
std::unique_lock<std::shared_mutex> _lock(throttling_release_map_mutex_);
float avg_power = -1;
if (!throttling_release_map_.count(sensor_name.data()) ||
!throttling_release_map_[sensor_name.data()].count(cdev_name.data()) ||
!power_status_map_.count(sensor_name.data()) ||
!power_status_map_[sensor_name.data()].count(binded_cdev_info.power_rail)) {
return false;
}
auto &release_status = throttling_release_map_[sensor_name.data()].at(cdev_name.data());
auto &power_status = power_status_map_[sensor_name.data()].at(binded_cdev_info.power_rail);
if (power_sample_update) {
if (time_elapsed_ms > power_status.time_remaining) {
power_status.time_remaining = power_rail_info.power_sample_delay;
} else {
power_status.time_remaining = power_status.time_remaining - time_elapsed_ms;
LOG(VERBOSE) << "Power rail " << binded_cdev_info.power_rail
<< " : timeout remaining = " << power_status.time_remaining.count();
if (!severity_changed) {
return true;
} else {
// get the cached average power when thermal severity is changed
power_sample_update = false;
}
}
} else if (!severity_changed &&
power_status.time_remaining != power_rail_info.power_sample_delay) {
return false;
}
if (!energy_info_map_.size() && !updateEnergyValues()) {
LOG(ERROR) << "Failed to update energy values";
release_status.release_step = 0;
return false;
}
if (!power_sample_update && !std::isnan(power_status.last_updated_avg_power)) {
avg_power = power_status.last_updated_avg_power;
} else {
// Return false if we cannot get the average power of the target power rail
if (!((power_rail_info.virtual_power_rail_info == nullptr)
? getAveragePower(binded_cdev_info.power_rail, &power_status.power_history[0],
power_sample_update, &avg_power)
: computeAveragePower(power_rail_info, &power_status, power_sample_update,
&avg_power))) {
release_status.release_step = 0;
if (binded_cdev_info.throttling_with_power_link) {
release_status.release_step = release_status.max_release_step;
}
return false;
} else if (avg_power < 0) {
if (binded_cdev_info.throttling_with_power_link) {
release_status.release_step = release_status.max_release_step;
}
return true;
}
}
power_status.last_updated_avg_power = avg_power;
bool is_over_budget = true;
if (!binded_cdev_info.high_power_check) {
if (avg_power < binded_cdev_info.power_thresholds[static_cast<int>(severity)]) {
is_over_budget = false;
}
} else {
if (avg_power > binded_cdev_info.power_thresholds[static_cast<int>(severity)]) {
is_over_budget = false;
}
}
LOG(INFO) << "Power rail " << binded_cdev_info.power_rail << ": power threshold = "
<< binded_cdev_info.power_thresholds[static_cast<int>(severity)]
<< ", avg power = " << avg_power;
switch (binded_cdev_info.release_logic) {
case ReleaseLogic::INCREASE:
if (!is_over_budget) {
if (std::abs(release_status.release_step) <
static_cast<int>(release_status.max_release_step)) {
release_status.release_step--;
}
} else {
release_status.release_step = 0;
}
break;
case ReleaseLogic::DECREASE:
if (!is_over_budget) {
if (release_status.release_step <
static_cast<int>(release_status.max_release_step)) {
release_status.release_step++;
}
} else {
release_status.release_step = 0;
}
break;
case ReleaseLogic::STEPWISE:
if (!is_over_budget) {
if (release_status.release_step <
static_cast<int>(release_status.max_release_step)) {
release_status.release_step++;
}
} else {
if (std::abs(release_status.release_step) <
static_cast<int>(release_status.max_release_step)) {
release_status.release_step--;
}
}
break;
case ReleaseLogic::RELEASE_TO_FLOOR:
release_status.release_step = is_over_budget ? 0 : release_status.max_release_step;
break;
case ReleaseLogic::NONE:
default:
break;
}
return true;
}
} // namespace implementation
} // namespace V2_0
} // namespace thermal
} // namespace hardware
} // namespace android