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 HandlerThread bgThread = new HandlerThread("bg thread"); 327 bgThread.setUncaughtExceptionHandler((thread, throwable)-> { 328 mThrowable = throwable; 329 }); 330 bgThread.start(); 331 mHandler = new Handler(bgThread.getLooper()); 332 333 initBatteryStats(); 334 mBatteryStats.setOnBatteryInternal(true); 335 mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0); 336 mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0); 337 } 338 339 private void after() throws Throwable { 340 waitForBackgroundThread(); 341 } 342 343 public void waitForBackgroundThread() throws Throwable { 344 if (mThrowable != null) { 345 throw mThrowable; 346 } 347 348 ConditionVariable done = new ConditionVariable(); 349 if (mHandler.post(done::open)) { 350 boolean success = done.block(5000); 351 if (mThrowable != null) { 352 throw mThrowable; 353 } 354 assertThat(success).isTrue(); 355 } 356 } 357 358 public PowerProfile getPowerProfile() { 359 return mPowerProfile; 360 } 361 362 public CpuScalingPolicies getCpuScalingPolicies() { 363 synchronized (mBatteryStats) { 364 return mBatteryStats.getCpuScalingPolicies(); 365 } 366 } 367 368 public MockBatteryStatsImpl getBatteryStats() { 369 if (mBatteryStats == null) { 370 initBatteryStats(); 371 } 372 return mBatteryStats; 373 } 374 375 public BatteryStatsImpl.Uid getUidStats(int uid) { 376 return mBatteryStats.getUidStatsLocked(uid); 377 } 378 379 public void setTime(long realtimeMs, long uptimeMs) { 380 mMockClock.currentTime = realtimeMs; 381 mMockClock.realtime = realtimeMs; 382 mMockClock.uptime = uptimeMs; 383 } 384 385 public void setCurrentTime(long currentTimeMs) { 386 mMockClock.currentTime = currentTimeMs; 387 } 388 389 BatteryUsageStats apply(PowerCalculator... calculators) { 390 return apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(), 391 calculators); 392 } 393 394 BatteryUsageStats apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) { 395 final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames(); 396 final boolean includePowerModels = (query.getFlags() 397 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; 398 final boolean includeProcessStateData = (query.getFlags() 399 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0; 400 final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); 401 BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( 402 customPowerComponentNames, includePowerModels, includeProcessStateData, 403 minConsumedPowerThreshold); 404 SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats(); 405 for (int i = 0; i < uidStats.size(); i++) { 406 builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i)); 407 } 408 409 for (PowerCalculator calculator : calculators) { 410 calculator.calculate(builder, mBatteryStats, mMockClock.realtime * 1000, 411 mMockClock.uptime * 1000, query); 412 } 413 414 mBatteryUsageStats = builder.build(); 415 return mBatteryUsageStats; 416 } 417 418 public BatteryConsumer getDeviceBatteryConsumer() { 419 return mBatteryUsageStats.getAggregateBatteryConsumer( 420 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); 421 } 422 423 public BatteryConsumer getAppsBatteryConsumer() { 424 return mBatteryUsageStats.getAggregateBatteryConsumer( 425 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); 426 } 427 428 public UidBatteryConsumer getUidBatteryConsumer(int uid) { 429 for (UidBatteryConsumer ubc : mBatteryUsageStats.getUidBatteryConsumers()) { 430 if (ubc.getUid() == uid) { 431 return ubc; 432 } 433 } 434 return null; 435 } 436 437 public UserBatteryConsumer getUserBatteryConsumer(int userId) { 438 for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) { 439 if (ubc.getUserId() == userId) { 440 return ubc; 441 } 442 } 443 return null; 444 } 445 446 public void clearDirectory() { 447 clearDirectory(mHistoryDir); 448 } 449 450 private void clearDirectory(File dir) { 451 if (dir.exists()) { 452 for (File child : dir.listFiles()) { 453 if (child.isDirectory()) { 454 clearDirectory(child); 455 } 456 child.delete(); 457 } 458 } 459 } 460 } 461