Loading services/core/java/com/android/server/power/feature/PowerManagerFlags.java +11 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,9 @@ public class PowerManagerFlags { Flags::perDisplayWakeByTouch ); private final FlagState mFrameworkWakelockInfo = new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo); /** Returns whether early-screen-timeout-detector is enabled on not. */ public boolean isEarlyScreenTimeoutDetectorEnabled() { return mEarlyScreenTimeoutDetectorFlagState.isEnabled(); Loading @@ -66,6 +69,13 @@ public class PowerManagerFlags { return mPerDisplayWakeByTouch.isEnabled(); } /** * @return Whether FrameworkWakelockInfo atom logging is enabled or not. */ public boolean isFrameworkWakelockInfoEnabled() { return mFrameworkWakelockInfo.isEnabled(); } /** * dumps all flagstates * @param pw printWriter Loading @@ -75,6 +85,7 @@ public class PowerManagerFlags { pw.println(" " + mEarlyScreenTimeoutDetectorFlagState); pw.println(" " + mImproveWakelockLatency); pw.println(" " + mPerDisplayWakeByTouch); pw.println(" " + mFrameworkWakelockInfo); } private static class FlagState { Loading services/core/java/com/android/server/power/feature/power_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -26,3 +26,10 @@ flag { bug: "343295183" is_fixed_read_only: true } flag { name: "framework_wakelock_info" namespace: "power" description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms" bug: "352602149" } services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +18 −1 Original line number Diff line number Diff line Loading @@ -145,6 +145,7 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.power.feature.PowerManagerFlags; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.server.power.stats.format.MobileRadioPowerStatsLayout; Loading Loading @@ -5142,6 +5143,10 @@ public class BatteryStatsImpl extends BatteryStats { mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, uidStats.mProcessState, true /* acquired */, getPowerManagerWakeLockLevel(type)); if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) { mFrameworkEvents.noteStartWakeLock( mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs); } } } Loading Loading @@ -5187,6 +5192,10 @@ public class BatteryStatsImpl extends BatteryStats { mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, uidStats.mProcessState, false/* acquired */, getPowerManagerWakeLockLevel(type)); if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) { mFrameworkEvents.noteStopWakeLock( mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs); } if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. Loading Loading @@ -11343,6 +11352,9 @@ public class BatteryStatsImpl extends BatteryStats { return mTmpCpuTimeInFreq; } WakelockStatsFrameworkEvents mFrameworkEvents = new WakelockStatsFrameworkEvents(); PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags(); public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock, @NonNull MonotonicClock monotonicClock, @Nullable File systemDir, @NonNull Handler handler, @Nullable PlatformIdleStateCallback platformIdleStateCallback, Loading Loading @@ -15964,6 +15976,10 @@ public class BatteryStatsImpl extends BatteryStats { // Already plugged in. Schedule the long plug in alarm. scheduleNextResetWhilePluggedInCheck(); } if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) { mFrameworkEvents.initialize(context); } } } Loading Loading @@ -16791,7 +16807,8 @@ public class BatteryStatsImpl extends BatteryStats { } int NSORPMS = in.readInt(); if (NSORPMS > 10000) { throw new ParcelFormatException("File corrupt: too many screen-off rpm stats " + NSORPMS); throw new ParcelFormatException( "File corrupt: too many screen-off rpm stats " + NSORPMS); } for (int irpm = 0; irpm < NSORPMS; irpm++) { if (in.readInt() != 0) { services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java 0 → 100644 +328 −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. */ package com.android.server.power.stats; import android.app.StatsManager; import android.content.Context; import android.os.SystemClock; import android.util.Log; import android.util.StatsEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FrameworkStatsLog; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; /** A class to initialise and log metrics pulled by statsd. */ public class WakelockStatsFrameworkEvents { // statsd has a dimensional limit on the number of different keys it can handle. // Beyond that limit, statsd will drop data. // // When we have seem SUMMARY_THRESHOLD distinct (uid, tag, wakeLockLevel) keys, // we start summarizing new keys as (uid, OVERFLOW_TAG, OVERFLOW_LEVEL) to // reduce the number of keys we pass to statsd. // // When we reach MAX_WAKELOCK_DIMENSIONS distinct keys, we summarize all new keys // as (OVERFLOW_UID, HARD_CAP_TAG, OVERFLOW_LEVEL) to hard cap the number of // distinct keys we pass to statsd. @VisibleForTesting public static final int SUMMARY_THRESHOLD = 500; @VisibleForTesting public static final int MAX_WAKELOCK_DIMENSIONS = 1000; @VisibleForTesting public static final int HARD_CAP_UID = -1; @VisibleForTesting public static final String OVERFLOW_TAG = "*overflow*"; @VisibleForTesting public static final String HARD_CAP_TAG = "*overflow hard cap*"; @VisibleForTesting public static final int OVERFLOW_LEVEL = 1; private static class WakeLockKey { private int uid; private String tag; private int powerManagerWakeLockLevel; private int hashCode; WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel) { this.uid = uid; this.tag = new String(tag); this.powerManagerWakeLockLevel = powerManagerWakeLockLevel; this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel); } int getUid() { return uid; } String getTag() { return tag; } int getPowerManagerWakeLockLevel() { return powerManagerWakeLockLevel; } void setOverflow() { tag = OVERFLOW_TAG; powerManagerWakeLockLevel = OVERFLOW_LEVEL; this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel); } void setHardCap() { uid = HARD_CAP_UID; tag = HARD_CAP_TAG; powerManagerWakeLockLevel = OVERFLOW_LEVEL; this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof WakeLockKey)) return false; WakeLockKey that = (WakeLockKey) o; return uid == that.uid && tag.equals(that.tag) && powerManagerWakeLockLevel == that.powerManagerWakeLockLevel; } @Override public int hashCode() { return this.hashCode; } } private static class WakeLockStats { // accumulated uptime attributed to this WakeLock since boot, where overlap // (including nesting) is ignored public long uptimeMillis = 0; // count of WakeLocks that have been acquired and then released public long completedCount = 0; } private final Object mLock = new Object(); @GuardedBy("mLock") private final Map<WakeLockKey, WakeLockStats> mWakeLockStats = new HashMap<>(); private static class WakeLockData { // uptime millis when first acquired public long acquireUptimeMillis = 0; public int refCount = 0; WakeLockData(long uptimeMillis) { acquireUptimeMillis = uptimeMillis; } } @GuardedBy("mLock") private final Map<WakeLockKey, WakeLockData> mOpenWakeLocks = new HashMap<>(); public void noteStartWakeLock( int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) { final WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel); synchronized (mLock) { WakeLockData data = mOpenWakeLocks.computeIfAbsent(key, k -> new WakeLockData(eventUptimeMillis)); data.refCount++; mOpenWakeLocks.put(key, data); } } @VisibleForTesting public boolean inOverflow() { synchronized (mLock) { return inOverflowLocked(); } } @GuardedBy("mLock") private boolean inOverflowLocked() { return mWakeLockStats.size() >= SUMMARY_THRESHOLD; } @VisibleForTesting public boolean inHardCap() { synchronized (mLock) { return inHardCapLocked(); } } @GuardedBy("mLock") private boolean inHardCapLocked() { return mWakeLockStats.size() >= MAX_WAKELOCK_DIMENSIONS; } public void noteStopWakeLock( int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) { WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel); synchronized (mLock) { WakeLockData data = mOpenWakeLocks.get(key); if (data == null) { Log.e(TAG, "WakeLock not found when stopping: " + uid + " " + tag); return; } if (data.refCount == 1) { mOpenWakeLocks.remove(key); long wakeLockDur = eventUptimeMillis - data.acquireUptimeMillis; // Rewrite key if in an overflow state. if (inOverflowLocked() && !mWakeLockStats.containsKey(key)) { key.setOverflow(); if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) { key.setHardCap(); } } WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats()); stats.uptimeMillis += wakeLockDur; stats.completedCount++; mWakeLockStats.put(key, stats); } else { data.refCount--; mOpenWakeLocks.put(key, data); } } } public List<StatsEvent> pullFrameworkWakelockInfoAtoms() { return pullFrameworkWakelockInfoAtoms(SystemClock.uptimeMillis()); } public List<StatsEvent> pullFrameworkWakelockInfoAtoms(long nowMillis) { List<StatsEvent> result = new ArrayList<>(); HashSet<WakeLockKey> keys = new HashSet<>(); // Used to collect open WakeLocks when in an overflow state. HashMap<WakeLockKey, WakeLockStats> openOverflowStats = new HashMap<>(); synchronized (mLock) { keys.addAll(mWakeLockStats.keySet()); // If we are in an overflow state, an open wakelock may have a new key // that needs to be summarized. if (inOverflowLocked()) { for (WakeLockKey key : mOpenWakeLocks.keySet()) { if (!mWakeLockStats.containsKey(key)) { WakeLockData data = mOpenWakeLocks.get(key); key.setOverflow(); if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) { key.setHardCap(); } keys.add(key); WakeLockStats stats = openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats()); stats.uptimeMillis += nowMillis - data.acquireUptimeMillis; openOverflowStats.put(key, stats); } } } else { keys.addAll(mOpenWakeLocks.keySet()); } for (WakeLockKey key : keys) { long openWakeLockUptime = 0; WakeLockData data = mOpenWakeLocks.get(key); if (data != null) { openWakeLockUptime = nowMillis - data.acquireUptimeMillis; } WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats()); WakeLockStats extraTime = openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats()); stats.uptimeMillis += openWakeLockUptime + extraTime.uptimeMillis; StatsEvent event = StatsEvent.newBuilder() .setAtomId(FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO) .writeInt(key.getUid()) .writeString(key.getTag()) .writeInt(key.getPowerManagerWakeLockLevel()) .writeLong(stats.uptimeMillis) .writeLong(stats.completedCount) .build(); result.add(event); } } return result; } private static final String TAG = "BatteryStatsPulledMetrics"; private final StatsPullCallbackHandler mStatsPullCallbackHandler = new StatsPullCallbackHandler(); private boolean mIsInitialized = false; public void initialize(Context context) { if (mIsInitialized) { return; } final StatsManager statsManager = context.getSystemService(StatsManager.class); if (statsManager == null) { Log.e( TAG, "Error retrieving StatsManager. Cannot initialize BatteryStatsPulledMetrics."); } else { Log.d(TAG, "Registering callback with StatsManager"); // DIRECT_EXECUTOR means that callback will run on binder thread. statsManager.setPullAtomCallback( FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO, null /* metadata */, ConcurrentUtils.DIRECT_EXECUTOR, mStatsPullCallbackHandler); mIsInitialized = true; } } private class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback { @Override public int onPullAtom(int atomTag, List<StatsEvent> data) { // handle the tags appropriately. List<StatsEvent> events = pullEvents(atomTag); if (events == null) { return StatsManager.PULL_SKIP; } data.addAll(events); return StatsManager.PULL_SUCCESS; } private List<StatsEvent> pullEvents(int atomTag) { switch (atomTag) { case FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO: return pullFrameworkWakelockInfoAtoms(); default: return null; } } } } services/tests/powerstatstests/Android.bp +6 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,9 @@ android_test { "servicestests-utils", "platform-test-annotations", "flag-junit", "statsdprotolite", "StatsdTestUtils", "platformprotoslite", ], libs: [ Loading Loading @@ -68,6 +71,9 @@ android_ravenwood_test { "androidx.test.uiautomator_uiautomator", "modules-utils-binary-xml", "flag-junit", "statsdprotolite", "StatsdTestUtils", "platformprotoslite", ], srcs: [ "src/com/android/server/power/stats/*.java", Loading Loading
services/core/java/com/android/server/power/feature/PowerManagerFlags.java +11 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,9 @@ public class PowerManagerFlags { Flags::perDisplayWakeByTouch ); private final FlagState mFrameworkWakelockInfo = new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo); /** Returns whether early-screen-timeout-detector is enabled on not. */ public boolean isEarlyScreenTimeoutDetectorEnabled() { return mEarlyScreenTimeoutDetectorFlagState.isEnabled(); Loading @@ -66,6 +69,13 @@ public class PowerManagerFlags { return mPerDisplayWakeByTouch.isEnabled(); } /** * @return Whether FrameworkWakelockInfo atom logging is enabled or not. */ public boolean isFrameworkWakelockInfoEnabled() { return mFrameworkWakelockInfo.isEnabled(); } /** * dumps all flagstates * @param pw printWriter Loading @@ -75,6 +85,7 @@ public class PowerManagerFlags { pw.println(" " + mEarlyScreenTimeoutDetectorFlagState); pw.println(" " + mImproveWakelockLatency); pw.println(" " + mPerDisplayWakeByTouch); pw.println(" " + mFrameworkWakelockInfo); } private static class FlagState { Loading
services/core/java/com/android/server/power/feature/power_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -26,3 +26,10 @@ flag { bug: "343295183" is_fixed_read_only: true } flag { name: "framework_wakelock_info" namespace: "power" description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms" bug: "352602149" }
services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +18 −1 Original line number Diff line number Diff line Loading @@ -145,6 +145,7 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.power.feature.PowerManagerFlags; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.server.power.stats.format.MobileRadioPowerStatsLayout; Loading Loading @@ -5142,6 +5143,10 @@ public class BatteryStatsImpl extends BatteryStats { mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, uidStats.mProcessState, true /* acquired */, getPowerManagerWakeLockLevel(type)); if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) { mFrameworkEvents.noteStartWakeLock( mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs); } } } Loading Loading @@ -5187,6 +5192,10 @@ public class BatteryStatsImpl extends BatteryStats { mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, uidStats.mProcessState, false/* acquired */, getPowerManagerWakeLockLevel(type)); if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) { mFrameworkEvents.noteStopWakeLock( mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs); } if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. Loading Loading @@ -11343,6 +11352,9 @@ public class BatteryStatsImpl extends BatteryStats { return mTmpCpuTimeInFreq; } WakelockStatsFrameworkEvents mFrameworkEvents = new WakelockStatsFrameworkEvents(); PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags(); public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock, @NonNull MonotonicClock monotonicClock, @Nullable File systemDir, @NonNull Handler handler, @Nullable PlatformIdleStateCallback platformIdleStateCallback, Loading Loading @@ -15964,6 +15976,10 @@ public class BatteryStatsImpl extends BatteryStats { // Already plugged in. Schedule the long plug in alarm. scheduleNextResetWhilePluggedInCheck(); } if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) { mFrameworkEvents.initialize(context); } } } Loading Loading @@ -16791,7 +16807,8 @@ public class BatteryStatsImpl extends BatteryStats { } int NSORPMS = in.readInt(); if (NSORPMS > 10000) { throw new ParcelFormatException("File corrupt: too many screen-off rpm stats " + NSORPMS); throw new ParcelFormatException( "File corrupt: too many screen-off rpm stats " + NSORPMS); } for (int irpm = 0; irpm < NSORPMS; irpm++) { if (in.readInt() != 0) {
services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java 0 → 100644 +328 −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. */ package com.android.server.power.stats; import android.app.StatsManager; import android.content.Context; import android.os.SystemClock; import android.util.Log; import android.util.StatsEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FrameworkStatsLog; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; /** A class to initialise and log metrics pulled by statsd. */ public class WakelockStatsFrameworkEvents { // statsd has a dimensional limit on the number of different keys it can handle. // Beyond that limit, statsd will drop data. // // When we have seem SUMMARY_THRESHOLD distinct (uid, tag, wakeLockLevel) keys, // we start summarizing new keys as (uid, OVERFLOW_TAG, OVERFLOW_LEVEL) to // reduce the number of keys we pass to statsd. // // When we reach MAX_WAKELOCK_DIMENSIONS distinct keys, we summarize all new keys // as (OVERFLOW_UID, HARD_CAP_TAG, OVERFLOW_LEVEL) to hard cap the number of // distinct keys we pass to statsd. @VisibleForTesting public static final int SUMMARY_THRESHOLD = 500; @VisibleForTesting public static final int MAX_WAKELOCK_DIMENSIONS = 1000; @VisibleForTesting public static final int HARD_CAP_UID = -1; @VisibleForTesting public static final String OVERFLOW_TAG = "*overflow*"; @VisibleForTesting public static final String HARD_CAP_TAG = "*overflow hard cap*"; @VisibleForTesting public static final int OVERFLOW_LEVEL = 1; private static class WakeLockKey { private int uid; private String tag; private int powerManagerWakeLockLevel; private int hashCode; WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel) { this.uid = uid; this.tag = new String(tag); this.powerManagerWakeLockLevel = powerManagerWakeLockLevel; this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel); } int getUid() { return uid; } String getTag() { return tag; } int getPowerManagerWakeLockLevel() { return powerManagerWakeLockLevel; } void setOverflow() { tag = OVERFLOW_TAG; powerManagerWakeLockLevel = OVERFLOW_LEVEL; this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel); } void setHardCap() { uid = HARD_CAP_UID; tag = HARD_CAP_TAG; powerManagerWakeLockLevel = OVERFLOW_LEVEL; this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof WakeLockKey)) return false; WakeLockKey that = (WakeLockKey) o; return uid == that.uid && tag.equals(that.tag) && powerManagerWakeLockLevel == that.powerManagerWakeLockLevel; } @Override public int hashCode() { return this.hashCode; } } private static class WakeLockStats { // accumulated uptime attributed to this WakeLock since boot, where overlap // (including nesting) is ignored public long uptimeMillis = 0; // count of WakeLocks that have been acquired and then released public long completedCount = 0; } private final Object mLock = new Object(); @GuardedBy("mLock") private final Map<WakeLockKey, WakeLockStats> mWakeLockStats = new HashMap<>(); private static class WakeLockData { // uptime millis when first acquired public long acquireUptimeMillis = 0; public int refCount = 0; WakeLockData(long uptimeMillis) { acquireUptimeMillis = uptimeMillis; } } @GuardedBy("mLock") private final Map<WakeLockKey, WakeLockData> mOpenWakeLocks = new HashMap<>(); public void noteStartWakeLock( int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) { final WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel); synchronized (mLock) { WakeLockData data = mOpenWakeLocks.computeIfAbsent(key, k -> new WakeLockData(eventUptimeMillis)); data.refCount++; mOpenWakeLocks.put(key, data); } } @VisibleForTesting public boolean inOverflow() { synchronized (mLock) { return inOverflowLocked(); } } @GuardedBy("mLock") private boolean inOverflowLocked() { return mWakeLockStats.size() >= SUMMARY_THRESHOLD; } @VisibleForTesting public boolean inHardCap() { synchronized (mLock) { return inHardCapLocked(); } } @GuardedBy("mLock") private boolean inHardCapLocked() { return mWakeLockStats.size() >= MAX_WAKELOCK_DIMENSIONS; } public void noteStopWakeLock( int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) { WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel); synchronized (mLock) { WakeLockData data = mOpenWakeLocks.get(key); if (data == null) { Log.e(TAG, "WakeLock not found when stopping: " + uid + " " + tag); return; } if (data.refCount == 1) { mOpenWakeLocks.remove(key); long wakeLockDur = eventUptimeMillis - data.acquireUptimeMillis; // Rewrite key if in an overflow state. if (inOverflowLocked() && !mWakeLockStats.containsKey(key)) { key.setOverflow(); if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) { key.setHardCap(); } } WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats()); stats.uptimeMillis += wakeLockDur; stats.completedCount++; mWakeLockStats.put(key, stats); } else { data.refCount--; mOpenWakeLocks.put(key, data); } } } public List<StatsEvent> pullFrameworkWakelockInfoAtoms() { return pullFrameworkWakelockInfoAtoms(SystemClock.uptimeMillis()); } public List<StatsEvent> pullFrameworkWakelockInfoAtoms(long nowMillis) { List<StatsEvent> result = new ArrayList<>(); HashSet<WakeLockKey> keys = new HashSet<>(); // Used to collect open WakeLocks when in an overflow state. HashMap<WakeLockKey, WakeLockStats> openOverflowStats = new HashMap<>(); synchronized (mLock) { keys.addAll(mWakeLockStats.keySet()); // If we are in an overflow state, an open wakelock may have a new key // that needs to be summarized. if (inOverflowLocked()) { for (WakeLockKey key : mOpenWakeLocks.keySet()) { if (!mWakeLockStats.containsKey(key)) { WakeLockData data = mOpenWakeLocks.get(key); key.setOverflow(); if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) { key.setHardCap(); } keys.add(key); WakeLockStats stats = openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats()); stats.uptimeMillis += nowMillis - data.acquireUptimeMillis; openOverflowStats.put(key, stats); } } } else { keys.addAll(mOpenWakeLocks.keySet()); } for (WakeLockKey key : keys) { long openWakeLockUptime = 0; WakeLockData data = mOpenWakeLocks.get(key); if (data != null) { openWakeLockUptime = nowMillis - data.acquireUptimeMillis; } WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats()); WakeLockStats extraTime = openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats()); stats.uptimeMillis += openWakeLockUptime + extraTime.uptimeMillis; StatsEvent event = StatsEvent.newBuilder() .setAtomId(FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO) .writeInt(key.getUid()) .writeString(key.getTag()) .writeInt(key.getPowerManagerWakeLockLevel()) .writeLong(stats.uptimeMillis) .writeLong(stats.completedCount) .build(); result.add(event); } } return result; } private static final String TAG = "BatteryStatsPulledMetrics"; private final StatsPullCallbackHandler mStatsPullCallbackHandler = new StatsPullCallbackHandler(); private boolean mIsInitialized = false; public void initialize(Context context) { if (mIsInitialized) { return; } final StatsManager statsManager = context.getSystemService(StatsManager.class); if (statsManager == null) { Log.e( TAG, "Error retrieving StatsManager. Cannot initialize BatteryStatsPulledMetrics."); } else { Log.d(TAG, "Registering callback with StatsManager"); // DIRECT_EXECUTOR means that callback will run on binder thread. statsManager.setPullAtomCallback( FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO, null /* metadata */, ConcurrentUtils.DIRECT_EXECUTOR, mStatsPullCallbackHandler); mIsInitialized = true; } } private class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback { @Override public int onPullAtom(int atomTag, List<StatsEvent> data) { // handle the tags appropriately. List<StatsEvent> events = pullEvents(atomTag); if (events == null) { return StatsManager.PULL_SKIP; } data.addAll(events); return StatsManager.PULL_SUCCESS; } private List<StatsEvent> pullEvents(int atomTag) { switch (atomTag) { case FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO: return pullFrameworkWakelockInfoAtoms(); default: return null; } } } }
services/tests/powerstatstests/Android.bp +6 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,9 @@ android_test { "servicestests-utils", "platform-test-annotations", "flag-junit", "statsdprotolite", "StatsdTestUtils", "platformprotoslite", ], libs: [ Loading Loading @@ -68,6 +71,9 @@ android_ravenwood_test { "androidx.test.uiautomator_uiautomator", "modules-utils-binary-xml", "flag-junit", "statsdprotolite", "StatsdTestUtils", "platformprotoslite", ], srcs: [ "src/com/android/server/power/stats/*.java", Loading