/* * 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. */ #include "thermal_stats_helper.h" #include #include #include #include #include #include namespace aidl { namespace android { namespace hardware { namespace thermal { namespace implementation { constexpr std::string_view kCustomThresholdSetSuffix("-TH-"); constexpr std::string_view kCompressedThresholdSuffix("-CMBN-TH"); using aidl::android::frameworks::stats::VendorAtom; namespace PixelAtoms = ::android::hardware::google::pixel::PixelAtoms; namespace { static std::shared_ptr stats_client = nullptr; std::shared_ptr getStatsService() { static std::once_flag statsServiceFlag; std::call_once(statsServiceFlag, []() { const std::string instance = std::string() + IStats::descriptor + "/default"; bool isStatsDeclared = AServiceManager_isDeclared(instance.c_str()); if (!isStatsDeclared) { LOG(ERROR) << "Stats service is not registered."; return; } stats_client = IStats::fromBinder( ndk::SpAIBinder(AServiceManager_waitForService(instance.c_str()))); }); return stats_client; } bool isRecordByDefaultThreshold(const std::variant> &record_by_default_threshold_all_or_name_set_, std::string_view name) { if (std::holds_alternative(record_by_default_threshold_all_or_name_set_)) { return std::get(record_by_default_threshold_all_or_name_set_); } return std::get>(record_by_default_threshold_all_or_name_set_) .count(name.data()); } template int calculateThresholdBucket(const std::vector &thresholds, T value) { if (thresholds.empty()) { LOG(VERBOSE) << "No threshold present, so bucket is " << value << " as int."; return static_cast(value); } auto threshold_idx = std::upper_bound(thresholds.begin(), thresholds.end(), value); int bucket = (threshold_idx - thresholds.begin()); LOG(VERBOSE) << "For value: " << value << " bucket is: " << bucket; return bucket; } } // namespace bool ThermalStatsHelper::initializeStats( const Json::Value &config, const std::unordered_map &sensor_info_map_, const std::unordered_map &cooling_device_info_map_) { StatsConfig stats_config; if (!ParseStatsConfig(config, sensor_info_map_, cooling_device_info_map_, &stats_config)) { LOG(ERROR) << "Failed to parse stats config"; return false; } bool is_initialized_ = initializeSensorTempStats(stats_config.sensor_stats_info, sensor_info_map_) && initializeSensorCdevRequestStats(stats_config.cooling_device_request_info, sensor_info_map_, cooling_device_info_map_); if (is_initialized_) { last_total_stats_report_time = boot_clock::now(); LOG(INFO) << "Thermal Stats Initialized Successfully"; } return is_initialized_; } bool ThermalStatsHelper::initializeSensorCdevRequestStats( const StatsInfo &request_stats_info, const std::unordered_map &sensor_info_map_, const std::unordered_map &cooling_device_info_map_) { std::unique_lock _lock(sensor_cdev_request_stats_map_mutex_); for (const auto &[sensor, sensor_info] : sensor_info_map_) { for (const auto &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) { const auto &cdev = binded_cdev_info_pair.first; const auto &max_state = cooling_device_info_map_.at(binded_cdev_info_pair.first).max_state; // Record by all state if (isRecordByDefaultThreshold( request_stats_info.record_by_default_threshold_all_or_name_set_, cdev)) { // if the number of states is greater / equal(as state starts from 0) than // residency_buckets in atom combine the initial states if (max_state >= kMaxStatsResidencyCount) { // buckets = [max_state -kMaxStatsResidencyCount + 1, ...max_state] // idx = [1, .. max_state - (max_state - kMaxStatsResidencyCount + 1) + 1] // idx = [1, .. kMaxStatsResidencyCount] const auto starting_state = max_state - kMaxStatsResidencyCount + 1; std::vector thresholds(kMaxStatsResidencyCount); std::iota(thresholds.begin(), thresholds.end(), starting_state); const auto logging_name = cdev + kCompressedThresholdSuffix.data(); ThresholdList threshold_list(logging_name, thresholds); sensor_cdev_request_stats_map_[sensor][cdev] .stats_by_custom_threshold.emplace_back(threshold_list); } else { // buckets = [0, 1, 2, 3, ...max_state] const auto default_threshold_time_in_state_size = max_state + 1; sensor_cdev_request_stats_map_[sensor][cdev].stats_by_default_threshold = StatsRecord(default_threshold_time_in_state_size); } LOG(INFO) << "Sensor Cdev user vote stats on basis of all state initialized for [" << sensor << "-" << cdev << "]"; } // Record by custom threshold if (request_stats_info.record_by_threshold.count(cdev)) { for (const auto &threshold_list : request_stats_info.record_by_threshold.at(cdev)) { // check last threshold value(which is >= number of buckets as numbers in // threshold are strictly increasing from 0) is less than max_state if (threshold_list.thresholds.back() >= max_state) { LOG(ERROR) << "For sensor " << sensor << " bindedCdev: " << cdev << "Invalid bindedCdev stats threshold: " << threshold_list.thresholds.back() << " >= " << max_state; sensor_cdev_request_stats_map_.clear(); return false; } sensor_cdev_request_stats_map_[sensor][cdev] .stats_by_custom_threshold.emplace_back(threshold_list); LOG(INFO) << "Sensor Cdev user vote stats on basis of threshold initialized for [" << sensor << "-" << cdev << "]"; } } } } return true; } bool ThermalStatsHelper::initializeSensorTempStats( const StatsInfo &sensor_stats_info, const std::unordered_map &sensor_info_map_) { std::unique_lock _lock(sensor_temp_stats_map_mutex_); const int severity_time_in_state_size = kThrottlingSeverityCount; for (const auto &[sensor, sensor_info] : sensor_info_map_) { // Record by severity if (sensor_info.is_watch && isRecordByDefaultThreshold( sensor_stats_info.record_by_default_threshold_all_or_name_set_, sensor)) { // number of buckets = number of severity sensor_temp_stats_map_[sensor].stats_by_default_threshold = StatsRecord(severity_time_in_state_size); LOG(INFO) << "Sensor temp stats on basis of severity initialized for [" << sensor << "]"; } // Record by custom threshold if (sensor_stats_info.record_by_threshold.count(sensor)) { for (const auto &threshold_list : sensor_stats_info.record_by_threshold.at(sensor)) { sensor_temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back( threshold_list); LOG(INFO) << "Sensor temp stats on basis of threshold initialized for [" << sensor << "]"; } } } return true; } void ThermalStatsHelper::updateStatsRecord(StatsRecord *stats_record, int new_state) { const auto now = boot_clock::now(); const auto cur_state_duration = std::chrono::duration_cast( now - stats_record->cur_state_start_time); LOG(VERBOSE) << "Adding duration " << cur_state_duration.count() << " for cur_state: " << stats_record->cur_state << " with value: " << stats_record->time_in_state_ms[stats_record->cur_state].count(); // Update last record end time stats_record->time_in_state_ms[stats_record->cur_state] += cur_state_duration; stats_record->cur_state_start_time = now; stats_record->cur_state = new_state; } void ThermalStatsHelper::updateSensorCdevRequestStats(std::string_view sensor, std::string_view cdev, int new_value) { std::unique_lock _lock(sensor_cdev_request_stats_map_mutex_); if (!sensor_cdev_request_stats_map_.count(sensor.data()) || !sensor_cdev_request_stats_map_[sensor.data()].count(cdev.data())) { return; } auto &request_stats = sensor_cdev_request_stats_map_[sensor.data()][cdev.data()]; for (auto &stats_by_threshold : request_stats.stats_by_custom_threshold) { int value = calculateThresholdBucket(stats_by_threshold.thresholds, new_value); if (value != stats_by_threshold.stats_record.cur_state) { LOG(VERBOSE) << "Updating bindedCdev stats for sensor: " << sensor.data() << " , cooling_device: " << cdev.data() << " with new value: " << value; updateStatsRecord(&stats_by_threshold.stats_record, value); } } if (request_stats.stats_by_default_threshold.has_value()) { auto &stats_record = request_stats.stats_by_default_threshold.value(); if (new_value != stats_record.cur_state) { LOG(VERBOSE) << "Updating bindedCdev stats for sensor: " << sensor.data() << " , cooling_device: " << cdev.data() << " with new value: " << new_value; updateStatsRecord(&stats_record, new_value); } } } void ThermalStatsHelper::updateSensorTempStatsByThreshold(std::string_view sensor, float temperature) { std::unique_lock _lock(sensor_temp_stats_map_mutex_); if (!sensor_temp_stats_map_.count(sensor.data())) { return; } auto &sensor_temp_stats = sensor_temp_stats_map_[sensor.data()]; for (auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) { int value = calculateThresholdBucket(stats_by_threshold.thresholds, temperature); if (value != stats_by_threshold.stats_record.cur_state) { LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data() << " with value: " << value; updateStatsRecord(&stats_by_threshold.stats_record, value); } } if (temperature > sensor_temp_stats.max_temp) { sensor_temp_stats.max_temp = temperature; sensor_temp_stats.max_temp_timestamp = system_clock::now(); } if (temperature < sensor_temp_stats.min_temp) { sensor_temp_stats.min_temp = temperature; sensor_temp_stats.min_temp_timestamp = system_clock::now(); } } void ThermalStatsHelper::updateSensorTempStatsBySeverity(std::string_view sensor, const ThrottlingSeverity &severity) { std::unique_lock _lock(sensor_temp_stats_map_mutex_); if (sensor_temp_stats_map_.count(sensor.data()) && sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) { auto &stats_record = sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.value(); int value = static_cast(severity); if (value != stats_record.cur_state) { LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data() << " with value: " << value; updateStatsRecord(&stats_record, value); } } } int ThermalStatsHelper::reportStats() { const auto curTime = boot_clock::now(); const auto since_last_total_stats_update_ms = std::chrono::duration_cast(curTime - last_total_stats_report_time); LOG(VERBOSE) << "Duration from last total stats update is: " << since_last_total_stats_update_ms.count(); if (since_last_total_stats_update_ms < kUpdateIntervalMs) { LOG(VERBOSE) << "Time elapsed since last update less than " << kUpdateIntervalMs.count(); return 0; } const std::shared_ptr stats_client = getStatsService(); if (!stats_client) { LOG(ERROR) << "Unable to get AIDL Stats service"; return -1; } int count_failed_reporting = reportAllSensorTempStats(stats_client) + reportAllSensorCdevRequestStats(stats_client); last_total_stats_report_time = curTime; return count_failed_reporting; } int ThermalStatsHelper::reportAllSensorTempStats(const std::shared_ptr &stats_client) { int count_failed_reporting = 0; std::unique_lock _lock(sensor_temp_stats_map_mutex_); for (auto &[sensor, temp_stats] : sensor_temp_stats_map_) { for (size_t threshold_set_idx = 0; threshold_set_idx < temp_stats.stats_by_custom_threshold.size(); threshold_set_idx++) { auto &stats_by_threshold = temp_stats.stats_by_custom_threshold[threshold_set_idx]; std::string sensor_name = stats_by_threshold.logging_name.value_or( sensor + kCustomThresholdSetSuffix.data() + std::to_string(threshold_set_idx)); if (!reportSensorTempStats(stats_client, sensor_name, temp_stats, &stats_by_threshold.stats_record)) { count_failed_reporting++; } } if (temp_stats.stats_by_default_threshold.has_value()) { if (!reportSensorTempStats(stats_client, sensor, temp_stats, &temp_stats.stats_by_default_threshold.value())) { count_failed_reporting++; } } } return count_failed_reporting; } bool ThermalStatsHelper::reportSensorTempStats(const std::shared_ptr &stats_client, std::string_view sensor, const SensorTempStats &sensor_temp_stats, StatsRecord *stats_record) { LOG(VERBOSE) << "Reporting sensor stats for " << sensor; // maintain a copy in case reporting fails StatsRecord thermal_stats_before_reporting = *stats_record; std::vector values(2); values[0].set(sensor); std::vector time_in_state_ms = processStatsRecordForReporting(stats_record); const auto since_last_update_ms = std::chrono::duration_cast( stats_record->cur_state_start_time - stats_record->last_stats_report_time); values[1].set(since_last_update_ms.count()); VendorAtomValue tmp; for (auto &time_in_state : time_in_state_ms) { tmp.set(time_in_state); values.push_back(tmp); } auto remaining_residency_buckets_count = kMaxStatsResidencyCount - time_in_state_ms.size(); if (remaining_residency_buckets_count > 0) { tmp.set(0); values.insert(values.end(), remaining_residency_buckets_count, tmp); } tmp.set(sensor_temp_stats.max_temp); values.push_back(tmp); tmp.set( system_clock::to_time_t(sensor_temp_stats.max_temp_timestamp)); values.push_back(tmp); tmp.set(sensor_temp_stats.min_temp); values.push_back(tmp); tmp.set( system_clock::to_time_t(sensor_temp_stats.min_temp_timestamp)); values.push_back(tmp); if (!reportAtom(stats_client, PixelAtoms::Atom::kVendorTempResidencyStats, std::move(values))) { LOG(ERROR) << "Unable to report VendorTempResidencyStats to Stats service for " "sensor: " << sensor; *stats_record = restoreStatsRecordOnFailure(std::move(thermal_stats_before_reporting)); return false; } // Update last time of stats reporting stats_record->last_stats_report_time = boot_clock::now(); return true; } int ThermalStatsHelper::reportAllSensorCdevRequestStats( const std::shared_ptr &stats_client) { int count_failed_reporting = 0; std::unique_lock _lock(sensor_cdev_request_stats_map_mutex_); for (auto &[sensor, cdev_request_stats_map] : sensor_cdev_request_stats_map_) { for (auto &[cdev, request_stats] : cdev_request_stats_map) { for (size_t threshold_set_idx = 0; threshold_set_idx < request_stats.stats_by_custom_threshold.size(); threshold_set_idx++) { auto &stats_by_threshold = request_stats.stats_by_custom_threshold[threshold_set_idx]; std::string cdev_name = stats_by_threshold.logging_name.value_or( cdev + kCustomThresholdSetSuffix.data() + std::to_string(threshold_set_idx)); if (!reportSensorCdevRequestStats(stats_client, sensor, cdev_name, &stats_by_threshold.stats_record)) { count_failed_reporting++; } } if (request_stats.stats_by_default_threshold.has_value()) { if (!reportSensorCdevRequestStats( stats_client, sensor, cdev, &request_stats.stats_by_default_threshold.value())) { count_failed_reporting++; } } } } return count_failed_reporting; } bool ThermalStatsHelper::reportSensorCdevRequestStats(const std::shared_ptr &stats_client, std::string_view sensor, std::string_view cdev, StatsRecord *stats_record) { LOG(VERBOSE) << "Reporting bindedCdev stats for sensor: " << sensor << " cooling_device: " << cdev; // maintain a copy in case reporting fails StatsRecord thermal_stats_before_reporting = *stats_record; std::vector values(3); values[0].set(sensor); values[1].set(cdev); std::vector time_in_state_ms = processStatsRecordForReporting(stats_record); const auto since_last_update_ms = std::chrono::duration_cast( stats_record->cur_state_start_time - stats_record->last_stats_report_time); values[2].set(since_last_update_ms.count()); VendorAtomValue tmp; for (auto &time_in_state : time_in_state_ms) { tmp.set(time_in_state); values.push_back(tmp); } if (!reportAtom(stats_client, PixelAtoms::Atom::kVendorSensorCoolingDeviceStats, std::move(values))) { LOG(ERROR) << "Unable to report VendorSensorCoolingDeviceStats to Stats " "service for sensor: " << sensor << " cooling_device: " << cdev; *stats_record = restoreStatsRecordOnFailure(std::move(thermal_stats_before_reporting)); return false; } // Update last time of stats reporting stats_record->last_stats_report_time = boot_clock::now(); return true; } std::vector ThermalStatsHelper::processStatsRecordForReporting(StatsRecord *stats_record) { // update the last unclosed entry and start new record with same state updateStatsRecord(stats_record, stats_record->cur_state); std::vector &time_in_state_ms = stats_record->time_in_state_ms; // convert std::chrono::milliseconds time_in_state to int64_t vector for reporting std::vector stats_residency(time_in_state_ms.size()); std::transform(time_in_state_ms.begin(), time_in_state_ms.end(), stats_residency.begin(), [](std::chrono::milliseconds time_ms) { return time_ms.count(); }); // clear previous stats std::fill(time_in_state_ms.begin(), time_in_state_ms.end(), std::chrono::milliseconds::zero()); return stats_residency; } bool ThermalStatsHelper::reportAtom(const std::shared_ptr &stats_client, const int32_t &atom_id, std::vector &&values) { LOG(VERBOSE) << "Reporting thermal stats for atom_id " << atom_id; // Send vendor atom to IStats HAL VendorAtom event = {.reverseDomainName = "", .atomId = atom_id, .values = std::move(values)}; const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); return ret.isOk(); } StatsRecord ThermalStatsHelper::restoreStatsRecordOnFailure( StatsRecord &&stats_record_before_failure) { stats_record_before_failure.report_fail_count += 1; // If consecutive count of failure is high, reset stat to avoid overflow if (stats_record_before_failure.report_fail_count >= kMaxStatsReportingFailCount) { return StatsRecord(stats_record_before_failure.time_in_state_ms.size(), stats_record_before_failure.cur_state); } else { return stats_record_before_failure; } } std::unordered_map ThermalStatsHelper::GetSensorTempStatsSnapshot() { auto sensor_temp_stats_snapshot = sensor_temp_stats_map_; for (auto &sensor_temp_stats_pair : sensor_temp_stats_snapshot) { for (auto &temp_stats : sensor_temp_stats_pair.second.stats_by_custom_threshold) { // update the last unclosed entry and start new record with same state updateStatsRecord(&temp_stats.stats_record, temp_stats.stats_record.cur_state); } if (sensor_temp_stats_pair.second.stats_by_default_threshold.has_value()) { auto &stats_by_default_threshold = sensor_temp_stats_pair.second.stats_by_default_threshold.value(); // update the last unclosed entry and start new record with same state updateStatsRecord(&stats_by_default_threshold, stats_by_default_threshold.cur_state); } } return sensor_temp_stats_snapshot; } std::unordered_map>> ThermalStatsHelper::GetSensorCoolingDeviceRequestStatsSnapshot() { auto sensor_cdev_request_stats_snapshot = sensor_cdev_request_stats_map_; for (auto &sensor_cdev_request_stats_pair : sensor_cdev_request_stats_snapshot) { for (auto &cdev_request_stats_pair : sensor_cdev_request_stats_pair.second) { for (auto &request_stats : cdev_request_stats_pair.second.stats_by_custom_threshold) { // update the last unclosed entry and start new record with same state updateStatsRecord(&request_stats.stats_record, request_stats.stats_record.cur_state); } if (cdev_request_stats_pair.second.stats_by_default_threshold.has_value()) { auto &stats_by_default_threshold = cdev_request_stats_pair.second.stats_by_default_threshold.value(); // update the last unclosed entry and start new record with same state updateStatsRecord(&stats_by_default_threshold, stats_by_default_threshold.cur_state); } } } return sensor_cdev_request_stats_snapshot; } } // namespace implementation } // namespace thermal } // namespace hardware } // namespace android } // namespace aidl