Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 88a7afe4 authored by Andy Hung's avatar Andy Hung
Browse files

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
parent 749910d5
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -19,18 +19,23 @@ cc_library {
    local_include_dirs: ["include"],
    export_include_dirs: ["include"],
    srcs: [
        "AudioPowerManager.cpp",
        "AudioToken.cpp",
        "HealthStats.cpp",
        "HealthStatsProvider.cpp",
        "PowerClientStats.cpp",
        "PowerStats.cpp",
        "PowerStatsCollector.cpp",
        "PowerStatsProvider.cpp",
    ],
    shared_libs: [
        "com.android.media.audio-aconfig-cc",
        "libaudioutils",
        "libbase",
        "libbinder_ndk",
        "libcutils",
        "liblog",
        "libmediautils",
        "libutils",
    ],
    cflags: [
+137 −0
Original line number Diff line number Diff line
/*
 * 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
+147 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#define LOG_TAG "AudioToken"
#include <android-base/logging.h>
#include <utils/Log.h>
#include "AudioToken.h"
#include <psh_utils/AudioPowerManager.h>

namespace android::media::psh_utils {

/* static */
constinit std::atomic<size_t> AudioClientToken::sIdCounter{};

AudioClientToken::AudioClientToken(
        std::shared_ptr<PowerClientStats> powerClientStats, pid_t pid, uid_t uid,
        const std::string& additional)
    : mPowerClientStats(std::move(powerClientStats))
    , mPid(pid)
    , mAdditional(additional)
    , mId(sIdCounter.fetch_add(1, std::memory_order_relaxed)) {
        (void)uid;
}

AudioClientToken::~AudioClientToken() {
    auto& apm = AudioPowerManager::getAudioPowerManager();

    // APM has a back pointer to AudioToken, which is accessible on toString().
    // We first remove ourselves to prevent use after free.
    apm.clear_token_ptr(this);
    apm.stopClient(mPid);
}

std::string AudioClientToken::toString() const {
    std::string result("Client-");
    result.append(std::to_string(mId)).append(": ")
            .append(" pid: ").append(std::to_string(mPid));
    if (!mAdditional.empty()) {
        result.append(" ").append(mAdditional);
    }
    return result;
}

std::unique_ptr<Token> createAudioClientToken(pid_t pid, uid_t uid,
        const std::string& additional) {
    return AudioPowerManager::getAudioPowerManager().startClient(pid, uid, additional);
}

/* static */
constinit std::atomic<size_t> AudioThreadToken::sIdCounter{};

AudioThreadToken::AudioThreadToken(
        pid_t tid, const std::string& wakeLockName,
        WakeFlag wakeFlag, const std::string& additional)
    : mTid(tid)
    , mWakeLockName(wakeLockName)
    , mWakeFlag(wakeFlag)
    , mAdditional(additional)
    , mId(sIdCounter.fetch_add(1, std::memory_order_relaxed)) {
}

AudioThreadToken::~AudioThreadToken() {
    auto& apm = AudioPowerManager::getAudioPowerManager();

    // APM has a back pointer to AudioToken, which is accessible on toString().
    // We first remove ourselves to prevent use after free.
    apm.clear_token_ptr(this);
}

std::string AudioThreadToken::toString() const {
    std::string result("Thread-");
    result.append(std::to_string(mId)).append(": ")
            .append(" ThreadBase-tid: ").append(std::to_string(mTid))
            .append(" wakeLockName: ").append(mWakeLockName)
            .append(" wakeFlag: ").append(::android::media::psh_utils::toString(mWakeFlag));
    if (!mAdditional.empty()) {
        result.append(" ").append(mAdditional);
    }
    return result;
}

std::unique_ptr<Token> createAudioThreadToken(
        pid_t pid, const std::string& wakeLockName,
        WakeFlag wakeFlag, const std::string& additional) {
    return AudioPowerManager::getAudioPowerManager().startThread(
            pid, wakeLockName, wakeFlag, additional);
}

/* static */
constinit std::atomic<size_t> AudioTrackToken::sIdCounter{};

AudioTrackToken::AudioTrackToken(
        std::shared_ptr<PowerClientStats> powerClientStats, const std::string& additional)
    : mPowerClientStats(std::move(powerClientStats))
    , mAdditional(additional)
    , mId(sIdCounter.fetch_add(1, std::memory_order_relaxed)) {
        if (mPowerClientStats){
            mPowerClientStats->getCommandThread().add(
                    "start",
                    [pas = mPowerClientStats, actualNs = systemTime(SYSTEM_TIME_BOOTTIME)]() {
                        pas->start(actualNs);
                    });
        }
}

AudioTrackToken::~AudioTrackToken() {
    // APM has a back pointer to AudioToken, which is accessible on toString().
    // We first remove ourselves to prevent use after free.
    AudioPowerManager::getAudioPowerManager().clear_token_ptr(this);
    if (mPowerClientStats) {
        mPowerClientStats->getCommandThread().add(
                "stop",
                [pas = mPowerClientStats, actualNs = systemTime(SYSTEM_TIME_BOOTTIME)]() {
                    pas->stop(actualNs);
                });
    }
}

std::string AudioTrackToken::toString() const {
    std::string result("Track-");
    result.append(std::to_string(mId)).append(": ")
            .append(mPowerClientStats ? mPowerClientStats->toString() : std::string("null"));
    if (!mAdditional.empty()) {
        result.append(" ").append(mAdditional);
    }
    return result;
}

std::unique_ptr<Token> createAudioTrackToken(uid_t uid, const std::string& additional) {
    return AudioPowerManager::getAudioPowerManager().startTrack(uid, additional);
}


} // namespace android::media::psh_utils
+83 −0
Original line number Diff line number Diff line
/*
 * 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 <psh_utils/PowerClientStats.h>
#include <psh_utils/Token.h>

#include <atomic>
#include <memory>
#include <string>

namespace android::media::psh_utils {

class AudioClientToken : public Token {
public:
    AudioClientToken(std::shared_ptr<PowerClientStats> powerClientStats, pid_t pid, uid_t uid,
             const std::string& additional);
    ~AudioClientToken() override;

    // AudioPowerManager may call toString() while AudioToken is in its dtor.
    // It is safe so long as toString is final.
    std::string toString() const final;

private:
    const std::shared_ptr<PowerClientStats> mPowerClientStats;
    const pid_t mPid;
    const std::string mAdditional;
    const size_t mId;
    static constinit std::atomic<size_t> sIdCounter;
};

class AudioThreadToken : public Token {
public:
    AudioThreadToken(
            pid_t tid, const std::string& wakeLockName,
            WakeFlag wakeFlag, const std::string& additional);
    ~AudioThreadToken() override;

    // AudioPowerManager may call toString() while AudioToken is in its dtor.
    // It is safe so long as toString is final.
    std::string toString() const final;

private:
    const pid_t mTid;
    const std::string mWakeLockName;
    const WakeFlag mWakeFlag;
    const std::string mAdditional;
    const size_t mId;
    static constinit std::atomic<size_t> sIdCounter;
};

class AudioTrackToken : public Token {
public:
    AudioTrackToken(
            std::shared_ptr<PowerClientStats> powerClientStats, const std::string& additional);
    ~AudioTrackToken() override;

    // AudioPowerManager may call toString() while AudioToken is in its dtor.
    // It is safe so long as toString is final.
    std::string toString() const final;

private:
    const std::shared_ptr<PowerClientStats> mPowerClientStats;
    const std::string mAdditional;
    const size_t mId;
    static constinit std::atomic<size_t> sIdCounter;
};

} // namespace android::media::psh_utils
+101 −0
Original line number Diff line number Diff line
/*
 * 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 <psh_utils/PowerClientStats.h>
#include <mediautils/ServiceUtilities.h>

namespace android::media::psh_utils {

/* static */
audio_utils::CommandThread& PowerClientStats::getCommandThread() {
    [[clang::no_destroy]] static audio_utils::CommandThread ct;
    return ct;
}

PowerClientStats::PowerClientStats(uid_t uid, const std::string& additional)
        : mUid(uid), mAdditional(additional) {}

void PowerClientStats::start(int64_t actualNs) {
    std::lock_guard l(mMutex);
    ++mTokenCount;
    if (mStartNs == 0) mStartNs = actualNs;
    if (mStartStats) return;
    mStartStats = PowerStatsCollector::getCollector().getStats(kStatTimeToleranceNs);
}

void PowerClientStats::stop(int64_t actualNs) {
    std::lock_guard l(mMutex);
    if (--mTokenCount > 0) return;
    if (mStartNs != 0) mDeltaNs += actualNs - mStartNs;
    mStartNs = 0;
    if (!mStartStats) return;
    const auto stopStats = PowerStatsCollector::getCollector().getStats(kStatTimeToleranceNs);
    if (stopStats && stopStats != mStartStats) {
        *mDeltaStats += *stopStats - *mStartStats;
    }
    mStartStats.reset();
}

void PowerClientStats::addPid(pid_t pid) {
    std::lock_guard l(mMutex);
    mPids.emplace(pid);
}

size_t PowerClientStats::removePid(pid_t pid) {
    std::lock_guard l(mMutex);
    mPids.erase(pid);
    return mPids.size();
}

std::string PowerClientStats::toString(bool stats, const std::string& prefix) const {
    std::lock_guard l(mMutex);

    // Adjust delta time and stats if currently running.
    auto deltaStats = mDeltaStats;
    auto deltaNs = mDeltaNs;
    if (mStartNs) deltaNs += systemTime(SYSTEM_TIME_BOOTTIME) - mStartNs;
    if (mStartStats) {
        const auto stopStats = PowerStatsCollector::getCollector().getStats(kStatTimeToleranceNs);
        if (stopStats && stopStats != mStartStats) {
            auto newStats = std::make_shared<PowerStats>(*deltaStats);
            *newStats += *stopStats - *mStartStats;
            deltaStats = newStats;
        }
    }

    std::string result(prefix);
    result.append("uid: ")
            .append(std::to_string(mUid))
            .append(" ").append(mediautils::UidInfo::getInfo(mUid)->package)
            .append(" streams: ").append(std::to_string(mTokenCount))
            .append(" seconds: ").append(std::to_string(deltaNs * 1e-9));
    result.append(" {");
    for (auto pid : mPids) {
        result.append(" ").append(std::to_string(pid));
    }
    result.append(" }");
    if (!mAdditional.empty()) {
        result.append("\n").append(prefix).append(mAdditional);
    }
    if (stats) {
        std::string prefix2(prefix);
        prefix2.append("  ");
        result.append("\n").append(deltaStats->normalizedEnergy(prefix2));
    }
    return result;
}

} // namespace android::media::psh_utils
Loading