diff options
| author | 2021-02-02 19:07:09 -0800 | |
|---|---|---|
| committer | 2021-02-09 17:55:51 -0800 | |
| commit | 2199b00cc6ac55521fce42b3e4167678e1e09693 (patch) | |
| tree | 46e87aff5aa9af46dd1aac29a135b50f528a94fc | |
| parent | 4db015d00ed1b2f2f96f9a2bea4449c0680c37dd (diff) | |
Convert CpuPowerCalculator to work with BatteryUsageStats
Bug: 158137862
Bug: 175156498
Test: atest CpuPowerCalculatorTest
Change-Id: Ifa7402dc90a572219d1f6eabef89c57dcf6aa2f6
5 files changed, 302 insertions, 58 deletions
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java index 11c87618762e..45d81280af4a 100644 --- a/core/java/com/android/internal/os/CpuPowerCalculator.java +++ b/core/java/com/android/internal/os/CpuPowerCalculator.java @@ -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)); - } - cpuPowerMaUs += cpuSpeedStepPower; + 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 += 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; } } diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java index 13c59dcdbc4e..141363fa8b25 100644 --- a/core/java/com/android/internal/os/PowerCalculator.java +++ b/core/java/com/android/internal/os/PowerCalculator.java @@ -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(); } /** diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 4b9286d4de26..0fac4f79686a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -45,6 +45,7 @@ import org.junit.runners.Suite; BluetoothPowerCalculatorTest.class, BstatsCpuTimesValidationTest.class, CameraPowerCalculatorTest.class, + CpuPowerCalculatorTest.class, FlashlightPowerCalculatorTest.class, GnssPowerCalculatorTest.class, IdlePowerCalculatorTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java index 0c31c5b9818b..0ddc4c0035ce 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java @@ -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); } diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java new file mode 100644 index 000000000000..9cf0d375ff51 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java @@ -0,0 +1,152 @@ +/* + * 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(); + } +} |