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.processor; 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.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER; 25 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON; 26 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; 27 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER; 28 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; 29 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN; 30 31 import static com.google.common.truth.Truth.assertThat; 32 33 import android.annotation.SuppressLint; 34 import android.os.BatteryConsumer; 35 import android.os.BatteryStats; 36 import android.os.PersistableBundle; 37 import android.os.Process; 38 39 import androidx.annotation.NonNull; 40 41 import com.android.internal.os.MonotonicClock; 42 import com.android.internal.os.PowerStats; 43 import com.android.server.power.stats.MockClock; 44 import com.android.server.power.stats.format.BinaryStatePowerStatsLayout; 45 46 import org.junit.Test; 47 48 import java.util.function.Supplier; 49 50 public class BinaryStatePowerStatsProcessorTest { 51 private static final double PRECISION = 0.00001; 52 private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42; 53 private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; 54 private static final int POWER_COMPONENT = BatteryConsumer.POWER_COMPONENT_AUDIO; 55 private static final int TEST_STATE_FLAG = 0x1; 56 57 private final MockClock mClock = new MockClock(); 58 private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); 59 60 private static class TestBinaryStatePowerStatsProcessor extends BinaryStatePowerStatsProcessor { TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp)61 TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp) { 62 super(powerComponentId, averagePowerMilliAmp); 63 } 64 65 @Override getBinaryState(BatteryStats.HistoryItem item)66 protected int getBinaryState(BatteryStats.HistoryItem item) { 67 return (item.states & TEST_STATE_FLAG) != 0 ? STATE_ON : STATE_OFF; 68 } 69 } 70 71 @SuppressLint("CheckResult") 72 @Test powerProfileModel()73 public void powerProfileModel() { 74 BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(); 75 76 PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats( 77 () -> new TestBinaryStatePowerStatsProcessor( 78 POWER_COMPONENT, /* averagePowerMilliAmp */ 100)); 79 80 stats.noteStateChange(buildHistoryItem(0, true, APP_UID1)); 81 82 // Turn the screen off after 2.5 seconds 83 stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); 84 stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); 85 stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000); 86 87 stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1)); 88 89 stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2)); 90 91 stats.finish(11000); 92 93 // Total usage duration is 10000 94 // Total estimated power = 10000 * 100 = 1000000 mA-ms = 0.277777 mAh 95 // Screen-on - 25% 96 // Screen-off - 75% 97 double expectedPower = 0.277778; 98 long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength]; 99 stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); 100 assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) 101 .isWithin(PRECISION).of(expectedPower * 0.25); 102 103 stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); 104 assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) 105 .isWithin(PRECISION).of(expectedPower * 0.75); 106 107 // UID1 = 108 // 6000 * 100 = 600000 mA-ms = 0.166666 mAh 109 // split between three different states 110 double expectedPower1 = 0.166666; 111 long[] uidStats = new long[stats.getPowerStatsDescriptor().uidStatsArrayLength]; 112 stats.getUidStats(uidStats, APP_UID1, 113 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); 114 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 115 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000); 116 117 stats.getUidStats(uidStats, APP_UID1, 118 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); 119 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 120 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000); 121 122 stats.getUidStats(uidStats, APP_UID1, 123 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); 124 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 125 .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000); 126 127 // UID2 = 128 // 4000 * 100 = 400000 mA-ms = 0.111111 mAh 129 // all in the same state 130 double expectedPower2 = 0.111111; 131 stats.getUidStats(uidStats, APP_UID2, 132 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); 133 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 134 .isWithin(PRECISION).of(expectedPower2); 135 136 if (stats.getUidStats(uidStats, APP_UID2, 137 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) { 138 assertThat(statsLayout.getUidPowerEstimate(uidStats)) 139 .isWithin(PRECISION).of(0); 140 } 141 } 142 143 @SuppressLint("CheckResult") 144 @Test energyConsumerModel()145 public void energyConsumerModel() { 146 BinaryStatePowerStatsLayout 147 statsLayout = new BinaryStatePowerStatsLayout(); 148 PersistableBundle extras = new PersistableBundle(); 149 statsLayout.toExtras(extras); 150 PowerStats.Descriptor descriptor = new PowerStats.Descriptor(POWER_COMPONENT, 151 statsLayout.getDeviceStatsArrayLength(), null, 0, 152 statsLayout.getUidStatsArrayLength(), extras); 153 PowerStats powerStats = new PowerStats(descriptor); 154 powerStats.stats = new long[descriptor.statsArrayLength]; 155 156 PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats( 157 () -> new TestBinaryStatePowerStatsProcessor( 158 POWER_COMPONENT, /* averagePowerMilliAmp */ 100)); 159 160 stats.start(0); 161 162 // Establish a baseline 163 stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime()); 164 165 stats.noteStateChange(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 stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1)); 173 174 statsLayout.setConsumedEnergy(powerStats.stats, 0, 2_160_000); 175 stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime()); 176 177 stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2)); 178 179 mClock.realtime = 11000; 180 statsLayout.setConsumedEnergy(powerStats.stats, 0, 1_440_000); 181 stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime()); 182 183 stats.finish(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 if (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 240 @NonNull buildHistoryItem(int elapsedRealtime, boolean stateOn, int uid)241 private BatteryStats.HistoryItem buildHistoryItem(int elapsedRealtime, boolean stateOn, 242 int uid) { 243 mClock.realtime = elapsedRealtime; 244 BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem(); 245 historyItem.time = mMonotonicClock.monotonicTime(); 246 historyItem.states = stateOn ? TEST_STATE_FLAG : 0; 247 if (stateOn) { 248 historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE 249 | BatteryStats.HistoryItem.EVENT_FLAG_START; 250 } else { 251 historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE 252 | BatteryStats.HistoryItem.EVENT_FLAG_FINISH; 253 } 254 historyItem.eventTag = historyItem.localEventTag; 255 historyItem.eventTag.uid = uid; 256 historyItem.eventTag.string = "test"; 257 return historyItem; 258 } 259 states(int... states)260 private int[] states(int... states) { 261 return states; 262 } 263 createAggregatedPowerStats( Supplier<PowerStatsProcessor> processorSupplier)264 private static PowerComponentAggregatedPowerStats createAggregatedPowerStats( 265 Supplier<PowerStatsProcessor> processorSupplier) { 266 AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); 267 config.trackPowerComponent(POWER_COMPONENT) 268 .trackDeviceStates(STATE_POWER, STATE_SCREEN) 269 .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE) 270 .setProcessorSupplier(processorSupplier); 271 272 PowerComponentAggregatedPowerStats powerComponentStats = 273 new AggregatedPowerStats(config).getPowerComponentStats(POWER_COMPONENT); 274 powerComponentStats.start(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