From 5626d0820e7e5fe263f4b0350568b3a73b9007c9 Mon Sep 17 00:00:00 2001 From: Benjamin Schwartz Date: Wed, 12 Dec 2018 14:16:15 -0800 Subject: [PATCH] BatteryStats: Update to use power.stats HAL This CL updates BatteryStatsService to use power.stats HAL if available. power.stats HAL is the new HAL that will be collecting platform and subsystem power statistics going forward. BatteryStatsService will still fall back to using the existing power HAL if power.stats HAL is not implemented. Bug: 120552155 Test: Disconnected the device from the charger, let battery drain 1-2%, then ran "adb shell dumpsys batterystats" and visually inspected. Test: Did the above test with and without power.stats HAL available Test: Took a bugreport and verified that subsystem stats show up in Volta Historian. Change-Id: I5eb63fcbbf87a134bd8c34a11330db8b55df3894 --- services/core/jni/Android.bp | 1 + ..._android_server_am_BatteryStatsService.cpp | 414 +++++++++++++++++- 2 files changed, 395 insertions(+), 20 deletions(-) diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 6d7219173b323..b4fe83704ff29 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -111,6 +111,7 @@ cc_defaults { "android.hardware.light@2.0", "android.hardware.power@1.0", "android.hardware.power@1.1", + "android.hardware.power.stats@1.0", "android.hardware.tetheroffload.config@1.0", "android.hardware.thermal@1.0", "android.hardware.tv.cec@1.0", diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp index 0ff60e44b0ceb..024760d46760e 100644 --- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp +++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp @@ -27,9 +27,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -74,6 +76,33 @@ static jmethodID jgetSubsystem = NULL; static jmethodID jputVoter = NULL; static jmethodID jputState = NULL; +std::mutex gPowerHalMutex; +std::unordered_map gPowerStatsHalEntityNames = {}; +std::unordered_map> + gPowerStatsHalStateNames = {}; +std::vector gPowerStatsHalPlatformIds = {}; +std::vector gPowerStatsHalSubsystemIds = {}; +sp gPowerStatsHalV1_0 = nullptr; +std::function gGetLowPowerStatsImpl = {}; +std::function gGetPlatformLowPowerStatsImpl = {}; +std::function gGetSubsystemLowPowerStatsImpl = {}; + +// The caller must be holding gPowerHalMutex. +static void deinitPowerStatsLocked() { + gPowerStatsHalV1_0 = nullptr; +} + +struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient { + virtual void serviceDied(uint64_t cookie, + const wp& who) override { + // The HAL just died. Reset all handles to HAL services. + std::lock_guard lock(gPowerHalMutex); + deinitPowerStatsLocked(); + } +}; + +sp gDeathRecipient = new PowerHalDeathRecipient(); + class WakeupCallback : public ISystemSuspendCallback { public: Return notifyWakeup(bool success) override { @@ -202,18 +231,291 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) return mergedreasonpos - mergedreason; } -static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) { - if (jrpmStats == NULL) { - jniThrowException(env, "java/lang/NullPointerException", - "The rpmstats jni input jobject jrpmStats is null."); - return; +// The caller must be holding gPowerHalMutex. +static bool checkResultLocked(const Return &ret, const char* function) { + if (!ret.isOk()) { + ALOGE("%s failed: requested HAL service not available. Description: %s", + function, ret.description().c_str()); + if (ret.isDeadObject()) { + deinitPowerStatsLocked(); + } + return false; } - if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL - || jputVoter == NULL || jputState == NULL) { - ALOGE("A rpmstats jni jmethodID is null."); + return true; +} + +// The caller must be holding gPowerHalMutex. +// gPowerStatsHalV1_0 must not be null +static bool initializePowerStats() { + using android::hardware::power::stats::V1_0::Status; + using android::hardware::power::stats::V1_0::PowerEntityType; + + // Clear out previous content if we are re-initializing + gPowerStatsHalEntityNames.clear(); + gPowerStatsHalStateNames.clear(); + gPowerStatsHalPlatformIds.clear(); + gPowerStatsHalSubsystemIds.clear(); + + Return ret; + ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) { + if (status != Status::SUCCESS) { + ALOGE("Error getting power entity info"); + return; + } + + // construct lookup table of powerEntityId to power entity name + // also construct vector of platform and subsystem IDs + for (auto info : infos) { + gPowerStatsHalEntityNames.emplace(info.powerEntityId, info.powerEntityName); + if (info.type == PowerEntityType::POWER_DOMAIN) { + gPowerStatsHalPlatformIds.emplace_back(info.powerEntityId); + } else { + gPowerStatsHalSubsystemIds.emplace_back(info.powerEntityId); + } + } + }); + if (!checkResultLocked(ret, __func__)) { + return false; + } + + ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) { + if (status != Status::SUCCESS) { + ALOGE("Error getting state info"); + return; + } + + // construct lookup table of powerEntityId, powerEntityStateId to power entity state name + for (auto stateSpace : stateSpaces) { + std::unordered_map stateNames = {}; + for (auto state : stateSpace.states) { + stateNames.emplace(state.powerEntityStateId, + state.powerEntityStateName); + } + gPowerStatsHalStateNames.emplace(stateSpace.powerEntityId, stateNames); + } + }); + if (!checkResultLocked(ret, __func__)) { + return false; + } + + return (!gPowerStatsHalEntityNames.empty()) && (!gPowerStatsHalStateNames.empty()); +} + +// The caller must be holding gPowerHalMutex. +static bool getPowerStatsHalLocked() { + if (gPowerStatsHalV1_0 == nullptr) { + gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService(); + if (gPowerStatsHalV1_0 == nullptr) { + ALOGE("Unable to get power.stats HAL service."); + return false; + } + + // Link death recipient to power.stats service handle + hardware::Return linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0); + if (!linked.isOk()) { + ALOGE("Transaction error in linking to power.stats HAL death: %s", + linked.description().c_str()); + deinitPowerStatsLocked(); + return false; + } else if (!linked) { + ALOGW("Unable to link to power.stats HAL death notifications"); + // We should still continue even though linking failed + } + return initializePowerStats(); + } + return true; +} + +// The caller must be holding powerHalMutex. +static void getPowerStatsHalLowPowerData(JNIEnv* env, jobject jrpmStats) { + using android::hardware::power::stats::V1_0::Status; + + if (!getPowerStatsHalLocked()) { + ALOGE("failed to get low power stats"); return; } + // Get power entity state residency data + bool success = false; + Return ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({}, + [&env, &jrpmStats, &success](auto results, auto status) { + if (status == Status::NOT_SUPPORTED) { + ALOGW("getPowerEntityStateResidencyData is not supported"); + success = false; + return; + } + + for (auto result : results) { + jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem, + env->NewStringUTF(gPowerStatsHalEntityNames.at(result.powerEntityId).c_str())); + if (jsubsystem == NULL) { + ALOGE("The rpmstats jni jobject jsubsystem is null."); + return; + } + for (auto stateResidency : result.stateResidencyData) { + + env->CallVoidMethod(jsubsystem, jputState, + env->NewStringUTF(gPowerStatsHalStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId).c_str()), + stateResidency.totalTimeInStateMs, + stateResidency.totalStateEntryCount); + } + } + success = true; + }); + checkResultLocked(ret, __func__); + if (!success) { + ALOGE("getPowerEntityStateResidencyData failed"); + } +} + +static jint getPowerStatsHalPlatformData(JNIEnv* env, jobject outBuf) { + using android::hardware::power::stats::V1_0::Status; + using hardware::power::stats::V1_0::PowerEntityStateResidencyResult; + using hardware::power::stats::V1_0::PowerEntityStateResidencyData; + + if (!getPowerStatsHalLocked()) { + ALOGE("failed to get low power stats"); + return -1; + } + + char *output = (char*)env->GetDirectBufferAddress(outBuf); + char *offset = output; + int remaining = (int)env->GetDirectBufferCapacity(outBuf); + int total_added = -1; + + // Get power entity state residency data + Return ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData( + gPowerStatsHalPlatformIds, + [&offset, &remaining, &total_added](auto results, auto status) { + if (status == Status::NOT_SUPPORTED) { + ALOGW("getPowerEntityStateResidencyData is not supported"); + return; + } + + for (size_t i = 0; i < results.size(); i++) { + const PowerEntityStateResidencyResult& result = results[i]; + + for (size_t j = 0; j < result.stateResidencyData.size(); j++) { + const PowerEntityStateResidencyData& stateResidency = + result.stateResidencyData[j]; + int added = snprintf(offset, remaining, + "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ", + j + 1, gPowerStatsHalStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId).c_str(), + stateResidency.totalTimeInStateMs, + stateResidency.totalStateEntryCount); + if (added < 0) { + break; + } + if (added > remaining) { + added = remaining; + } + offset += added; + remaining -= added; + total_added += added; + } + if (remaining <= 0) { + /* rewrite NULL character*/ + offset--; + total_added--; + ALOGE("power.stats Hal: buffer not enough"); + break; + } + } + }); + if (!checkResultLocked(ret, __func__)) { + return -1; + } + + total_added += 1; + return total_added; +} + +static jint getPowerStatsHalSubsystemData(JNIEnv* env, jobject outBuf) { + using android::hardware::power::stats::V1_0::Status; + using hardware::power::stats::V1_0::PowerEntityStateResidencyResult; + using hardware::power::stats::V1_0::PowerEntityStateResidencyData; + + if (!getPowerStatsHalLocked()) { + ALOGE("failed to get low power stats"); + return -1; + } + + char *output = (char*)env->GetDirectBufferAddress(outBuf); + char *offset = output; + int remaining = (int)env->GetDirectBufferCapacity(outBuf); + int total_added = -1; + + // Get power entity state residency data + Return ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData( + gPowerStatsHalSubsystemIds, + [&offset, &remaining, &total_added](auto results, auto status) { + if (status == Status::NOT_SUPPORTED) { + ALOGW("getPowerEntityStateResidencyData is not supported"); + return; + } + + int added = snprintf(offset, remaining, "SubsystemPowerState "); + offset += added; + remaining -= added; + total_added += added; + + for (size_t i = 0; i < results.size(); i++) { + const PowerEntityStateResidencyResult& result = results[i]; + added = snprintf(offset, remaining, "subsystem_%zu name=%s ", + i + 1, gPowerStatsHalEntityNames.at(result.powerEntityId).c_str()); + if (added < 0) { + break; + } + + if (added > remaining) { + added = remaining; + } + + offset += added; + remaining -= added; + total_added += added; + + for (size_t j = 0; j < result.stateResidencyData.size(); j++) { + const PowerEntityStateResidencyData& stateResidency = + result.stateResidencyData[j]; + added = snprintf(offset, remaining, + "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%" + PRIu64 " ", j + 1, gPowerStatsHalStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId).c_str(), + stateResidency.totalTimeInStateMs, + stateResidency.totalStateEntryCount, + stateResidency.lastEntryTimestampMs); + if (added < 0) { + break; + } + if (added > remaining) { + added = remaining; + } + offset += added; + remaining -= added; + total_added += added; + } + if (remaining <= 0) { + /* rewrite NULL character*/ + offset--; + total_added--; + ALOGE("power.stats Hal: buffer not enough"); + break; + } + } + }); + if (!checkResultLocked(ret, __func__)) { + return -1; + } + + total_added += 1; + return total_added; +} + +// The caller must be holding powerHalMutex. +static void getPowerHalLowPowerData(JNIEnv* env, jobject jrpmStats) { sp powerHalV1_0 = getPowerHalV1_0(); if (powerHalV1_0 == nullptr) { ALOGE("Power Hal not loaded"); @@ -286,17 +588,12 @@ static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats processPowerHalReturn(ret, "getSubsystemLowPowerStats"); } -static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) { +static jint getPowerHalPlatformData(JNIEnv* env, jobject outBuf) { char *output = (char*)env->GetDirectBufferAddress(outBuf); char *offset = output; int remaining = (int)env->GetDirectBufferCapacity(outBuf); int total_added = -1; - if (outBuf == NULL) { - jniThrowException(env, "java/lang/NullPointerException", "null argument"); - return -1; - } - { sp powerHalV1_0 = getPowerHalV1_0(); if (powerHalV1_0 == nullptr) { @@ -365,7 +662,7 @@ static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject o return total_added; } -static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) { +static jint getPowerHalSubsystemData(JNIEnv* env, jobject outBuf) { char *output = (char*)env->GetDirectBufferAddress(outBuf); char *offset = output; int remaining = (int)env->GetDirectBufferCapacity(outBuf); @@ -374,11 +671,6 @@ static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject // This is a IPower 1.1 API sp powerHal_1_1 = nullptr; - if (outBuf == NULL) { - jniThrowException(env, "java/lang/NullPointerException", "null argument"); - return -1; - } - { // Trying to get 1.1, this will succeed only for devices supporting 1.1 powerHal_1_1 = getPowerHalV1_1(); @@ -458,6 +750,88 @@ static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject return total_added; } +static void setUpPowerStatsLocked() { + // First see if power.stats HAL is available. Fall back to power HAL if + // power.stats HAL is unavailable. + if (android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) { + ALOGI("Using power.stats HAL"); + gGetLowPowerStatsImpl = getPowerStatsHalLowPowerData; + gGetPlatformLowPowerStatsImpl = getPowerStatsHalPlatformData; + gGetSubsystemLowPowerStatsImpl = getPowerStatsHalSubsystemData; + } else if (android::hardware::power::V1_0::IPower::getService() != nullptr) { + ALOGI("Using power HAL"); + gGetLowPowerStatsImpl = getPowerHalLowPowerData; + gGetPlatformLowPowerStatsImpl = getPowerHalPlatformData; + gGetSubsystemLowPowerStatsImpl = getPowerHalSubsystemData; + } +} + +static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) { + if (jrpmStats == NULL) { + jniThrowException(env, "java/lang/NullPointerException", + "The rpmstats jni input jobject jrpmStats is null."); + return; + } + if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL + || jputVoter == NULL || jputState == NULL) { + ALOGE("A rpmstats jni jmethodID is null."); + return; + } + + std::lock_guard lock(gPowerHalMutex); + + if (!gGetLowPowerStatsImpl) { + setUpPowerStatsLocked(); + } + + if (gGetLowPowerStatsImpl) { + return gGetLowPowerStatsImpl(env, jrpmStats); + } + + ALOGE("Unable to load Power Hal or power.stats HAL"); + return; +} + +static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) { + if (outBuf == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "null argument"); + return -1; + } + + std::lock_guard lock(gPowerHalMutex); + + if (!gGetPlatformLowPowerStatsImpl) { + setUpPowerStatsLocked(); + } + + if (gGetPlatformLowPowerStatsImpl) { + return gGetPlatformLowPowerStatsImpl(env, outBuf); + } + + ALOGE("Unable to load Power Hal or power.stats HAL"); + return -1; +} + +static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) { + if (outBuf == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "null argument"); + return -1; + } + + std::lock_guard lock(gPowerHalMutex); + + if (!gGetSubsystemLowPowerStatsImpl) { + setUpPowerStatsLocked(); + } + + if (gGetSubsystemLowPowerStatsImpl) { + return gGetSubsystemLowPowerStatsImpl(env, outBuf); + } + + ALOGE("Unable to load Power Hal or power.stats HAL"); + return -1; +} + static const JNINativeMethod method_table[] = { { "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup }, { "getLowPowerStats", "(Lcom/android/internal/os/RpmStats;)V", (void*)getLowPowerStats },