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

Commit b5f213d8 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Convert CpuPowerCalculator to work with BatteryUsageStats" into sc-dev

parents ed2ba5d6 2199b00c
Loading
Loading
Loading
Loading
+129 −45
Original line number Diff line number Diff line
@@ -17,81 +17,166 @@ package com.android.internal.os;

import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;

import java.util.List;

public class CpuPowerCalculator extends PowerCalculator {
    private static final String TAG = "CpuPowerCalculator";
    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
    private final PowerProfile mProfile;
    private final int mNumCpuClusters;

    // Time-in-state based CPU power estimation model computes the estimated power
    // by adding up three components:
    //   - CPU Active power:    the constant amount of charge consumed by the CPU when it is on
    //   - Per Cluster power:   the additional amount of charge consumed by a CPU cluster
    //                          when it is running
    //   - Per frequency power: the additional amount of charge caused by dynamic frequency scaling

    private final UsageBasedPowerEstimator mCpuActivePowerEstimator;
    // One estimator per cluster
    private final UsageBasedPowerEstimator[] mPerClusterPowerEstimators;
    // Multiple estimators per cluster: one per available scaling frequency. Note that different
    // clusters have different sets of frequencies and corresponding power consumption averages.
    private final UsageBasedPowerEstimator[][] mPerCpuFreqPowerEstimators;

    private static class Result {
        public long durationMs;
        public double powerMah;
        public long durationFgMs;
        public String packageWithHighestDrain;
    }

    public CpuPowerCalculator(PowerProfile profile) {
        mProfile = profile;
        mNumCpuClusters = profile.getNumCpuClusters();

        mCpuActivePowerEstimator = new UsageBasedPowerEstimator(
                profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE));

        mPerClusterPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters];
        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
            mPerClusterPowerEstimators[cluster] = new UsageBasedPowerEstimator(
                    profile.getAveragePowerForCpuCluster(cluster));
        }

        mPerCpuFreqPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters][];
        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
            final int speedsForCluster = profile.getNumSpeedStepsInCpuCluster(cluster);
            mPerCpuFreqPowerEstimators[cluster] = new UsageBasedPowerEstimator[speedsForCluster];
            for (int speed = 0; speed < speedsForCluster; speed++) {
                mPerCpuFreqPowerEstimators[cluster][speed] =
                        new UsageBasedPowerEstimator(
                                profile.getAveragePowerForCpuCore(cluster, speed));
            }
        }
    }

    @Override
    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
        final int statsType = BatteryStats.STATS_SINCE_CHARGED;
        Result result = new Result();
        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                builder.getUidBatteryConsumerBuilders();
        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
            calculateApp(app, app.getBatteryStatsUid(), result);
        }
    }

        long cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
        final int numClusters = mProfile.getNumCpuClusters();
    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, Result result) {
        calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, result);

        double cpuPowerMaUs = 0;
        for (int cluster = 0; cluster < numClusters; cluster++) {
            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
            for (int speed = 0; speed < speedsForCluster; speed++) {
                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
                final double cpuSpeedStepPower = timeUs *
                        mProfile.getAveragePowerForCpuCore(cluster, speed);
                if (DEBUG) {
                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                            + speed + " timeUs=" + timeUs + " power="
                            + formatCharge(cpuSpeedStepPower / MICROSEC_IN_HR));
        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, result.powerMah)
                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, result.durationMs)
                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND,
                        result.durationFgMs)
                .setPackageWithHighestDrain(result.packageWithHighestDrain);
    }

    @Override
    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
        Result result = new Result();
        for (int i = sippers.size() - 1; i >= 0; i--) {
            final BatterySipper app = sippers.get(i);
            if (app.drainType == BatterySipper.DrainType.APP) {
                calculateApp(app, app.uidObj, statsType, result);
            }
                cpuPowerMaUs += cpuSpeedStepPower;
        }
    }
        cpuPowerMaUs += u.getCpuActiveTime() * 1000 * mProfile.getAveragePower(
                PowerProfile.POWER_CPU_ACTIVE);

    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) {
        calculatePowerAndDuration(u, statsType, result);

        app.cpuPowerMah = result.powerMah;
        app.cpuTimeMs = result.durationMs;
        app.cpuFgTimeMs = result.durationFgMs;
        app.packageWithHighestDrain = result.packageWithHighestDrain;
    }

    private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, Result result) {
        long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;

        // Constant battery drain when CPU is active
        double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime());

        // Additional per-cluster battery drain
        long[] cpuClusterTimes = u.getCpuClusterTimes();
        if (cpuClusterTimes != null) {
            if (cpuClusterTimes.length == numClusters) {
                for (int i = 0; i < numClusters; i++) {
                    double power =
                            cpuClusterTimes[i] * 1000 * mProfile.getAveragePowerForCpuCluster(i);
                    cpuPowerMaUs += power;
            if (cpuClusterTimes.length == mNumCpuClusters) {
                for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
                    double power = mPerClusterPowerEstimators[cluster]
                            .calculatePower(cpuClusterTimes[cluster]);
                    powerMah += power;
                    if (DEBUG) {
                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
                                + cpuClusterTimes[i] + " power="
                                + formatCharge(power / MICROSEC_IN_HR));
                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
                                + " clusterTimeMs=" + cpuClusterTimes[cluster]
                                + " power=" + formatCharge(power));
                    }
                }
            } else {
                Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
                        + numClusters + " actual # " + cpuClusterTimes.length);
                        + mNumCpuClusters + " actual # " + cpuClusterTimes.length);
            }
        }
        final double cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;

        if (DEBUG && (cpuTimeMs != 0 || cpuPowerMah != 0)) {
            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + cpuTimeMs + " ms power="
                    + formatCharge(cpuPowerMah));
        // Additional per-frequency battery drain
        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
            final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
            for (int speed = 0; speed < speedsForCluster; speed++) {
                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
                final double power =
                        mPerCpuFreqPowerEstimators[cluster][speed].calculatePower(timeUs / 1000);
                if (DEBUG) {
                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                            + speed + " timeUs=" + timeUs + " power="
                            + formatCharge(power));
                }
                powerMah += power;
            }
        }

        if (DEBUG && (durationMs != 0 || powerMah != 0)) {
            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + durationMs + " ms power="
                    + formatCharge(powerMah));
        }

        // Keep track of the package with highest drain.
        double highestDrain = 0;
        String packageWithHighestDrain = null;
        long cpuFgTimeMs = 0;
        long durationFgMs = 0;
        final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
        final int processStatsCount = processStats.size();
        for (int i = 0; i < processStatsCount; i++) {
            final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
            final String processName = processStats.keyAt(i);
            cpuFgTimeMs += ps.getForegroundTime(statsType);
            durationFgMs += ps.getForegroundTime(statsType);

            final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
                    + ps.getForegroundTime(statsType);
@@ -107,20 +192,19 @@ public class CpuPowerCalculator extends PowerCalculator {
            }
        }


        // Ensure that the CPU times make sense.
        if (cpuFgTimeMs > cpuTimeMs) {
            if (DEBUG && cpuFgTimeMs > cpuTimeMs + 10000) {
        if (durationFgMs > durationMs) {
            if (DEBUG && durationFgMs > durationMs + 10000) {
                Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
            }

            // Statistics may not have been gathered yet.
            cpuTimeMs = cpuFgTimeMs;
            durationMs = durationFgMs;
        }

        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, cpuPowerMah)
                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, cpuTimeMs)
                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, cpuFgTimeMs)
                .setPackageWithHighestDrain(packageWithHighestDrain);
        result.durationMs = durationMs;
        result.durationFgMs = durationFgMs;
        result.powerMah = powerMah;
        result.packageWithHighestDrain = packageWithHighestDrain;
    }
}
+0 −13
Original line number Diff line number Diff line
@@ -92,19 +92,6 @@ public abstract class PowerCalculator {
     */
    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                                      long rawUptimeUs, int statsType) {

        // TODO(b/175156498): Temporary code during the transition from BatterySippers to
        //  BatteryConsumers.
        UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, u);
        calculateApp(builder, u, rawRealtimeUs, rawUptimeUs, BatteryUsageStatsQuery.DEFAULT);
        final UidBatteryConsumer uidBatteryConsumer = builder.build();
        app.cpuPowerMah = uidBatteryConsumer.getConsumedPower(
                UidBatteryConsumer.POWER_COMPONENT_CPU);
        app.cpuTimeMs = uidBatteryConsumer.getUsageDurationMillis(
                UidBatteryConsumer.TIME_COMPONENT_CPU);
        app.cpuFgTimeMs = uidBatteryConsumer.getUsageDurationMillis(
                UidBatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND);
        app.packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
    }

    /**
+1 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import org.junit.runners.Suite;
        BluetoothPowerCalculatorTest.class,
        BstatsCpuTimesValidationTest.class,
        CameraPowerCalculatorTest.class,
        CpuPowerCalculatorTest.class,
        FlashlightPowerCalculatorTest.class,
        GnssPowerCalculatorTest.class,
        IdlePowerCalculatorTest.class,
+20 −0
Original line number Diff line number Diff line
@@ -77,6 +77,26 @@ public class BatteryUsageStatsRule implements TestRule {
        return this;
    }

    public BatteryUsageStatsRule setNumCpuClusters(int number) {
        when(mPowerProfile.getNumCpuClusters()).thenReturn(number);
        return this;
    }

    public BatteryUsageStatsRule setNumSpeedStepsInCpuCluster(int cluster, int speeds) {
        when(mPowerProfile.getNumSpeedStepsInCpuCluster(cluster)).thenReturn(speeds);
        return this;
    }

    public BatteryUsageStatsRule setAveragePowerForCpuCluster(int cluster, double value) {
        when(mPowerProfile.getAveragePowerForCpuCluster(cluster)).thenReturn(value);
        return this;
    }

    public BatteryUsageStatsRule setAveragePowerForCpuCore(int cluster, int step, double value) {
        when(mPowerProfile.getAveragePowerForCpuCore(cluster, step)).thenReturn(value);
        return this;
    }

    public void setNetworkStats(NetworkStats networkStats) {
        mBatteryStats.setNetworkStats(networkStats);
    }
+152 −0
Original line number Diff line number Diff line
/*
 * 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 com.android.internal.os;

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

import static org.mockito.ArgumentMatchers.any;
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.Process;
import android.os.UidBatteryConsumer;

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

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;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class CpuPowerCalculatorTest {
    private static final double PRECISION = 0.00001;

    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;

    @Rule
    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
            .setNumCpuClusters(2)
            .setNumSpeedStepsInCpuCluster(0, 2)
            .setNumSpeedStepsInCpuCluster(1, 2)
            .setAveragePowerForCpuCluster(0, 360)
            .setAveragePowerForCpuCluster(1, 480)
            .setAveragePowerForCpuCore(0, 0, 300)
            .setAveragePowerForCpuCore(0, 1, 400)
            .setAveragePowerForCpuCore(1, 0, 500)
            .setAveragePowerForCpuCore(1, 1, 600);

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

    @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;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mStatsRule.getBatteryStats()
                .setUserInfoProvider(mMockUserInfoProvider)
                .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
                .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
                .setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader)
                .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
                .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
    }

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

        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
        doAnswer(invocation -> {
            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
            // User/system time in microseconds
            callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000});
            callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000});
            return null;
        }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(any());

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

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

        mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true);

        mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234);
        mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);

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

        mStatsRule.apply(calculator);

        UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
        assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
                .isEqualTo(3333);
        assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                .isWithin(PRECISION).of(1.092233);
        assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");

        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
        assertThat(uidConsumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
                .isEqualTo(7777);
        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                .isWithin(PRECISION).of(2.672322);
        assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
    }
}