1 /* 2 * Copyright (C) 2020 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 com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.anyDouble; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.spy; 25 import static org.mockito.Mockito.when; 26 27 import android.content.Context; 28 import android.content.res.Resources; 29 import android.net.NetworkStats; 30 import android.os.BatteryConsumer; 31 import android.os.BatteryStats; 32 import android.os.BatteryUsageStats; 33 import android.os.BatteryUsageStatsQuery; 34 import android.os.ConditionVariable; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.UidBatteryConsumer; 38 import android.os.UserBatteryConsumer; 39 import android.platform.test.ravenwood.RavenwoodRule; 40 import android.util.SparseArray; 41 import android.util.Xml; 42 43 import com.android.internal.os.CpuScalingPolicies; 44 import com.android.internal.os.PowerProfile; 45 import com.android.internal.power.EnergyConsumerStats; 46 47 import org.junit.rules.TestRule; 48 import org.junit.runner.Description; 49 import org.junit.runners.model.Statement; 50 import org.mockito.stubbing.Answer; 51 import org.xmlpull.v1.XmlPullParser; 52 53 import java.io.File; 54 import java.io.IOException; 55 import java.nio.file.Files; 56 import java.util.Arrays; 57 58 @SuppressWarnings("SynchronizeOnNonFinalField") 59 public class BatteryUsageStatsRule implements TestRule { 60 public static final BatteryUsageStatsQuery POWER_PROFILE_MODEL_ONLY = 61 new BatteryUsageStatsQuery.Builder() 62 .powerProfileModeledOnly() 63 .includePowerModels() 64 .build(); 65 66 private final PowerProfile mPowerProfile; 67 private final MockClock mMockClock = new MockClock(); 68 private String mTestName; 69 private boolean mCreateTempDirectory; 70 private File mHistoryDir; 71 private MockBatteryStatsImpl mBatteryStats; 72 private Handler mHandler; 73 74 private BatteryUsageStats mBatteryUsageStats; 75 private boolean mScreenOn; 76 private boolean mDefaultCpuScalingPolicy = true; 77 private SparseArray<int[]> mCpusByPolicy = new SparseArray<>(); 78 private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>(); 79 80 private int mDisplayCount = -1; 81 private int mPerUidModemModel = -1; 82 private NetworkStats mNetworkStats; 83 private boolean[] mSupportedStandardBuckets; 84 private String[] mCustomPowerComponentNames; 85 private Throwable mThrowable; 86 private final BatteryStatsImpl.BatteryStatsConfig.Builder mBatteryStatsConfigBuilder; 87 BatteryUsageStatsRule()88 public BatteryUsageStatsRule() { 89 this(0); 90 } 91 BatteryUsageStatsRule(long currentTime)92 public BatteryUsageStatsRule(long currentTime) { 93 mHandler = mock(Handler.class); 94 mPowerProfile = spy(new PowerProfile()); 95 mMockClock.currentTime = currentTime; 96 mCpusByPolicy.put(0, new int[]{0, 1, 2, 3}); 97 mCpusByPolicy.put(4, new int[]{4, 5, 6, 7}); 98 mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); 99 mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); 100 mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() 101 .setPowerStatsThrottlePeriodMillis( 102 BatteryConsumer.powerComponentIdToString( 103 BatteryConsumer.POWER_COMPONENT_CPU), 10000) 104 .setPowerStatsThrottlePeriodMillis( 105 BatteryConsumer.powerComponentIdToString( 106 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000); 107 } 108 initBatteryStats()109 private void initBatteryStats() { 110 if (mBatteryStats != null) return; 111 112 if (mCreateTempDirectory) { 113 try { 114 mHistoryDir = Files.createTempDirectory(mTestName).toFile(); 115 } catch (IOException e) { 116 throw new RuntimeException(e); 117 } 118 clearDirectory(); 119 } 120 mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(), 121 mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver()); 122 mBatteryStats.setPowerProfile(mPowerProfile); 123 mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); 124 synchronized (mBatteryStats) { 125 mBatteryStats.initEnergyConsumerStatsLocked(mSupportedStandardBuckets, 126 mCustomPowerComponentNames); 127 } 128 mBatteryStats.informThatAllExternalStatsAreFlushed(); 129 130 if (mDisplayCount != -1) { 131 mBatteryStats.setDisplayCountLocked(mDisplayCount); 132 } 133 if (mPerUidModemModel != -1) { 134 synchronized (mBatteryStats) { 135 mBatteryStats.setPerUidModemModel(mPerUidModemModel); 136 } 137 } 138 if (mNetworkStats != null) { 139 mBatteryStats.setNetworkStats(mNetworkStats); 140 } 141 } 142 getMockClock()143 public MockClock getMockClock() { 144 return mMockClock; 145 } 146 getHandler()147 public Handler getHandler() { 148 return mHandler; 149 } 150 getHistoryDir()151 public File getHistoryDir() { 152 return mHistoryDir; 153 } 154 createTempDirectory()155 public BatteryUsageStatsRule createTempDirectory() { 156 mCreateTempDirectory = true; 157 return this; 158 } 159 setTestPowerProfile(String resourceName)160 public BatteryUsageStatsRule setTestPowerProfile(String resourceName) { 161 mPowerProfile.initForTesting(resolveParser(resourceName)); 162 return this; 163 } 164 resolveParser(String resourceName)165 public static XmlPullParser resolveParser(String resourceName) { 166 if (RavenwoodRule.isOnRavenwood()) { 167 try { 168 return Xml.resolvePullParser(BatteryUsageStatsRule.class.getClassLoader() 169 .getResourceAsStream("res/xml/" + resourceName + ".xml")); 170 } catch (IOException e) { 171 throw new RuntimeException(e); 172 } 173 } else { 174 Context context = androidx.test.InstrumentationRegistry.getContext(); 175 Resources resources = context.getResources(); 176 int resId = resources.getIdentifier(resourceName, "xml", context.getPackageName()); 177 return resources.getXml(resId); 178 } 179 } 180 setCpuScalingPolicy(int policy, int[] relatedCpus, int[] frequencies)181 public BatteryUsageStatsRule setCpuScalingPolicy(int policy, int[] relatedCpus, 182 int[] frequencies) { 183 if (mDefaultCpuScalingPolicy) { 184 mCpusByPolicy.clear(); 185 mFreqsByPolicy.clear(); 186 mDefaultCpuScalingPolicy = false; 187 } 188 mCpusByPolicy.put(policy, relatedCpus); 189 mFreqsByPolicy.put(policy, frequencies); 190 if (mBatteryStats != null) { 191 mBatteryStats.setCpuScalingPolicies( 192 new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); 193 } 194 return this; 195 } 196 setAveragePower(String key, double value)197 public BatteryUsageStatsRule setAveragePower(String key, double value) { 198 when(mPowerProfile.getAveragePower(key)).thenReturn(value); 199 when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value); 200 return this; 201 } 202 setAveragePowerUnspecified(String key)203 public BatteryUsageStatsRule setAveragePowerUnspecified(String key) { 204 when(mPowerProfile.getAveragePower(key)).thenReturn(0.0); 205 when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())) 206 .thenAnswer((Answer<Double>) invocation -> (Double) invocation.getArguments()[1]); 207 return this; 208 } 209 setAveragePower(String key, double[] values)210 public BatteryUsageStatsRule setAveragePower(String key, double[] values) { 211 when(mPowerProfile.getNumElements(key)).thenReturn(values.length); 212 for (int i = 0; i < values.length; i++) { 213 when(mPowerProfile.getAveragePower(key, i)).thenReturn(values[i]); 214 } 215 return this; 216 } 217 setAveragePowerForCpuScalingPolicy(int policy, double value)218 public BatteryUsageStatsRule setAveragePowerForCpuScalingPolicy(int policy, double value) { 219 when(mPowerProfile.getAveragePowerForCpuScalingPolicy(policy)).thenReturn(value); 220 return this; 221 } 222 setAveragePowerForCpuScalingStep(int policy, int step, double value)223 public BatteryUsageStatsRule setAveragePowerForCpuScalingStep(int policy, int step, 224 double value) { 225 when(mPowerProfile.getAveragePowerForCpuScalingStep(policy, step)).thenReturn(value); 226 return this; 227 } 228 229 /** 230 * Mocks the CPU bracket count 231 */ setCpuPowerBracketCount(int count)232 public BatteryUsageStatsRule setCpuPowerBracketCount(int count) { 233 when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(count); 234 return this; 235 } 236 237 /** 238 * Mocks the CPU bracket for the given CPU scaling policy and step 239 */ setCpuPowerBracket(int policy, int step, int bracket)240 public BatteryUsageStatsRule setCpuPowerBracket(int policy, int step, int bracket) { 241 when(mPowerProfile.getCpuPowerBracketForScalingStep(policy, step)).thenReturn(bracket); 242 return this; 243 } 244 setAveragePowerForOrdinal(String group, int ordinal, double value)245 public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal, 246 double value) { 247 when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value); 248 when(mPowerProfile.getAveragePowerForOrdinal(eq(group), eq(ordinal), 249 anyDouble())).thenReturn(value); 250 return this; 251 } 252 setNumDisplays(int value)253 public BatteryUsageStatsRule setNumDisplays(int value) { 254 when(mPowerProfile.getNumDisplays()).thenReturn(value); 255 mDisplayCount = value; 256 if (mBatteryStats != null) { 257 mBatteryStats.setDisplayCountLocked(mDisplayCount); 258 } 259 return this; 260 } 261 setPerUidModemModel(int perUidModemModel)262 public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) { 263 mPerUidModemModel = perUidModemModel; 264 if (mBatteryStats != null) { 265 synchronized (mBatteryStats) { 266 mBatteryStats.setPerUidModemModel(mPerUidModemModel); 267 } 268 } 269 return this; 270 } 271 272 /** Call only after setting the power profile information. */ initMeasuredEnergyStatsLocked()273 public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() { 274 return initMeasuredEnergyStatsLocked(new String[0]); 275 } 276 277 /** Call only after setting the power profile information. */ initMeasuredEnergyStatsLocked( String[] customPowerComponentNames)278 public BatteryUsageStatsRule initMeasuredEnergyStatsLocked( 279 String[] customPowerComponentNames) { 280 mCustomPowerComponentNames = customPowerComponentNames; 281 mSupportedStandardBuckets = new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS]; 282 Arrays.fill(mSupportedStandardBuckets, true); 283 if (mBatteryStats != null) { 284 synchronized (mBatteryStats) { 285 mBatteryStats.initEnergyConsumerStatsLocked(mSupportedStandardBuckets, 286 mCustomPowerComponentNames); 287 mBatteryStats.informThatAllExternalStatsAreFlushed(); 288 } 289 } 290 return this; 291 } 292 setPowerStatsThrottlePeriodMillis(int powerComponent, long throttleMs)293 public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent, 294 long throttleMs) { 295 mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis( 296 BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs); 297 return this; 298 } 299 startWithScreenOn(boolean screenOn)300 public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) { 301 mScreenOn = screenOn; 302 return this; 303 } 304 setNetworkStats(NetworkStats networkStats)305 public void setNetworkStats(NetworkStats networkStats) { 306 mNetworkStats = networkStats; 307 if (mBatteryStats != null) { 308 mBatteryStats.setNetworkStats(mNetworkStats); 309 } 310 } 311 312 @Override apply(Statement base, Description description)313 public Statement apply(Statement base, Description description) { 314 mTestName = description.getClassName() + "#" + description.getMethodName(); 315 return new Statement() { 316 @Override 317 public void evaluate() throws Throwable { 318 before(); 319 base.evaluate(); 320 after(); 321 } 322 }; 323 } 324 325 private void before() { 326 BatteryUsageStats.DEBUG_INSTANCE_COUNT = true; 327 HandlerThread bgThread = new HandlerThread("bg thread"); 328 bgThread.setUncaughtExceptionHandler((thread, throwable)-> { 329 mThrowable = throwable; 330 }); 331 bgThread.start(); 332 mHandler = new Handler(bgThread.getLooper()); 333 334 initBatteryStats(); 335 mBatteryStats.setOnBatteryInternal(true); 336 mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0); 337 mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0); 338 } 339 340 private void after() throws Throwable { 341 waitForBackgroundThread(); 342 if (mBatteryUsageStats != null) { 343 mBatteryUsageStats.close(); 344 } 345 BatteryUsageStats.assertAllInstancesClosed(); 346 } 347 348 public void waitForBackgroundThread() throws Throwable { 349 if (mThrowable != null) { 350 throw mThrowable; 351 } 352 353 ConditionVariable done = new ConditionVariable(); 354 if (mHandler.post(done::open)) { 355 boolean success = done.block(5000); 356 if (mThrowable != null) { 357 throw mThrowable; 358 } 359 assertThat(success).isTrue(); 360 } 361 } 362 363 public PowerProfile getPowerProfile() { 364 return mPowerProfile; 365 } 366 367 public CpuScalingPolicies getCpuScalingPolicies() { 368 synchronized (mBatteryStats) { 369 return mBatteryStats.getCpuScalingPolicies(); 370 } 371 } 372 373 public MockBatteryStatsImpl getBatteryStats() { 374 if (mBatteryStats == null) { 375 initBatteryStats(); 376 } 377 return mBatteryStats; 378 } 379 380 public BatteryStatsImpl.Uid getUidStats(int uid) { 381 return mBatteryStats.getUidStatsLocked(uid); 382 } 383 384 /** 385 * Adds the supplied duration to all three: current time, elapsed time and uptime 386 */ 387 public void advanceTime(long millis) { 388 mMockClock.currentTime += millis; 389 mMockClock.realtime += millis; 390 mMockClock.uptime += millis; 391 } 392 393 /** 394 * Adds the supplied duration to current time and elapsed time, but not to uptime 395 */ 396 public void advanceSuspendedTime(long millis) { 397 mMockClock.currentTime += millis; 398 mMockClock.realtime += millis; 399 } 400 401 public void setTime(long realtimeMs, long uptimeMs) { 402 mMockClock.currentTime = realtimeMs; 403 mMockClock.realtime = realtimeMs; 404 mMockClock.uptime = uptimeMs; 405 } 406 407 public void setCurrentTime(long currentTimeMs) { 408 mMockClock.currentTime = currentTimeMs; 409 } 410 411 BatteryUsageStats apply(PowerCalculator... calculators) { 412 return apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(), 413 calculators); 414 } 415 416 BatteryUsageStats apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) { 417 if (mBatteryUsageStats != null) { 418 try { 419 mBatteryUsageStats.close(); 420 } catch (IOException e) { 421 throw new RuntimeException(e); 422 } 423 mBatteryUsageStats = null; 424 } 425 final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames(); 426 final boolean includeProcessStateData = (query.getFlags() 427 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0; 428 final boolean includeScreenStateData = (query.getFlags() 429 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0; 430 final boolean includePowerStateData = (query.getFlags() 431 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0; 432 final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); 433 BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( 434 customPowerComponentNames, includeProcessStateData, 435 includeScreenStateData, includePowerStateData, minConsumedPowerThreshold); 436 SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats(); 437 for (int i = 0; i < uidStats.size(); i++) { 438 builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i)); 439 } 440 441 for (PowerCalculator calculator : calculators) { 442 calculator.calculate(builder, mBatteryStats, mMockClock.realtime * 1000, 443 mMockClock.uptime * 1000, query); 444 } 445 446 mBatteryUsageStats = builder.build(); 447 return mBatteryUsageStats; 448 } 449 450 public BatteryConsumer getDeviceBatteryConsumer() { 451 return mBatteryUsageStats.getAggregateBatteryConsumer( 452 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); 453 } 454 455 public BatteryConsumer getAppsBatteryConsumer() { 456 return mBatteryUsageStats.getAggregateBatteryConsumer( 457 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); 458 } 459 460 public UidBatteryConsumer getUidBatteryConsumer(int uid) { 461 for (UidBatteryConsumer ubc : mBatteryUsageStats.getUidBatteryConsumers()) { 462 if (ubc.getUid() == uid) { 463 return ubc; 464 } 465 } 466 return null; 467 } 468 469 public UserBatteryConsumer getUserBatteryConsumer(int userId) { 470 for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) { 471 if (ubc.getUserId() == userId) { 472 return ubc; 473 } 474 } 475 return null; 476 } 477 478 public void clearDirectory() { 479 clearDirectory(mHistoryDir); 480 } 481 482 private void clearDirectory(File dir) { 483 if (dir.exists()) { 484 for (File child : dir.listFiles()) { 485 if (child.isDirectory()) { 486 clearDirectory(child); 487 } 488 child.delete(); 489 } 490 } 491 } 492 } 493