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