Loading core/java/com/android/internal/os/CpuPowerCalculator.java +129 −45 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; } } core/java/com/android/internal/os/PowerCalculator.java +0 −13 Original line number Diff line number Diff line Loading @@ -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(); } /** Loading core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +1 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import org.junit.runners.Suite; BluetoothPowerCalculatorTest.class, BstatsCpuTimesValidationTest.class, CameraPowerCalculatorTest.class, CpuPowerCalculatorTest.class, FlashlightPowerCalculatorTest.class, GnssPowerCalculatorTest.class, IdlePowerCalculatorTest.class, Loading core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +20 −0 Original line number Diff line number Diff line Loading @@ -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); } Loading core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java 0 → 100644 +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(); } } Loading
core/java/com/android/internal/os/CpuPowerCalculator.java +129 −45 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; } }
core/java/com/android/internal/os/PowerCalculator.java +0 −13 Original line number Diff line number Diff line Loading @@ -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(); } /** Loading
core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +1 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import org.junit.runners.Suite; BluetoothPowerCalculatorTest.class, BstatsCpuTimesValidationTest.class, CameraPowerCalculatorTest.class, CpuPowerCalculatorTest.class, FlashlightPowerCalculatorTest.class, GnssPowerCalculatorTest.class, IdlePowerCalculatorTest.class, Loading
core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +20 −0 Original line number Diff line number Diff line Loading @@ -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); } Loading
core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java 0 → 100644 +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(); } }