Media: Add power-systems-health utils
Used for audio power management and statistics.
This CL is library only.
Flag: com.android.media.audioserver.power_stats
Test: atest powerstats_collector_tests
Test: atest audio_powerstats_benchmark
Bug: 350114693
Change-Id: Iab8cae3f4418f86d43acef109ae7ce2776d627ca
diff --git a/media/psh_utils/PowerStatsProvider.cpp b/media/psh_utils/PowerStatsProvider.cpp
new file mode 100644
index 0000000..71a39e1
--- /dev/null
+++ b/media/psh_utils/PowerStatsProvider.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 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 "PowerStatsProvider.h"
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <unordered_map>
+#include <aidl/android/hardware/power/stats/IPowerStats.h>
+
+using ::aidl::android::hardware::power::stats::IPowerStats;
+
+namespace android::media::psh_utils {
+
+static auto getPowerStatsService() {
+ [[clang::no_destroy]] static constinit std::mutex m;
+ [[clang::no_destroy]] static constinit
+ std::shared_ptr<IPowerStats> powerStats;
+
+ std::lock_guard l(m);
+ if (powerStats) {
+ return powerStats;
+ }
+ const auto serviceName =
+ std::string(IPowerStats::descriptor)
+ .append("/default");
+ powerStats = IPowerStats::fromBinder(
+ ::ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+ return powerStats;
+}
+
+int RailEnergyDataProvider::fill(PowerStats *stat) const {
+ auto powerStatsService = getPowerStatsService();
+ if (powerStatsService == nullptr) {
+ LOG(ERROR) << "unable to get power.stats AIDL service";
+ return 1;
+ }
+
+ std::unordered_map<int32_t, ::aidl::android::hardware::power::stats::Channel> channelMap;
+ {
+ std::vector<::aidl::android::hardware::power::stats::Channel> channels;
+ if (!powerStatsService->getEnergyMeterInfo(&channels).isOk()) {
+ LOG(ERROR) << "unable to get energy meter info";
+ return 1;
+ }
+ for (auto& channel : channels) {
+ channelMap.emplace(channel.id, std::move(channel));
+ }
+ }
+
+ std::vector<::aidl::android::hardware::power::stats::EnergyMeasurement> measurements;
+ if (!powerStatsService->readEnergyMeter({}, &measurements).isOk()) {
+ LOG(ERROR) << "unable to get energy measurements";
+ return 1;
+ }
+
+ for (const auto& measurement : measurements) {
+ stat->rail_energy.emplace_back(
+ channelMap.at(measurement.id).subsystem,
+ channelMap.at(measurement.id).name,
+ measurement.energyUWs);
+ }
+
+ // Sort entries first by subsystem_name, then by rail_name.
+ // Sorting is needed to make interval processing efficient.
+ std::sort(stat->rail_energy.begin(), stat->rail_energy.end(),
+ [](const auto& a, const auto& b) {
+ if (a.subsystem_name != b.subsystem_name) {
+ return a.subsystem_name < b.subsystem_name;
+ }
+ return a.rail_name < b.rail_name;
+ });
+
+ return 0;
+}
+
+int PowerEntityResidencyDataProvider::fill(PowerStats* stat) const {
+ auto powerStatsService = getPowerStatsService();
+ if (powerStatsService == nullptr) {
+ LOG(ERROR) << "unable to get power.stats AIDL service";
+ return 1;
+ }
+
+ // these are based on entityId
+ std::unordered_map<int32_t, std::string> entityNames;
+ std::unordered_map<int32_t, std::unordered_map<int32_t, std::string>> stateNames;
+ std::vector<int32_t> powerEntityIds; // ids to use
+
+ {
+ std::vector<::aidl::android::hardware::power::stats::PowerEntity> entities;
+ if (!powerStatsService->getPowerEntityInfo(&entities).isOk()) {
+ LOG(ERROR) << __func__ << ": unable to get entity info";
+ return 1;
+ }
+
+ std::vector<std::string> powerEntityNames;
+ for (const auto& entity : entities) {
+ std::unordered_map<int32_t, std::string> states;
+ for (const auto& state : entity.states) {
+ states.emplace(state.id, state.name);
+ }
+
+ if (std::find(powerEntityNames.begin(), powerEntityNames.end(), entity.name) !=
+ powerEntityNames.end()) {
+ powerEntityIds.emplace_back(entity.id);
+ }
+ entityNames.emplace(entity.id, std::move(entity.name));
+ stateNames.emplace(entity.id, std::move(states));
+ }
+ }
+
+ std::vector<::aidl::android::hardware::power::stats::StateResidencyResult> results;
+ if (!powerStatsService->getStateResidency(powerEntityIds, &results).isOk()) {
+ LOG(ERROR) << __func__ << ": Unable to get state residency";
+ return 1;
+ }
+
+ for (const auto& result : results) {
+ for (const auto& curStateResidency : result.stateResidencyData) {
+ stat->power_entity_state_residency.emplace_back(
+ entityNames.at(result.id),
+ stateNames.at(result.id).at(curStateResidency.id),
+ static_cast<uint64_t>(curStateResidency.totalTimeInStateMs),
+ static_cast<uint64_t>(curStateResidency.totalStateEntryCount));
+ }
+ }
+
+ // Sort entries first by entity_name, then by state_name.
+ // Sorting is needed to make interval processing efficient.
+ std::sort(stat->power_entity_state_residency.begin(),
+ stat->power_entity_state_residency.end(),
+ [](const auto& a, const auto& b) {
+ if (a.entity_name != b.entity_name) {
+ return a.entity_name < b.entity_name;
+ }
+ return a.state_name < b.state_name;
+ });
+ return 0;
+}
+
+} // namespace android::media::psh_utils