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

Commit 1643f794 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Fix system service power calculation when measured energy is available

Bug: 188930285
Test: atest FrameworksCoreTests:SystemServicePowerCalculatorTest
Change-Id: Ic48bd627b8458cde18f812eb63cbc1e37496326d
parent 87d4c9a9
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -201,7 +201,10 @@ public class CpuPowerCalculator extends PowerCalculator {
        result.packageWithHighestDrain = packageWithHighestDrain;
    }

    private double calculateUidModeledPowerMah(BatteryStats.Uid u, int statsType) {
    /**
     * Calculates CPU power consumed by the specified app, using the PowerProfile model.
     */
    public double calculateUidModeledPowerMah(BatteryStats.Uid u, int statsType) {
        // Constant battery drain when CPU is active
        double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime());

+51 −7
Original line number Diff line number Diff line
@@ -40,8 +40,10 @@ public class SystemServicePowerCalculator extends PowerCalculator {
    // to this layout:
    // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...}
    private final UsageBasedPowerEstimator[] mPowerEstimators;
    private final CpuPowerCalculator mCpuPowerCalculator;

    public SystemServicePowerCalculator(PowerProfile powerProfile) {
        mCpuPowerCalculator = new CpuPowerCalculator(powerProfile);
        int numFreqs = 0;
        final int numCpuClusters = powerProfile.getNumCpuClusters();
        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
@@ -62,7 +64,22 @@ public class SystemServicePowerCalculator extends PowerCalculator {
    @Override
    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
        double systemServicePowerMah = calculateSystemServicePower(batteryStats);
        final BatteryStats.Uid systemUid = batteryStats.getUidStats().get(Process.SYSTEM_UID);
        if (systemUid == null) {
            return;
        }

        final long consumptionUC = systemUid.getCpuMeasuredBatteryConsumptionUC();
        final int powerModel = getPowerModel(consumptionUC, query);

        double systemServicePowerMah;
        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
            systemServicePowerMah = calculatePowerUsingMeasuredConsumption(batteryStats,
                    systemUid, consumptionUC);
        } else {
            systemServicePowerMah = calculatePowerUsingPowerProfile(batteryStats);
        }

        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                builder.getUidBatteryConsumerBuilders();
        final UidBatteryConsumer.Builder systemServerConsumer = uidBatteryConsumerBuilders.get(
@@ -76,7 +93,7 @@ public class SystemServicePowerCalculator extends PowerCalculator {
            // distributed to applications
            systemServerConsumer.setConsumedPower(
                    BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
                    -systemServicePowerMah);
                    -systemServicePowerMah, powerModel);
        }

        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
@@ -84,7 +101,8 @@ public class SystemServicePowerCalculator extends PowerCalculator {
            if (app != systemServerConsumer) {
                final BatteryStats.Uid uid = app.getBatteryStatsUid();
                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
                        systemServicePowerMah * uid.getProportionalSystemServiceUsage());
                        systemServicePowerMah * uid.getProportionalSystemServiceUsage(),
                        powerModel);
            }
        }

@@ -102,7 +120,20 @@ public class SystemServicePowerCalculator extends PowerCalculator {
    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
            long rawRealtimeUs, long rawUptimeUs, int statsType,
            SparseArray<UserHandle> asUsers) {
        double systemServicePowerMah = calculateSystemServicePower(batteryStats);
        final BatteryStats.Uid systemUid = batteryStats.getUidStats().get(Process.SYSTEM_UID);
        if (systemUid == null) {
            return;
        }

        final long consumptionUC = systemUid.getCpuMeasuredBatteryConsumptionUC();
        double systemServicePowerMah;
        if (getPowerModel(consumptionUC) == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
            systemServicePowerMah = calculatePowerUsingMeasuredConsumption(batteryStats,
                    systemUid, consumptionUC);
        } else {
            systemServicePowerMah = calculatePowerUsingPowerProfile(batteryStats);
        }

        BatterySipper systemServerSipper = null;
        for (int i = sippers.size() - 1; i >= 0; i--) {
            final BatterySipper app = sippers.get(i);
@@ -134,7 +165,21 @@ public class SystemServicePowerCalculator extends PowerCalculator {
        }
    }

    private double calculateSystemServicePower(BatteryStats batteryStats) {
    private double calculatePowerUsingMeasuredConsumption(BatteryStats batteryStats,
            BatteryStats.Uid systemUid, long consumptionUC) {
        // Use the PowerProfile based model to estimate the ratio between the power consumed
        // while handling incoming binder calls and the entire System UID power consumption.
        // Apply that ratio to the _measured_ system UID power consumption to get a more
        // accurate estimate of the power consumed by incoming binder calls.
        final double systemServiceModeledPowerMah = calculatePowerUsingPowerProfile(batteryStats);
        final double systemUidModeledPowerMah = mCpuPowerCalculator.calculateUidModeledPowerMah(
                systemUid, BatteryStats.STATS_SINCE_CHARGED);

        return uCtoMah(consumptionUC) * systemServiceModeledPowerMah
                / systemUidModeledPowerMah;
    }

    private double calculatePowerUsingPowerProfile(BatteryStats batteryStats) {
        final long[] systemServiceTimeAtCpuSpeeds = batteryStats.getSystemServiceTimeAtCpuSpeeds();
        if (systemServiceTimeAtCpuSpeeds == null) {
            return 0;
@@ -145,13 +190,12 @@ public class SystemServicePowerCalculator extends PowerCalculator {
        double powerMah = 0;
        final int size = Math.min(mPowerEstimators.length, systemServiceTimeAtCpuSpeeds.length);
        for (int i = 0; i < size; i++) {
            powerMah += mPowerEstimators[i].calculatePower(systemServiceTimeAtCpuSpeeds[i]);
            powerMah += mPowerEstimators[i].calculatePower(systemServiceTimeAtCpuSpeeds[i] / 1000);
        }

        if (DEBUG) {
            Log.d(TAG, "System service power:" + powerMah);
        }

        return powerMah;
    }
}
+131 −100
Original line number Diff line number Diff line
@@ -18,25 +18,28 @@ package com.android.internal.os;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Binder;
import android.os.Process;
import android.os.UidBatteryConsumer;

import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.power.MeasuredEnergyStats;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.IOException;
import java.util.ArrayList;
@@ -47,6 +50,8 @@ import java.util.Collection;
public class SystemServicePowerCalculatorTest {

    private static final double PRECISION = 0.000001;
    private static final int APP_UID1 = 100;
    private static final int APP_UID2 = 200;

    @Rule
    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
@@ -61,73 +66,52 @@ public class SystemServicePowerCalculatorTest {
            .setAveragePowerForCpuCore(1, 0, 500)
            .setAveragePowerForCpuCore(1, 1, 600);

    @Mock
    private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
    @Mock
    private KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader mMockKernelCpuUidClusterTimeReader;
    @Mock
    private KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
    @Mock
    private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mMockKernelCpuUidUserSysTimeReader;
    @Mock
    private KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader mMockKerneCpuUidActiveTimeReader;
    @Mock
    private SystemServerCpuThreadReader mMockSystemServerCpuThreadReader;

    private final KernelCpuSpeedReader[] mMockKernelCpuSpeedReaders = new KernelCpuSpeedReader[]{
            mock(KernelCpuSpeedReader.class),
            mock(KernelCpuSpeedReader.class),
    };

    private MockBatteryStatsImpl mMockBatteryStats;
    private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
    private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader;

    @Before
    public void setUp() throws IOException {
        mMockUserInfoProvider = mock(BatteryStatsImpl.UserInfoProvider.class);
        mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader();
        mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
        MockitoAnnotations.initMocks(this);
        mMockBatteryStats = mStatsRule.getBatteryStats()
                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
                .setUserInfoProvider(mMockUserInfoProvider)
                .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
                .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
                .setUserInfoProvider(mMockUserInfoProvider);
                .setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader)
                .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
                .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
    }

    @Test
    public void testPowerProfileBasedModel() {
        when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);

        // Test Power Profile has two CPU clusters with 2 speeds each, thus 4 freq times total
        mMockSystemServerCpuThreadReader.setCpuTimes(
                new long[] {30000, 40000, 50000, 60000},
                new long[] {20000, 30000, 40000, 50000});

        mMockCpuUidFreqTimeReader.setSystemServerCpuTimes(
                new long[] {10000, 20000, 30000, 40000}
        );

        mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false, null);

        int workSourceUid1 = 100;
        int workSourceUid2 = 200;
        int transactionCode = 42;

        Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>();
        BinderCallsStats.CallStat stat1 = new BinderCallsStats.CallStat(workSourceUid1,
                Binder.class, transactionCode, true /*screenInteractive */);
        stat1.incrementalCallCount = 100;
        stat1.recordedCallCount = 100;
        stat1.cpuTimeMicros = 1000000;
        callStats.add(stat1);

        mMockBatteryStats.noteBinderCallStats(workSourceUid1, 100, callStats);

        callStats.clear();
        BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(workSourceUid2,
                Binder.class, transactionCode, true /*screenInteractive */);
        stat2.incrementalCallCount = 100;
        stat2.recordedCallCount = 100;
        stat2.cpuTimeMicros = 9000000;
        callStats.add(stat2);

        mMockBatteryStats.noteBinderCallStats(workSourceUid2, 100, callStats);

        mMockBatteryStats.updateSystemServiceCallStats();
        mMockBatteryStats.updateSystemServerThreadStats();
        prepareBatteryStats(null);

        SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
                mStatsRule.getPowerProfile());

        mStatsRule.apply(new FakeCpuPowerCalculator(), calculator);
        mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getPowerProfile()), calculator);

        assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid1)
        assertThat(mStatsRule.getUidBatteryConsumer(APP_UID1)
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
                .isWithin(PRECISION).of(1.888888);
        assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid2)
        assertThat(mStatsRule.getUidBatteryConsumer(APP_UID2)
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
                .isWithin(PRECISION).of(17.0);
        assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
@@ -141,58 +125,105 @@ public class SystemServicePowerCalculatorTest {
                .isWithin(PRECISION).of(18.888888);
    }

    private static class MockKernelCpuUidFreqTimeReader extends
            KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader {
        private long[] mSystemServerCpuTimes;
    @Test
    public void testMeasuredEnergyBasedModel() {
        final boolean[] supportedPowerBuckets =
                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
        supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
        mStatsRule.getBatteryStats()
                .initMeasuredEnergyStatsLocked(supportedPowerBuckets, new String[0]);

        MockKernelCpuUidFreqTimeReader() {
            super(/*throttle */false);
        }
        prepareBatteryStats(new long[]{50000000, 100000000});

        void setSystemServerCpuTimes(long[] systemServerCpuTimes) {
            mSystemServerCpuTimes = systemServerCpuTimes;
        }
        SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
                mStatsRule.getPowerProfile());

        @Override
        public boolean perClusterTimesAvailable() {
            return true;
        }
        mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getPowerProfile()), calculator);

        @Override
        public void readDelta(boolean forcedRead, @Nullable Callback<long[]> cb) {
            if (cb != null) {
                cb.onUidCpuTime(Process.SYSTEM_UID, mSystemServerCpuTimes);
            }
        }
        assertThat(mStatsRule.getUidBatteryConsumer(APP_UID1)
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
                .isWithin(PRECISION).of(1.979351);
        assertThat(mStatsRule.getUidBatteryConsumer(APP_UID2)
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
                .isWithin(PRECISION).of(17.814165);
        assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS))
                .isWithin(PRECISION).of(-19.793517);
        assertThat(mStatsRule.getDeviceBatteryConsumer()
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
                .isWithin(PRECISION).of(19.793517);
        assertThat(mStatsRule.getAppsBatteryConsumer()
                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
                .isWithin(PRECISION).of(19.793517);
    }

    private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader {
        private final SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
    private void prepareBatteryStats(long[] clusterChargesUc) {
        when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);

        MockSystemServerCpuThreadReader() {
            super(null);
        }
        when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
        when(mMockKernelCpuSpeedReaders[1].readDelta()).thenReturn(new long[]{3000, 4000});

        when(mMockCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(false);

        // User/System CPU time in microseconds
        doAnswer(invocation -> {
            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1);
            callback.onUidCpuTime(APP_UID1, new long[]{1_000_000, 2_000_000});
            callback.onUidCpuTime(APP_UID2, new long[]{3_000_000, 4_000_000});
            callback.onUidCpuTime(Process.SYSTEM_UID, new long[]{60_000_000, 80_000_000});
            return null;
        }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(anyBoolean(), any());

        // Active CPU time in milliseconds
        doAnswer(invocation -> {
            final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(1);
            callback.onUidCpuTime(APP_UID1, 1111L);
            callback.onUidCpuTime(APP_UID2, 3333L);
            callback.onUidCpuTime(Process.SYSTEM_UID, 10000L);
            return null;
        }).when(mMockKerneCpuUidActiveTimeReader).readDelta(anyBoolean(), any());

        // Per-cluster CPU time in milliseconds
        doAnswer(invocation -> {
            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1);
            callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222});
            callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444});
            callback.onUidCpuTime(Process.SYSTEM_UID, new long[]{50_000, 80_000});
            return null;
        }).when(mMockKernelCpuUidClusterTimeReader).readDelta(anyBoolean(), any());

        // System service CPU time
        final SystemServerCpuThreadReader.SystemServiceCpuThreadTimes threadTimes =
                new SystemServerCpuThreadReader.SystemServiceCpuThreadTimes();
        threadTimes.binderThreadCpuTimesUs =
                new long[]{20_000_000, 30_000_000, 40_000_000, 50_000_000};

        when(mMockSystemServerCpuThreadReader.readDelta()).thenReturn(threadTimes);

        public void setCpuTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) {
            mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
            mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
        }
        int transactionCode = 42;

        @Override
        public SystemServiceCpuThreadTimes readDelta() {
            return mThreadTimes;
        }
    }
        Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>();
        BinderCallsStats.CallStat stat1 = new BinderCallsStats.CallStat(APP_UID1,
                Binder.class, transactionCode, true /*screenInteractive */);
        stat1.incrementalCallCount = 100;
        stat1.recordedCallCount = 100;
        stat1.cpuTimeMicros = 1_000_000;
        callStats.add(stat1);

    private static class FakeCpuPowerCalculator extends PowerCalculator {
        @Override
        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
            if (u.getUid() == Process.SYSTEM_UID) {
                // SystemServer must be attributed at least as much power as the total
                // of all system services requested by apps.
                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 1000000);
            }
        }
        mMockBatteryStats.noteBinderCallStats(APP_UID1, 100, callStats);

        callStats.clear();
        BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(APP_UID2,
                Binder.class, transactionCode, true /*screenInteractive */);
        stat2.incrementalCallCount = 100;
        stat2.recordedCallCount = 100;
        stat2.cpuTimeMicros = 9_000_000;
        callStats.add(stat2);

        mMockBatteryStats.noteBinderCallStats(APP_UID2, 100, callStats);

        mMockBatteryStats.updateCpuTimeLocked(true, true, clusterChargesUc);

        mMockBatteryStats.prepareForDumpLocked();
    }
}