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