From 2ad7cd41697805d5236cfb01012f9fd0562c5104 Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Wed, 13 Jan 2021 19:29:22 -0800 Subject: [PATCH] ScreenPowerCalculator is responsible for smearing The estimated energy used by the screen is smeared over all apps based on their foreground time. This calculation is performed by BatteryStatsHelper after most of its other processing. In this cl, we move that logic to the ScreenPowerCalculator, which is actually responsible for calculating the total and app screen energy blaming. This also allows other power calculators for screen usage (such as actual screen energy measurements) to be hooked up in the future, without BSH worrying about whether to smear. Note: Smeared energy is not included in the app's total energy usage until the very end; prior to that, it is recorded in a special field BatterySipper.screenPowerMah. This is NOT changed in this cl. NOTE: The actual smearing algorithm is NOT altered in this cl. Test: atest BatteryStatsHelperTest Bug: 174818228 Bug: 162379528 Change-Id: I30b436b736e96071a1e4167d37512d250119665c Merged-In: I30b436b736e96071a1e4167d37512d250119665c (cherry picked from commit 3754504f4b149963e08526a17274e2f702481a3a) --- .../internal/os/BatteryStatsHelper.java | 67 +------------------ .../internal/os/ScreenPowerCalculator.java | 64 ++++++++++++++++++ .../internal/os/BatteryStatsHelperTest.java | 23 +++---- 3 files changed, 76 insertions(+), 78 deletions(-) diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index a21c68b4f01a9..fcf8bb4e748bb 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -41,7 +41,6 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; -import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; @@ -379,6 +378,7 @@ public class BatteryStatsHelper { mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() * mPowerProfile.getBatteryCapacity()) / 100; + // Create list of (almost all) sippers, calculate their usage, and put them in mUsageList. processAppUsage(asUsers); Collections.sort(mUsageList); @@ -556,8 +556,7 @@ public class BatteryStatsHelper { } /** - * Mark the {@link BatterySipper} that we should hide and smear the screen usage based on - * foreground activity time. + * Mark the {@link BatterySipper} that we should hide. * * @param sippers sipper list that need to check and remove * @return the total power of the hidden items of {@link BatterySipper} @@ -565,7 +564,6 @@ public class BatteryStatsHelper { */ public double removeHiddenBatterySippers(List sippers) { double proportionalSmearPowerMah = 0; - BatterySipper screenSipper = null; for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper sipper = sippers.get(i); sipper.shouldHide = shouldHideSipper(sipper); @@ -581,44 +579,10 @@ public class BatteryStatsHelper { proportionalSmearPowerMah += sipper.totalPowerMah; } } - - if (sipper.drainType == BatterySipper.DrainType.SCREEN) { - screenSipper = sipper; - } } - - smearScreenBatterySipper(sippers, screenSipper); - return proportionalSmearPowerMah; } - /** - * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity - * time. - */ - public void smearScreenBatterySipper(List sippers, BatterySipper screenSipper) { - long totalActivityTimeMs = 0; - final SparseLongArray activityTimeArray = new SparseLongArray(); - for (int i = 0, size = sippers.size(); i < size; i++) { - final BatteryStats.Uid uid = sippers.get(i).uidObj; - if (uid != null) { - final long timeMs = getProcessForegroundTimeMs(uid, - BatteryStats.STATS_SINCE_CHARGED); - activityTimeArray.put(uid.getUid(), timeMs); - totalActivityTimeMs += timeMs; - } - } - - if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) { - final double screenPowerMah = screenSipper.totalPowerMah; - for (int i = 0, size = sippers.size(); i < size; i++) { - final BatterySipper sipper = sippers.get(i); - sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0) - / totalActivityTimeMs; - } - } - } - /** * Check whether we should hide the battery sipper. */ @@ -681,33 +645,6 @@ public class BatteryStatsHelper { return timeMs * 1000; } - @VisibleForTesting - public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { - final BatteryStats.Timer timer = uid.getForegroundActivityTimer(); - if (timer != null) { - return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - } - - return 0; - } - - @VisibleForTesting - public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) { - final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime()); - final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP}; - - long timeUs = 0; - for (int type : foregroundTypes) { - final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which); - timeUs += localTime; - } - - // Return the min value of STATE_TOP time and foreground activity time, since both of these - // time have some errors. - return convertUsToMs( - Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs))); - } - @VisibleForTesting public void setPackageManager(PackageManager packageManager) { mPackageManager = packageManager; diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java index 25f6b4d169711..9c4a26724ba8b 100644 --- a/core/java/com/android/internal/os/ScreenPowerCalculator.java +++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java @@ -21,9 +21,14 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.SystemBatteryConsumer; +import android.os.SystemClock; import android.os.UserHandle; +import android.text.format.DateUtils; import android.util.Log; import android.util.SparseArray; +import android.util.SparseLongArray; + +import com.android.internal.annotations.VisibleForTesting; import java.util.List; @@ -57,6 +62,7 @@ public class ScreenPowerCalculator extends PowerCalculator { .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah); } + // TODO(b/178140704): Attribute screen usage similar to smearScreenBatterySipper. } /** @@ -73,6 +79,8 @@ public class ScreenPowerCalculator extends PowerCalculator { bs.usageTimeMs = durationMs; bs.sumPower(); sippers.add(bs); + + smearScreenBatterySipper(sippers, bs); } } @@ -96,4 +104,60 @@ public class ScreenPowerCalculator extends PowerCalculator { } return power; } + + /** + * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity + * time, and store this in the {@link BatterySipper#screenPowerMah} field. + */ + @VisibleForTesting + public void smearScreenBatterySipper(List sippers, BatterySipper screenSipper) { + + long totalActivityTimeMs = 0; + final SparseLongArray activityTimeArray = new SparseLongArray(); + for (int i = sippers.size() - 1; i >= 0; i--) { + final BatteryStats.Uid uid = sippers.get(i).uidObj; + if (uid != null) { + final long timeMs = getProcessForegroundTimeMs(uid); + activityTimeArray.put(uid.getUid(), timeMs); + totalActivityTimeMs += timeMs; + } + } + + if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) { + final double screenPowerMah = screenSipper.totalPowerMah; + for (int i = sippers.size() - 1; i >= 0; i--) { + final BatterySipper sipper = sippers.get(i); + sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0) + / totalActivityTimeMs; + } + } + } + + /** Get the minimum of the uid's ForegroundActivity time and its TOP time. */ + @VisibleForTesting + public long getProcessForegroundTimeMs(BatteryStats.Uid uid) { + final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000; + final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP}; + + long timeUs = 0; + for (int type : foregroundTypes) { + final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, + BatteryStats.STATS_SINCE_CHARGED); + timeUs += localTime; + } + + // Return the min value of STATE_TOP time and foreground activity time, since both of these + // time have some errors. + return Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)) / 1000; + } + + /** Get the ForegroundActivity time of the given uid. */ + @VisibleForTesting + public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { + final BatteryStats.Timer timer = uid.getForegroundActivityTimer(); + if (timer == null) { + return 0; + } + return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java index fbe16f2c39d17..d2107eaefefc7 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java @@ -21,12 +21,10 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -194,7 +192,6 @@ public class BatteryStatsHelperTest extends TestCase { sippers.add(mBluetoothBatterySipper); sippers.add(mIdleBatterySipper); doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper); - doNothing().when(mBatteryStatsHelper).smearScreenBatterySipper(any(), any()); final double totalUsage = mBatteryStatsHelper.removeHiddenBatterySippers(sippers); @@ -208,19 +205,20 @@ public class BatteryStatsHelperTest extends TestCase { @Test public void testSmearScreenBatterySipper() { + final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class); final BatterySipper sipperNull = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO, - BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */); + BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */, spc); final BatterySipper sipperBg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO, - BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */); + BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */, spc); final BatterySipper sipperFg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY, - BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */); + BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */, spc); final List sippers = new ArrayList<>(); sippers.add(sipperNull); sippers.add(sipperBg); sippers.add(sipperFg); - mBatteryStatsHelper.smearScreenBatterySipper(sippers, mScreenBatterySipper); + spc.smearScreenBatterySipper(sippers, mScreenBatterySipper); assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0); assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0); @@ -249,13 +247,13 @@ public class BatteryStatsHelperTest extends TestCase { @Test public void testGetProcessForegroundTimeMs_largerActivityTime_returnMinTime() { - doReturn(TIME_STATE_FOREGROUND_US + 500).when(mBatteryStatsHelper) + final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class); + doReturn(TIME_STATE_FOREGROUND_US + 500).when(spc) .getForegroundActivityTotalTimeUs(eq(mUid), anyLong()); doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP), anyLong(), anyInt()); - final long time = mBatteryStatsHelper.getProcessForegroundTimeMs(mUid, - BatteryStats.STATS_SINCE_CHARGED); + final long time = spc.getProcessForegroundTimeMs(mUid); assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS); } @@ -291,15 +289,14 @@ public class BatteryStatsHelperTest extends TestCase { } private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah, - int uidCode, boolean isUidNull) { + int uidCode, boolean isUidNull, ScreenPowerCalculator spc) { final BatterySipper sipper = mock(BatterySipper.class); sipper.drainType = BatterySipper.DrainType.APP; sipper.totalPowerMah = totalPowerMah; doReturn(uidCode).when(sipper).getUid(); if (!isUidNull) { final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS); - doReturn(activityTime).when(mBatteryStatsHelper).getProcessForegroundTimeMs(eq(uid), - anyInt()); + doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid)); doReturn(uidCode).when(uid).getUid(); sipper.uidObj = uid; }