diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fa209cc02cd1b..520730fa7352e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -3373,6 +3373,11 @@ public abstract class BatteryStats implements Parcelable { public abstract Map getKernelWakelockStats(); + /** + * Returns aggregated wake lock stats. + */ + public abstract WakeLockStats getWakeLockStats(); + /** * Returns Timers tracking the total time of each Resource Power Manager state and voter. */ diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 7f526c13a75a3..6339435e539c4 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -157,6 +157,7 @@ public final class BatteryStatsManager { @Retention(RetentionPolicy.SOURCE) public @interface WifiSupplState {} + private final IBatteryStats mBatteryStats; /** @hide */ @@ -351,6 +352,21 @@ public final class BatteryStatsManager { } } + /** + * Retrieves accumulate wake lock stats. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BATTERY_STATS) + @NonNull + public WakeLockStats getWakeLockStats() { + try { + return mBatteryStats.getWakeLockStats(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Indicates an app acquiring full wifi lock. * diff --git a/core/java/android/os/WakeLockStats.aidl b/core/java/android/os/WakeLockStats.aidl new file mode 100644 index 0000000000000..be08d782264f9 --- /dev/null +++ b/core/java/android/os/WakeLockStats.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 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 android.os; + +/** {@hide} */ +parcelable WakeLockStats; diff --git a/core/java/android/os/WakeLockStats.java b/core/java/android/os/WakeLockStats.java new file mode 100644 index 0000000000000..05a7313d1600f --- /dev/null +++ b/core/java/android/os/WakeLockStats.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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 android.os; + +import android.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Snapshot of wake lock stats. + * @hide + */ +public final class WakeLockStats implements Parcelable { + + /** @hide */ + public static class WakeLock { + public final int uid; + @NonNull + public final String name; + public final int timesAcquired; + public final long totalTimeHeldMs; + + /** + * Time in milliseconds that the lock has been held or 0 if not currently holding the lock + */ + public final long timeHeldMs; + + public WakeLock(int uid, @NonNull String name, int timesAcquired, long totalTimeHeldMs, + long timeHeldMs) { + this.uid = uid; + this.name = name; + this.timesAcquired = timesAcquired; + this.totalTimeHeldMs = totalTimeHeldMs; + this.timeHeldMs = timeHeldMs; + } + + private WakeLock(Parcel in) { + uid = in.readInt(); + name = in.readString(); + timesAcquired = in.readInt(); + totalTimeHeldMs = in.readLong(); + timeHeldMs = in.readLong(); + } + + private void writeToParcel(Parcel out) { + out.writeInt(uid); + out.writeString(name); + out.writeInt(timesAcquired); + out.writeLong(totalTimeHeldMs); + out.writeLong(timeHeldMs); + } + + @Override + public String toString() { + return "WakeLock{" + + "uid=" + uid + + ", name='" + name + '\'' + + ", timesAcquired=" + timesAcquired + + ", totalTimeHeldMs=" + totalTimeHeldMs + + ", timeHeldMs=" + timeHeldMs + + '}'; + } + } + + private final List mWakeLocks; + + /** @hide **/ + public WakeLockStats(@NonNull List wakeLocks) { + mWakeLocks = wakeLocks; + } + + @NonNull + public List getWakeLocks() { + return mWakeLocks; + } + + private WakeLockStats(Parcel in) { + final int size = in.readInt(); + mWakeLocks = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + mWakeLocks.add(new WakeLock(in)); + } + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + final int size = mWakeLocks.size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + WakeLock stats = mWakeLocks.get(i); + stats.writeToParcel(out); + } + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + public WakeLockStats createFromParcel(Parcel in) { + return new WakeLockStats(in); + } + + public WakeLockStats[] newArray(int size) { + return new WakeLockStats[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "WakeLockStats " + mWakeLocks; + } +} diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 1d13b73fc1861..587876df0df6a 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -22,6 +22,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.ParcelFileDescriptor; +import android.os.WakeLockStats; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.WifiActivityEnergyInfo; @@ -157,6 +158,10 @@ interface IBatteryStats { /** {@hide} */ GpsBatteryStats getGpsBatteryStats(); + /** {@hide} */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)") + WakeLockStats getWakeLockStats(); + HealthStatsParceler takeUidSnapshot(int uid); HealthStatsParceler[] takeUidSnapshots(in int[] uid); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 209c64a023242..21f719c74aa80 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.WakeLockStats; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.os.connectivity.CellularBatteryStats; @@ -1143,6 +1144,37 @@ public class BatteryStatsImpl extends BatteryStats { return mKernelWakelockStats; } + @Override + public WakeLockStats getWakeLockStats() { + final long realtimeMs = mClock.elapsedRealtime(); + final long realtimeUs = realtimeMs * 1000; + List uidWakeLockStats = new ArrayList<>(); + for (int i = mUidStats.size() - 1; i >= 0; i--) { + final Uid uid = mUidStats.valueAt(i); + final ArrayMap wakelockStats = + uid.mWakelockStats.getMap(); + for (int j = wakelockStats.size() - 1; j >= 0; j--) { + final String name = wakelockStats.keyAt(j); + final Uid.Wakelock wakelock = (Uid.Wakelock) wakelockStats.valueAt(j); + final DualTimer timer = wakelock.mTimerPartial; + if (timer != null) { + final long totalTimeLockHeldMs = + timer.getTotalTimeLocked(realtimeUs, STATS_SINCE_CHARGED) / 1000; + if (totalTimeLockHeldMs != 0) { + uidWakeLockStats.add( + new WakeLockStats.WakeLock(uid.getUid(), name, + timer.getCountLocked(STATS_SINCE_CHARGED), + totalTimeLockHeldMs, + timer.isRunningLocked() + ? timer.getCurrentDurationMsLocked(realtimeMs) + : 0)); + } + } + } + } + return new WakeLockStats(uidWakeLockStats); + } + String mLastWakeupReason = null; long mLastWakeupUptimeMs = 0; private final HashMap mWakeupReasonStats = new HashMap<>(); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index c1a45c47539d9..388cf6e15e0b0 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -22,6 +22,8 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED; import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE; import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -36,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.os.BatteryStats; +import android.os.WakeLockStats; import android.util.SparseArray; import android.view.Display; @@ -50,6 +53,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + @LargeTest @RunWith(AndroidJUnit4.class) @SuppressWarnings("GuardedBy") @@ -507,4 +512,51 @@ public class BatteryStatsImplTest { final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid); u.addIsolatedUid(childUid); } + + @Test + public void testGetWakeLockStats() { + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + // First wakelock, acquired once, not currently held + mMockClock.realtime = 1000; + mBatteryStatsImpl.noteStartWakeLocked(10100, 100, null, "wakeLock1", null, + BatteryStats.WAKE_TYPE_PARTIAL, false); + + mMockClock.realtime = 3000; + mBatteryStatsImpl.noteStopWakeLocked(10100, 100, null, "wakeLock1", null, + BatteryStats.WAKE_TYPE_PARTIAL); + + // Second wakelock, acquired twice, still held + mMockClock.realtime = 4000; + mBatteryStatsImpl.noteStartWakeLocked(10200, 101, null, "wakeLock2", null, + BatteryStats.WAKE_TYPE_PARTIAL, false); + + mMockClock.realtime = 5000; + mBatteryStatsImpl.noteStopWakeLocked(10200, 101, null, "wakeLock2", null, + BatteryStats.WAKE_TYPE_PARTIAL); + + mMockClock.realtime = 6000; + mBatteryStatsImpl.noteStartWakeLocked(10200, 101, null, "wakeLock2", null, + BatteryStats.WAKE_TYPE_PARTIAL, false); + + mMockClock.realtime = 9000; + + List wakeLockStats = + mBatteryStatsImpl.getWakeLockStats().getWakeLocks(); + assertThat(wakeLockStats).hasSize(2); + + WakeLockStats.WakeLock wakeLock1 = wakeLockStats.stream() + .filter(wl -> wl.uid == 10100 && wl.name.equals("wakeLock1")).findFirst().get(); + + assertThat(wakeLock1.timesAcquired).isEqualTo(1); + assertThat(wakeLock1.timeHeldMs).isEqualTo(0); // Not currently held + assertThat(wakeLock1.totalTimeHeldMs).isEqualTo(2000); // 3000-1000 + + WakeLockStats.WakeLock wakeLock2 = wakeLockStats.stream() + .filter(wl -> wl.uid == 10200 && wl.name.equals("wakeLock2")).findFirst().get(); + + assertThat(wakeLock2.timesAcquired).isEqualTo(2); + assertThat(wakeLock2.timeHeldMs).isEqualTo(3000); // 9000-6000 + assertThat(wakeLock2.totalTimeHeldMs).isEqualTo(4000); // (5000-4000) + (9000-6000) + } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 5fc11e8fff7c6..8cb20404b3e16 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -56,6 +56,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.WakeLockStats; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.GpsBatteryStats; @@ -2595,6 +2596,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + /** + * Gets a snapshot of wake lock stats + * @hide + */ + public WakeLockStats getWakeLockStats() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BATTERY_STATS, null); + + // Wait for the completion of pending works if there is any + awaitCompletion(); + synchronized (mStats) { + return mStats.getWakeLockStats(); + } + } + /** * Gets a snapshot of the system health for a particular uid. */