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

Commit 125ff52f authored by Andy Hung's avatar Andy Hung
Browse files

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
parent 05a75ae3
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
package {
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
    // all of the 'license_kinds' from "frameworks_av_license"
    // to get the below license kinds:
    //   SPDX-license-identifier-Apache-2.0
    default_applicable_licenses: ["frameworks_av_license"],
}

// libraries that are included whole_static for test apps
ndk_libs = [
    "android.hardware.power.stats-V1-ndk",
]

// Power, System, Health utils
cc_library {
    name: "libpshutils",
    local_include_dirs: ["include"],
    export_include_dirs: ["include"],
    srcs: [
        "PowerStats.cpp",
        "PowerStatsCollector.cpp",
        "PowerStatsProvider.cpp",
    ],
    shared_libs: [
        "libaudioutils",
        "libbase",
        "libbinder_ndk",
        "libcutils",
        "liblog",
        "libutils",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wthread-safety",
    ],
    shared: {
        shared_libs: ndk_libs,
    },
    static: {
        whole_static_libs: ndk_libs,
    },
}
+272 −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 <android-base/logging.h>
#include <audio_utils/clock.h>
#include <psh_utils/PowerStats.h>

namespace android::media::psh_utils {

// Determine the best start time from a and b, which is
// min(a, b) if both exist, otherwise the one that exists.
template <typename T>
const T& choose_best_start_time(const T& a, const T& b) {
    if (a) {
        return b ? std::min(a, b) : a;
    } else {
        return b;
    }
}

// subtract two time differences.
template <typename T, typename U>
const T sub_time_diff(const T& diff_a, const T& diff_b, const U& abs_c, const U& abs_d) {
    if (diff_a) {
        return diff_b ? (diff_a - diff_b) : diff_a;
    } else if (diff_b) {
        return diff_b;
    } else {  // no difference exists, use absolute time.
        return abs_c - abs_d;
    }
}

std::string PowerStats::Metadata::toString() const {
    return std::string("start_time_since_boot_ms: ").append(
                    std::to_string(start_time_since_boot_ms))
            .append(" start_time_monotonic_ms: ").append(std::to_string(start_time_monotonic_ms))
            .append(audio_utils_time_string_from_ns(start_time_epoch_ms * 1'000'000).time)
            .append(" duration_ms: ").append(std::to_string(duration_ms))
            .append(" duration_monotonic_ms: ").append(std::to_string(duration_monotonic_ms));
}

PowerStats::Metadata PowerStats::Metadata::operator+=(const Metadata& other) {
    start_time_since_boot_ms = choose_best_start_time(
            start_time_since_boot_ms, other.start_time_since_boot_ms);
    start_time_epoch_ms = choose_best_start_time(
            start_time_epoch_ms, other.start_time_epoch_ms);
    start_time_monotonic_ms = choose_best_start_time(
            start_time_monotonic_ms, other.start_time_monotonic_ms);
    duration_ms += other.duration_ms;
    duration_monotonic_ms += other.duration_monotonic_ms;
    return *this;
}

PowerStats::Metadata PowerStats::Metadata::operator-=(const Metadata& other) {
    // here we calculate duration, if it makes sense.
    duration_ms = sub_time_diff(duration_ms, other.duration_ms,
                                start_time_since_boot_ms, other.start_time_since_boot_ms);
    duration_monotonic_ms = sub_time_diff(
            duration_monotonic_ms, other.duration_monotonic_ms,
            start_time_monotonic_ms, other.start_time_monotonic_ms);
    start_time_since_boot_ms = choose_best_start_time(
            start_time_since_boot_ms, other.start_time_since_boot_ms);
    start_time_epoch_ms = choose_best_start_time(
            start_time_epoch_ms, other.start_time_epoch_ms);
    start_time_monotonic_ms = choose_best_start_time(
            start_time_monotonic_ms, other.start_time_monotonic_ms);
    return *this;
}

PowerStats::Metadata PowerStats::Metadata::operator+(const Metadata& other) const {
    Metadata result = *this;
    result += other;
    return result;
}

PowerStats::Metadata PowerStats::Metadata::operator-(const Metadata& other) const {
    Metadata result = *this;
    result -= other;
    return result;
}

std::string PowerStats::StateResidency::toString() const {
    return std::string(entity_name).append(state_name)
            .append(" ").append(std::to_string(time_ms))
            .append(" ").append(std::to_string(entry_count));
}

PowerStats::StateResidency PowerStats::StateResidency::operator+=(const StateResidency& other) {
    if (entity_name.empty()) entity_name = other.entity_name;
    if (state_name.empty()) state_name = other.state_name;
    time_ms += other.time_ms;
    entry_count += other.entry_count;
    return *this;
}

PowerStats::StateResidency PowerStats::StateResidency::operator-=(const StateResidency& other) {
    if (entity_name.empty()) entity_name = other.entity_name;
    if (state_name.empty()) state_name = other.state_name;
    time_ms -= other.time_ms;
    entry_count -= other.entry_count;
    return *this;
}

PowerStats::StateResidency PowerStats::StateResidency::operator+(
        const StateResidency& other) const {
    StateResidency result = *this;
    result += other;
    return result;
}

PowerStats::StateResidency PowerStats::StateResidency::operator-(
        const StateResidency& other) const {
    StateResidency result = *this;
    result -= other;
    return result;
}

std::string PowerStats::RailEnergy::toString() const {
    return std::string(subsystem_name)
            .append(rail_name)
            .append(" ")
            .append(std::to_string(energy_uws));
}

PowerStats::RailEnergy PowerStats::RailEnergy::operator+=(const RailEnergy& other) {
    if (subsystem_name.empty()) subsystem_name = other.subsystem_name;
    if (rail_name.empty()) rail_name = other.rail_name;
    energy_uws += other.energy_uws;
    return *this;
}

PowerStats::RailEnergy PowerStats::RailEnergy::operator-=(const RailEnergy& other) {
    if (subsystem_name.empty()) subsystem_name = other.subsystem_name;
    if (rail_name.empty()) rail_name = other.rail_name;
    energy_uws -= other.energy_uws;
    return *this;
}

PowerStats::RailEnergy PowerStats::RailEnergy::operator+(const RailEnergy& other) const {
    RailEnergy result = *this;
    result += other;
    return result;
}

PowerStats::RailEnergy PowerStats::RailEnergy::operator-(const RailEnergy& other) const {
    RailEnergy result = *this;
    result -= other;
    return result;
}

std::string PowerStats::toString() const {
    std::string result;
    result.append(metadata.toString()).append("\n");
    for (const auto &residency: power_entity_state_residency) {
        result.append(residency.toString()).append("\n");
    }
    for (const auto &energy: rail_energy) {
        result.append(energy.toString()).append("\n");
    }
    return result;
}

std::string PowerStats::normalizedEnergy() const {
    if (metadata.duration_ms == 0) return {};

    std::string result(audio_utils_time_string_from_ns(
            metadata.start_time_epoch_ms * 1'000'000).time);
    result.append(" duration_boottime: ")
            .append(std::to_string(metadata.duration_ms * 1e-3f))
            .append(" duration_monotonic: ")
            .append(std::to_string(metadata.duration_monotonic_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;
    for (const auto& energy: rail_energy) {
        total_energy += energy.energy_uws;
        result.append(energy.subsystem_name)
                .append(energy.rail_name)
                .append(" ")
                .append(std::to_string(energy.energy_uws * 1e-6))
                .append(" ")
                .append(std::to_string(energy.energy_uws * recipTime))
                .append("\n");
    }
    result.append("total J and ave W: ")
            .append(std::to_string(total_energy * 1e-6))
            .append(" ")
            .append(std::to_string(total_energy * recipTime))
            .append("\n");
    return result;
}

// seconds, joules, watts
std::tuple<float, float, float> PowerStats::energyFrom(const std::string& railMatcher) const {
    if (metadata.duration_ms == 0) return {};

    // 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;
    for (const auto& energy: rail_energy) {
        if (energy.subsystem_name.find(railMatcher) != std::string::npos
                || energy.rail_name.find(railMatcher) != std::string::npos) {
            total_energy += energy.energy_uws;
        }
    }
    return {metadata.duration_ms * 1e-3, total_energy * 1e-6, total_energy * recipTime};
}

PowerStats PowerStats::operator+=(const PowerStats& other) {
    metadata += other.metadata;
    if (power_entity_state_residency.empty()) {
        power_entity_state_residency = other.power_entity_state_residency;
    } else {
        for (size_t i = 0; i < power_entity_state_residency.size(); ++i) {
            power_entity_state_residency[i] += other.power_entity_state_residency[i];
        }
    }
    if (rail_energy.empty()) {
        rail_energy = other.rail_energy;
    } else {
        for (size_t i = 0; i < rail_energy.size(); ++i) {
            rail_energy[i] += other.rail_energy[i];
        }
    }
    return *this;
}

PowerStats PowerStats::operator-=(const PowerStats& other) {
    metadata -= other.metadata;
    if (power_entity_state_residency.empty()) {
        power_entity_state_residency = other.power_entity_state_residency;
    } else {
        for (size_t i = 0; i < power_entity_state_residency.size(); ++i) {
            power_entity_state_residency[i] -= other.power_entity_state_residency[i];
        }
    }
    if (rail_energy.empty()) {
        rail_energy = other.rail_energy;
    } else {
        for (size_t i = 0; i < rail_energy.size(); ++i) {
            rail_energy[i] -= other.rail_energy[i];
        }
    }
    return *this;
}

PowerStats PowerStats::operator+(const PowerStats& other) const {
    PowerStats result = *this;
    result += other;
    return result;
}

PowerStats PowerStats::operator-(const PowerStats& other) const {
    PowerStats result = *this;
    result -= other;
    return result;
}

} // namespace android::media::psh_utils
+68 −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 <android-base/logging.h>
#include <psh_utils/PowerStatsCollector.h>
#include "PowerStatsProvider.h"
#include <utils/Timers.h>

namespace android::media::psh_utils {

PowerStatsCollector::PowerStatsCollector() {
    addProvider(std::make_unique<PowerEntityResidencyDataProvider>());
    addProvider(std::make_unique<RailEnergyDataProvider>());
}

/* static */
PowerStatsCollector& PowerStatsCollector::getCollector() {
    [[clang::no_destroy]] static PowerStatsCollector psc;
    return psc;
}

std::shared_ptr<PowerStats> PowerStatsCollector::getStats() const {
    auto result = std::make_shared<PowerStats>();
    (void)fill(result.get());
    return result;
}

void PowerStatsCollector::addProvider(std::unique_ptr<PowerStatsProvider>&& powerStatsProvider) {
    mPowerStatsProviders.emplace_back(std::move(powerStatsProvider));
}

int PowerStatsCollector::fill(PowerStats* stats) const {
    if (!stats) {
        LOG(ERROR) << __func__ << ": bad args; stat is null";
        return 1;
    }

    for (const auto& provider : mPowerStatsProviders) {
        if (provider->fill(stats) != 0) {
            LOG(ERROR) << __func__ << ": a data provider failed";
            continue;
        }
    }

    // boot time follows wall clock time, but starts from boot.
    stats->metadata.start_time_since_boot_ms = systemTime(SYSTEM_TIME_BOOTTIME) / 1'000'000;

    // wall clock time
    stats->metadata.start_time_epoch_ms = systemTime(SYSTEM_TIME_REALTIME) / 1'000'000;

    // monotonic time follows boot time, but does not include any time suspended.
    stats->metadata.start_time_monotonic_ms = systemTime() / 1'000'000;
    return 0;
}

} // namespace android::media::psh_utils
+153 −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 "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
+34 −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/PowerStatsCollector.h>
#include <iostream>

namespace android::media::psh_utils {

class RailEnergyDataProvider : public PowerStatsProvider {
public:
    int fill(PowerStats* stat) const override;
};

class PowerEntityResidencyDataProvider : public PowerStatsProvider {
public:
    int fill(PowerStats* stat) const override;
};

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