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

Commit 2ad7cd41 authored by Adam Bookatz's avatar Adam Bookatz
Browse files

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 3754504f)
parent 11d9d6cf
Loading
Loading
Loading
Loading
+2 −65
Original line number Diff line number Diff line
@@ -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<BatterySipper> 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<BatterySipper> 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;
+64 −0
Original line number Diff line number Diff line
@@ -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<BatterySipper> 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);
    }
}
+10 −13
Original line number Diff line number Diff line
@@ -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<BatterySipper> 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;
        }