audioserver: add automated audio power logging
We utilize the wakelock and batterystat logic to measure
by app the power used during audio playback.
This is integrated into audioserver to allow close tracking
of apps in and out of suspend.
This is default disabled for now. Before trying, use
$ adb shell setenforce 0
$ adb shell setprop persist.audio.power_stats.enabled true
$ adb shell aflags enable com.android.media.audioserver.power_stats true
Flag: com.android.media.audioserver.power_stats
Test: atest powerstats_collector_tests
Test: atest audio_powerstats_benchmark
Test: audio_powerstatscollector_benchmark
Test: atest audio_token_benchmark
Bug: 350114693
Change-Id: I6e0c04089fce72b1e3e22532e39f38f5d6657f28
diff --git a/media/psh_utils/AudioPowerManager.cpp b/media/psh_utils/AudioPowerManager.cpp
new file mode 100644
index 0000000..3ae681a
--- /dev/null
+++ b/media/psh_utils/AudioPowerManager.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "AudioToken.h"
+#define LOG_TAG "AudioPowerManager"
+#include <com_android_media_audioserver.h>
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <psh_utils/AudioPowerManager.h>
+
+namespace android::media::psh_utils {
+
+/* static */
+AudioPowerManager& AudioPowerManager::getAudioPowerManager() {
+ [[clang::no_destroy]] static AudioPowerManager apm;
+ return apm;
+}
+
+std::unique_ptr<Token> AudioPowerManager::startClient(pid_t pid, uid_t uid,
+ const std::string& additional) {
+ std::shared_ptr<PowerClientStats> powerClientStats;
+ std::lock_guard l(mMutex);
+ if (mPowerClientStats.count(uid) == 0) {
+ const auto it = mHistoricalClients.find(uid);
+ if (it == mHistoricalClients.end()) {
+ powerClientStats = std::make_shared<PowerClientStats>(uid, additional);
+ } else {
+ powerClientStats = it->second;
+ mHistoricalClients.erase(it);
+ }
+ mPowerClientStats[uid] = powerClientStats;
+ } else {
+ powerClientStats = mPowerClientStats[uid];
+ }
+ powerClientStats->addPid(pid);
+ mPidToUid[pid] = uid;
+ std::unique_ptr<Token> token =
+ std::make_unique<AudioClientToken>(powerClientStats, pid, uid, additional);
+ mOutstandingTokens.emplace(token.get());
+ return token;
+}
+
+std::unique_ptr<Token> AudioPowerManager::startTrack(uid_t uid, const std::string& additional) {
+ std::lock_guard l(mMutex);
+ if (mPowerClientStats.count(uid) == 0) {
+ ALOGW("%s: Cannot find uid: %d", __func__, uid);
+ return {};
+ }
+ auto powerClientStats = mPowerClientStats[uid];
+ std::unique_ptr<Token> token =
+ std::make_unique<AudioTrackToken>(powerClientStats, additional);
+ mOutstandingTokens.emplace(token.get());
+ return token;
+}
+
+std::unique_ptr<Token> AudioPowerManager::startThread(
+ pid_t pid, const std::string& wakeLockName,
+ WakeFlag wakeFlag, const std::string& additional) {
+ std::lock_guard l(mMutex);
+ std::unique_ptr<Token> token =
+ std::make_unique<AudioThreadToken>(pid, wakeLockName, wakeFlag, additional);
+ mOutstandingTokens.emplace(token.get());
+ return token;
+}
+
+std::string AudioPowerManager::toString() const {
+ const std::string prefix(" ");
+ std::string result;
+ std::lock_guard l(mMutex);
+ result.append("Power Tokens:\n");
+ std::vector<std::string> tokenInfo;
+ for (const auto& token: mOutstandingTokens) {
+ tokenInfo.emplace_back(token->toString());
+ }
+ std::sort(tokenInfo.begin(), tokenInfo.end());
+ for (const auto& info: tokenInfo) {
+ result.append(prefix).append(info).append("\n");
+ }
+ result.append("Power Clients:\n");
+ for (const auto& [uid, powerClientStats]: mPowerClientStats) {
+ result.append(powerClientStats->toString(true, prefix));
+ }
+ result.append("Power Client History:\n");
+ for (const auto& [power, powerClientStats]: mHistoricalClients) {
+ result.append(powerClientStats->toString(true, prefix));
+ }
+ return result;
+}
+
+void AudioPowerManager::stopClient(pid_t pid) {
+ std::lock_guard l(mMutex);
+ const auto pidit = mPidToUid.find(pid);
+ if (pidit == mPidToUid.end()) return;
+ const uid_t uid = pidit->second;
+ const auto it = mPowerClientStats.find(uid);
+ if (it == mPowerClientStats.end()) return;
+
+ auto powerClientStats = it->second;
+ size_t count = powerClientStats->removePid(pid);
+ if (count == 0) {
+ mHistoricalClients[uid] = powerClientStats;
+ mPowerClientStats.erase(it);
+ if (mHistoricalClients.size() > kHistory) {
+ mHistoricalClients.erase(mHistoricalClients.begin()); // remove oldest.
+ }
+ }
+ mPidToUid.erase(pid);
+}
+
+void AudioPowerManager::clear_token_ptr(Token* token) {
+ if (token != nullptr) {
+ std::lock_guard l(mMutex);
+ (void)mOutstandingTokens.erase(token);
+ }
+}
+
+/* static */
+bool AudioPowerManager::enabled() {
+ static const bool enabled = com::android::media::audioserver::power_stats()
+ && property_get_bool("persist.audio.power_stats.enabled", false);
+ return enabled;
+}
+
+} // namespace android::media::psh_utils