psh_utils: Add HealthStats collection
Flag: com.android.media.audioserver.power_stats
Test: atest powerstats_collector_tests
Bug: 350114693
Change-Id: I92e0e68bbeef548b314c92e382d4057218838b14
diff --git a/media/psh_utils/Android.bp b/media/psh_utils/Android.bp
index f67f63e..4662db8 100644
--- a/media/psh_utils/Android.bp
+++ b/media/psh_utils/Android.bp
@@ -9,6 +9,7 @@
// libraries that are included whole_static for test apps
ndk_libs = [
+ "android.hardware.health-V3-ndk",
"android.hardware.power.stats-V1-ndk",
]
@@ -18,6 +19,8 @@
local_include_dirs: ["include"],
export_include_dirs: ["include"],
srcs: [
+ "HealthStats.cpp",
+ "HealthStatsProvider.cpp",
"PowerStats.cpp",
"PowerStatsCollector.cpp",
"PowerStatsProvider.cpp",
diff --git a/media/psh_utils/HealthStats.cpp b/media/psh_utils/HealthStats.cpp
new file mode 100644
index 0000000..b40c8fe
--- /dev/null
+++ b/media/psh_utils/HealthStats.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 <android-base/logging.h>
+#include <psh_utils/HealthStats.h>
+
+namespace android::media::psh_utils {
+
+template <typename T>
+const T& choose_voltage(const T& a, const T& b) {
+ return std::max(a, b); // we use max here, could use avg.
+}
+
+std::string HealthStats::toString() const {
+ std::string result;
+ const float batteryVoltage = batteryVoltageMillivolts * 1e-3f; // Volts
+ const float charge = batteryChargeCounterUah * (3600 * 1e-6); // Joules = Amp-Second
+ result.append(" battery_voltage: ")
+ .append(std::to_string(batteryVoltage))
+ .append(" charge: ")
+ .append(std::to_string(charge));
+ return result;
+}
+
+std::string HealthStats::normalizedEnergy(double timeSec) const {
+ std::string result;
+ const float batteryVoltage = batteryVoltageMillivolts * 1e-3f; // Volts
+ const float charge = -batteryChargeCounterUah * (3600 * 1e-6f); // Joules = Amp-Second
+ const float watts = charge * batteryVoltage / timeSec;
+ result.append(" battery_voltage: ")
+ .append(std::to_string(batteryVoltage))
+ .append(" J: ")
+ .append(std::to_string(charge))
+ .append(" W: ")
+ .append(std::to_string(watts));
+ return result;
+}
+
+HealthStats HealthStats::operator+=(const HealthStats& other) {
+ batteryVoltageMillivolts = choose_voltage(
+ batteryVoltageMillivolts, other.batteryVoltageMillivolts);
+ batteryFullChargeUah = std::max(batteryFullChargeUah, other.batteryFullChargeUah);
+ batteryChargeCounterUah += other.batteryChargeCounterUah;
+ return *this;
+}
+
+HealthStats HealthStats::operator-=(const HealthStats& other) {
+ batteryVoltageMillivolts = choose_voltage(
+ batteryVoltageMillivolts, other.batteryVoltageMillivolts);
+ batteryFullChargeUah = std::max(batteryFullChargeUah, other.batteryFullChargeUah);
+ batteryChargeCounterUah -= other.batteryChargeCounterUah;
+ return *this;
+}
+
+HealthStats HealthStats::operator+(const HealthStats& other) const {
+ HealthStats result = *this;
+ result += other;
+ return result;
+}
+
+HealthStats HealthStats::operator-(const HealthStats& other) const {
+ HealthStats result = *this;
+ result -= other;
+ return result;
+}
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/HealthStatsProvider.cpp b/media/psh_utils/HealthStatsProvider.cpp
new file mode 100644
index 0000000..744daad
--- /dev/null
+++ b/media/psh_utils/HealthStatsProvider.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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 <aidl/android/hardware/health/IHealth.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+
+using ::aidl::android::hardware::health::HealthInfo;
+using ::aidl::android::hardware::health::IHealth;
+
+namespace android::media::psh_utils {
+
+static auto getHealthService() {
+ [[clang::no_destroy]] static constinit std::mutex m;
+ [[clang::no_destroy]] static constinit
+ std::shared_ptr<IHealth> healthService;
+
+ std::lock_guard l(m);
+ if (healthService) {
+ return healthService;
+ }
+ const auto serviceName =
+ std::string(IHealth::descriptor).append("/default");
+ healthService = IHealth::fromBinder(
+ ::ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+ return healthService;
+}
+
+status_t HealthStatsDataProvider::fill(PowerStats* stat) const {
+ if (stat == nullptr) return BAD_VALUE;
+ HealthStats& stats = stat->health_stats;
+ auto healthService = getHealthService();
+ if (healthService == nullptr) {
+ LOG(ERROR) << "unable to get health AIDL service";
+ return NO_INIT;
+ }
+ HealthInfo healthInfo;
+ if (!healthService->getHealthInfo(&healthInfo).isOk()) {
+ LOG(ERROR) << __func__ << ": unable to get health info";
+ return INVALID_OPERATION;
+ }
+
+ stats.batteryVoltageMillivolts = healthInfo.batteryVoltageMillivolts;
+ stats.batteryFullChargeUah = healthInfo.batteryFullChargeUah;
+ stats.batteryChargeCounterUah = healthInfo.batteryChargeCounterUah;
+ return NO_ERROR;
+}
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/PowerStats.cpp b/media/psh_utils/PowerStats.cpp
index c1f3d9a..9c6968f 100644
--- a/media/psh_utils/PowerStats.cpp
+++ b/media/psh_utils/PowerStats.cpp
@@ -163,6 +163,7 @@
std::string PowerStats::toString() const {
std::string result;
result.append(metadata.toString()).append("\n");
+ result.append(health_stats.toString()).append("\n");
for (const auto &residency: power_entity_state_residency) {
result.append(residency.toString()).append("\n");
}
@@ -182,6 +183,8 @@
.append(" duration_monotonic: ")
.append(std::to_string(metadata.duration_monotonic_ms * 1e-3f))
.append("\n");
+ result.append(health_stats.normalizedEnergy(metadata.duration_ms * 1e-3f)).append("\n");
+
// energy_uws is converted to ave W using recip time in us.
const float recipTime = 1e-3 / metadata.duration_ms;
int64_t total_energy = 0;
@@ -221,6 +224,7 @@
PowerStats PowerStats::operator+=(const PowerStats& other) {
metadata += other.metadata;
+ health_stats += other.health_stats;
if (power_entity_state_residency.empty()) {
power_entity_state_residency = other.power_entity_state_residency;
} else {
@@ -240,6 +244,7 @@
PowerStats PowerStats::operator-=(const PowerStats& other) {
metadata -= other.metadata;
+ health_stats -= other.health_stats;
if (power_entity_state_residency.empty()) {
power_entity_state_residency = other.power_entity_state_residency;
} else {
diff --git a/media/psh_utils/PowerStatsCollector.cpp b/media/psh_utils/PowerStatsCollector.cpp
index c166d85..7bee3f8 100644
--- a/media/psh_utils/PowerStatsCollector.cpp
+++ b/media/psh_utils/PowerStatsCollector.cpp
@@ -23,6 +23,7 @@
PowerStatsCollector::PowerStatsCollector() {
addProvider(std::make_unique<PowerEntityResidencyDataProvider>());
addProvider(std::make_unique<RailEnergyDataProvider>());
+ addProvider(std::make_unique<HealthStatsDataProvider>());
}
/* static */
diff --git a/media/psh_utils/PowerStatsProvider.h b/media/psh_utils/PowerStatsProvider.h
index 2be1872..5f5a506 100644
--- a/media/psh_utils/PowerStatsProvider.h
+++ b/media/psh_utils/PowerStatsProvider.h
@@ -31,4 +31,9 @@
status_t fill(PowerStats* stat) const override;
};
+class HealthStatsDataProvider : public PowerStatsProvider {
+public:
+ status_t fill(PowerStats* stat) const override;
+};
+
} // namespace android::media::psh_utils
diff --git a/media/psh_utils/include/psh_utils/HealthStats.h b/media/psh_utils/include/psh_utils/HealthStats.h
new file mode 100644
index 0000000..982c390
--- /dev/null
+++ b/media/psh_utils/include/psh_utils/HealthStats.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace android::media::psh_utils {
+
+// From hardware/interfaces/health/aidl/android/hardware/health/HealthInfo.aidl
+
+struct HealthStats {
+ /**
+ * Instantaneous battery voltage in millivolts (mV).
+ *
+ * Historically, the unit of this field is microvolts (µV), but all
+ * clients and implementations uses millivolts in practice, making it
+ * the de-facto standard.
+ */
+ double batteryVoltageMillivolts;
+ /**
+ * Battery charge value when it is considered to be "full" in µA-h
+ */
+ double batteryFullChargeUah;
+ /**
+ * Instantaneous battery capacity in µA-h
+ */
+ double batteryChargeCounterUah;
+
+ std::string normalizedEnergy(double time) const;
+
+ // Returns {seconds, joules, watts} from battery counters
+ std::tuple<float, float, float> energyFrom(const std::string& s) const;
+ std::string toString() const;
+
+ HealthStats operator+=(const HealthStats& other);
+ HealthStats operator-=(const HealthStats& other);
+ HealthStats operator+(const HealthStats& other) const;
+ HealthStats operator-(const HealthStats& other) const;
+ bool operator==(const HealthStats& other) const = default;
+};
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/include/psh_utils/PowerStats.h b/media/psh_utils/include/psh_utils/PowerStats.h
index e8652d5..1e819cc 100644
--- a/media/psh_utils/include/psh_utils/PowerStats.h
+++ b/media/psh_utils/include/psh_utils/PowerStats.h
@@ -16,6 +16,8 @@
#pragma once
+#include "HealthStats.h"
+
#include <string>
#include <unordered_map>
#include <vector>
@@ -83,6 +85,8 @@
bool operator==(const RailEnergy& other) const = default;
};
+ HealthStats health_stats;
+
std::string normalizedEnergy() const;
// Returns {seconds, joules, watts} from all rails containing a matching string.
diff --git a/media/psh_utils/tests/powerstats_collector_tests.cpp b/media/psh_utils/tests/powerstats_collector_tests.cpp
index b007be3..5115d09 100644
--- a/media/psh_utils/tests/powerstats_collector_tests.cpp
+++ b/media/psh_utils/tests/powerstats_collector_tests.cpp
@@ -140,3 +140,42 @@
EXPECT_EQ(kEnergyUws2 - kEnergyUws1,
ps5.rail_energy[0].energy_uws);
}
+
+TEST(powerstat_collector_tests, health_stats) {
+ PowerStats ps1, ps2;
+
+ constexpr double kBatteryChargeCounterUah1 = 21;
+ constexpr double kBatteryChargeCounterUah2 = 25;
+ ps1.health_stats.batteryChargeCounterUah = kBatteryChargeCounterUah1;
+ ps2.health_stats.batteryChargeCounterUah = kBatteryChargeCounterUah2;
+
+ constexpr double kBatteryFullChargeUah1 = 32;
+ constexpr double kBatteryFullChargeUah2 = 33;
+ ps1.health_stats.batteryFullChargeUah = kBatteryFullChargeUah1;
+ ps2.health_stats.batteryFullChargeUah = kBatteryFullChargeUah2;
+
+ constexpr double kBatteryVoltageMillivolts1 = 42;
+ constexpr double kBatteryVoltageMillivolts2 = 43;
+ ps1.health_stats.batteryVoltageMillivolts = kBatteryVoltageMillivolts1;
+ ps2.health_stats.batteryVoltageMillivolts = kBatteryVoltageMillivolts2;
+
+ PowerStats ps3 = ps1 + ps2;
+ PowerStats ps4 = ps2 + ps1;
+ EXPECT_EQ(ps3, ps4);
+ EXPECT_EQ(kBatteryChargeCounterUah1 + kBatteryChargeCounterUah2,
+ ps3.health_stats.batteryChargeCounterUah);
+
+ EXPECT_NO_FATAL_FAILURE(inRange(ps3.health_stats.batteryFullChargeUah,
+ kBatteryFullChargeUah1, kBatteryFullChargeUah2));
+ EXPECT_NO_FATAL_FAILURE(inRange(ps3.health_stats.batteryVoltageMillivolts,
+ kBatteryVoltageMillivolts1, kBatteryVoltageMillivolts2));
+
+ PowerStats ps5 = ps2 - ps1;
+ EXPECT_EQ(kBatteryChargeCounterUah2 - kBatteryChargeCounterUah1,
+ ps5.health_stats.batteryChargeCounterUah);
+
+ EXPECT_NO_FATAL_FAILURE(inRange(ps5.health_stats.batteryFullChargeUah,
+ kBatteryFullChargeUah1, kBatteryFullChargeUah2));
+ EXPECT_NO_FATAL_FAILURE(inRange(ps5.health_stats.batteryVoltageMillivolts,
+ kBatteryVoltageMillivolts1, kBatteryVoltageMillivolts2));
+}