1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.power.stats; 18 19 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND; 20 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED; 21 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND; 22 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE; 23 24 import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; 25 import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; 26 import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; 27 import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; 28 import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; 29 import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; 30 31 import static com.google.common.truth.Truth.assertThat; 32 33 import android.os.BatteryConsumer; 34 import android.os.BatteryStats; 35 import android.os.PersistableBundle; 36 import android.os.Process; 37 import android.platform.test.ravenwood.RavenwoodRule; 38 39 import androidx.annotation.NonNull; 40 41 import com.android.internal.os.MonotonicClock; 42 import com.android.internal.os.PowerStats; 43 44 import org.junit.Rule; 45 import org.junit.Test; 46 47 public class BinaryStatePowerStatsProcessorTest { 48 @Rule(order = 0) 49 public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() 50 .setProvideMainThread(true) 51 .build(); 52 53 private static final double PRECISION = 0.00001; 54 private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42; 55 private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; 56 private static final int POWER_COMPONENT = BatteryConsumer.POWER_COMPONENT_AUDIO; 57 private static final int TEST_STATE_FLAG = 0x1; 58 59 private final MockClock mClock = new MockClock(); 60 private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); 61 private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver(); 62 63 private static class TestBinaryStatePowerStatsProcessor extends BinaryStatePowerStatsProcessor { TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp, PowerStatsUidResolver uidResolver)64 TestBinaryStatePowerStatsProcessor(int powerComponentId, 65 double averagePowerMilliAmp, PowerStatsUidResolver uidResolver) { 66 super(powerComponentId, uidResolver, averagePowerMilliAmp); 67 } 68 69 @Override getBinaryState(BatteryStats.HistoryItem item)70 protected int getBinaryState(BatteryStats.HistoryItem item) { 71 return (item.states & TEST_STATE_FLAG) != 0 ? STATE_ON : STATE_OFF; 72 } 73 } 74 75 @Test powerProfileModel()76 public void powerProfileModel() { 77 TestBinaryStatePowerStatsProcessor processor = new TestBinaryStatePowerStatsProcessor( 78 POWER_COMPONENT, /* averagePowerMilliAmp */ 100, mUidResolver); 79 80 BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(); 81 82 PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor); 83 84 processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1)); 85 86 // Turn the screen off after 2.5 seconds 87 stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); 88 stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); 89 stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000); 90 91 processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1)); 92 93 processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2)); 94 95 processor.finish(stats, 11000); 96 97 // Total usage duration is 10000 98 // Total estimated power = 10000 * 100 = 1000000 mA-ms = 0.277777 mAh 99 // Screen-on - 25% 100 // Screen-off - 75% 101 double expectedPower = 0.277778; 102 long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength]; 103 stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); 104 assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) 105 .isWithin(PRECISION).of(expectedPower * 0.25); 106 107 stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); 108 assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) 109 .isWithin(PRECISION).of(expectedPower * 0.75); 110 111 // UID1 = 112 // 6000 * 100 = 600000 mA-ms = 0.166666 mAh 113 // split between three different states 114 double expectedPower1 = 0.166666; 115 long[] uidStats = new long[stats.getPowerStatsDescriptor().uidStatsArrayLength]; 116 stats.getUidStats(uidStats, APP_UID1, 117 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); 118 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 119 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000); 120 121 stats.getUidStats(uidStats, APP_UID1, 122 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); 123 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 124 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000); 125 126 stats.getUidStats(uidStats, APP_UID1, 127 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); 128 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 129 .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000); 130 131 // UID2 = 132 // 4000 * 100 = 400000 mA-ms = 0.111111 mAh 133 // all in the same state 134 double expectedPower2 = 0.111111; 135 stats.getUidStats(uidStats, APP_UID2, 136 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); 137 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 138 .isWithin(PRECISION).of(expectedPower2); 139 140 stats.getUidStats(uidStats, APP_UID2, 141 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); 142 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 143 .isWithin(PRECISION).of(0); 144 } 145 146 @Test energyConsumerModel()147 public void energyConsumerModel() { 148 TestBinaryStatePowerStatsProcessor processor = new TestBinaryStatePowerStatsProcessor( 149 POWER_COMPONENT, /* averagePowerMilliAmp */ 100, mUidResolver); 150 151 BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(); 152 PersistableBundle extras = new PersistableBundle(); 153 statsLayout.toExtras(extras); 154 PowerStats.Descriptor descriptor = new PowerStats.Descriptor(POWER_COMPONENT, 155 statsLayout.getDeviceStatsArrayLength(), null, 0, 156 statsLayout.getUidStatsArrayLength(), extras); 157 PowerStats powerStats = new PowerStats(descriptor); 158 powerStats.stats = new long[descriptor.statsArrayLength]; 159 160 PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor); 161 162 // Establish a baseline 163 processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime()); 164 165 processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1)); 166 167 // Turn the screen off after 2.5 seconds 168 stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); 169 stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); 170 stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000); 171 172 processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1)); 173 174 statsLayout.setConsumedEnergy(powerStats.stats, 0, 2_160_000); 175 processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime()); 176 177 processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2)); 178 179 mClock.realtime = 11000; 180 statsLayout.setConsumedEnergy(powerStats.stats, 0, 1_440_000); 181 processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime()); 182 183 processor.finish(stats, 11000); 184 185 // Total estimated power = 3,600,000 uC = 1.0 mAh 186 // of which 3,000,000 is distributed: 187 // Screen-on - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh 188 // Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh 189 // and 600,000 was fully with screen off: 190 // Screen-off - 1440000 uC = 0.4 mAh 191 long[] deviceStats = new long[descriptor.statsArrayLength]; 192 stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); 193 assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) 194 .isWithin(PRECISION).of(0.25); 195 196 stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); 197 assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) 198 .isWithin(PRECISION).of(0.35 + 0.4); 199 200 // UID1 = 201 // 2,160,000 uC = 0.6 mAh 202 // split between three different states 203 // fg screen-on: 2500/6000 204 // bg screen-off: 2500/6000 205 // fgs screen-off: 1000/6000 206 double expectedPower1 = 0.6; 207 long[] uidStats = new long[descriptor.uidStatsArrayLength]; 208 stats.getUidStats(uidStats, APP_UID1, 209 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); 210 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 211 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000); 212 213 stats.getUidStats(uidStats, APP_UID1, 214 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); 215 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 216 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000); 217 218 stats.getUidStats(uidStats, APP_UID1, 219 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); 220 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 221 .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000); 222 223 // UID2 = 224 // 1440000 mA-ms = 0.4 mAh 225 // all in the same state 226 double expectedPower2 = 0.4; 227 stats.getUidStats(uidStats, APP_UID2, 228 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); 229 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 230 .isWithin(PRECISION).of(expectedPower2); 231 232 stats.getUidStats(uidStats, APP_UID2, 233 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); 234 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 235 .isWithin(PRECISION).of(0); 236 } 237 238 239 @NonNull buildHistoryItem(int elapsedRealtime, boolean stateOn, int uid)240 private BatteryStats.HistoryItem buildHistoryItem(int elapsedRealtime, boolean stateOn, 241 int uid) { 242 mClock.realtime = elapsedRealtime; 243 BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem(); 244 historyItem.time = mMonotonicClock.monotonicTime(); 245 historyItem.states = stateOn ? TEST_STATE_FLAG : 0; 246 if (stateOn) { 247 historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE 248 | BatteryStats.HistoryItem.EVENT_FLAG_START; 249 } else { 250 historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE 251 | BatteryStats.HistoryItem.EVENT_FLAG_FINISH; 252 } 253 historyItem.eventTag = historyItem.localEventTag; 254 historyItem.eventTag.uid = uid; 255 historyItem.eventTag.string = "test"; 256 return historyItem; 257 } 258 states(int... states)259 private int[] states(int... states) { 260 return states; 261 } 262 createAggregatedPowerStats( BinaryStatePowerStatsProcessor processor)263 private static PowerComponentAggregatedPowerStats createAggregatedPowerStats( 264 BinaryStatePowerStatsProcessor processor) { 265 AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); 266 config.trackPowerComponent(POWER_COMPONENT) 267 .trackDeviceStates(STATE_POWER, STATE_SCREEN) 268 .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE) 269 .setProcessor(processor); 270 271 AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config); 272 PowerComponentAggregatedPowerStats powerComponentStats = 273 aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT); 274 processor.start(powerComponentStats, 0); 275 276 powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0); 277 powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0); 278 powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); 279 powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); 280 281 return powerComponentStats; 282 } 283 } 284