1 /*
2  * Copyright (C) 2021 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 package com.android.internal.os;
17 
18 import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.fail;
26 
27 import android.os.AggregateBatteryConsumer;
28 import android.os.BatteryConsumer;
29 import android.os.BatteryUsageStats;
30 import android.os.UidBatteryConsumer;
31 import android.os.nano.BatteryUsageStatsAtomsProto;
32 import android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage;
33 
34 import androidx.test.filters.SmallTest;
35 
36 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
37 
38 import org.junit.Test;
39 
40 import java.util.Arrays;
41 import java.util.List;
42 
43 
44 @SmallTest
45 public class BatteryUsageStatsPulledTest {
46 
47     private static final int UID_0 = 1000;
48     private static final int UID_1 = 2000;
49     private static final int UID_2 = 3000;
50     private static final int UID_3 = 4000;
51     private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
52             BatteryConsumer.PROCESS_STATE_FOREGROUND,
53             BatteryConsumer.PROCESS_STATE_BACKGROUND,
54             BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE
55     };
56 
57     @Test
testGetStatsProto()58     public void testGetStatsProto() {
59         final BatteryUsageStats bus = buildBatteryUsageStats();
60         final byte[] bytes = bus.getStatsProto();
61         BatteryUsageStatsAtomsProto proto;
62         try {
63             proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
64         } catch (InvalidProtocolBufferNanoException e) {
65             fail("Invalid proto: " + e);
66             return;
67         }
68 
69         assertEquals(bus.getStatsStartTimestamp(), proto.sessionStartMillis);
70         assertEquals(bus.getStatsEndTimestamp(), proto.sessionEndMillis);
71         assertEquals(
72                 bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(),
73                 proto.sessionDurationMillis);
74         assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage);
75         assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis);
76 
77         assertEquals(3, proto.deviceBatteryConsumer.powerComponents.length); // Only 3 are non-empty
78 
79         final AggregateBatteryConsumer abc = bus.getAggregateBatteryConsumer(
80                 AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
81         assertSameBatteryConsumer("For deviceBatteryConsumer",
82                 bus.getAggregateBatteryConsumer(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE),
83                 proto.deviceBatteryConsumer);
84 
85         for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) {
86             assertPowerComponentModel(i, abc.getPowerModel(i), proto);
87         }
88 
89         // Now for the UidBatteryConsumers.
90         final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
91         uidConsumers.sort((a, b) -> a.getUid() - b.getUid());
92 
93         final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto
94                 = proto.uidBatteryConsumers;
95         Arrays.sort(uidConsumersProto, (a, b) -> a.uid - b.uid);
96 
97         // UID_0 - After sorting, UID_0 should be in position 0 for both data structures
98         assertEquals(UID_0, bus.getUidBatteryConsumers().get(0).getUid());
99         assertEquals(UID_0, proto.uidBatteryConsumers[0].uid);
100         assertSameUidBatteryConsumer(
101                 bus.getUidBatteryConsumers().get(0),
102                 proto.uidBatteryConsumers[0],
103                 false);
104 
105         // UID_1 - After sorting, UID_1 should be in position 1 for both data structures
106         assertEquals(UID_1, bus.getUidBatteryConsumers().get(1).getUid());
107         assertEquals(UID_1, proto.uidBatteryConsumers[1].uid);
108         assertSameUidBatteryConsumer(
109                 bus.getUidBatteryConsumers().get(1),
110                 proto.uidBatteryConsumers[1],
111                 true);
112 
113         // UID_2 - After sorting, UID_2 should be in position 2 for both data structures
114         assertEquals(UID_2, bus.getUidBatteryConsumers().get(2).getUid());
115         assertEquals(UID_2, proto.uidBatteryConsumers[2].uid);
116         assertSameUidBatteryConsumer(
117                 bus.getUidBatteryConsumers().get(2),
118                 proto.uidBatteryConsumers[2],
119                 false);
120 
121         // UID_3 - Should be none, since no interesting data (done last for debugging convenience).
122         assertEquals(3, proto.uidBatteryConsumers.length);
123     }
124 
assertSameBatteryConsumer(String message, BatteryConsumer consumer, android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData consumerProto)125     private void assertSameBatteryConsumer(String message, BatteryConsumer consumer,
126             android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData consumerProto) {
127         assertNotNull(message, consumerProto);
128         assertEquals(
129                 convertMahToDc(consumer.getConsumedPower()),
130                 consumerProto.totalConsumedPowerDeciCoulombs);
131 
132         for (PowerComponentUsage componentProto : consumerProto.powerComponents) {
133             final int componentId = componentProto.component;
134             if (componentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
135                 assertEquals(message + " for component " + componentId,
136                         convertMahToDc(consumer.getConsumedPower(componentId)),
137                         componentProto.powerDeciCoulombs);
138                 assertEquals(message + " for component " + componentId,
139                         consumer.getUsageDurationMillis(componentId),
140                         componentProto.durationMillis);
141             } else {
142                 assertEquals(message + " for custom component " + componentId,
143                         convertMahToDc(consumer.getConsumedPowerForCustomComponent(componentId)),
144                         componentProto.powerDeciCoulombs);
145                 assertEquals(message + " for custom component " + componentId,
146                         consumer.getUsageDurationForCustomComponentMillis(componentId),
147                         componentProto.durationMillis);
148             }
149         }
150 
151         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
152                 componentId++) {
153             final BatteryConsumer.Key[] keys = consumer.getKeys(componentId);
154             if (keys == null || keys.length <= 1) {
155                 continue;
156             }
157 
158             for (BatteryConsumer.Key key : keys) {
159                 if (key.processState == 0) {
160                     continue;
161                 }
162 
163                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
164                         sliceProto = null;
165                 for (BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
166                         slice : consumerProto.slices) {
167                     if (slice.powerComponent.component == componentId
168                             && slice.processState == key.processState) {
169                         sliceProto = slice;
170                         break;
171                     }
172                 }
173 
174                 final long expectedPowerDc = convertMahToDc(consumer.getConsumedPower(key));
175                 final long expectedUsageDurationMillis = consumer.getUsageDurationMillis(key);
176                 if (expectedPowerDc == 0 && expectedUsageDurationMillis == 0) {
177                     assertThat(sliceProto).isNull();
178                 } else {
179                     assertThat(sliceProto).isNotNull();
180                     assertThat(sliceProto.powerComponent.powerDeciCoulombs)
181                             .isEqualTo(expectedPowerDc);
182                     assertThat(sliceProto.powerComponent.durationMillis)
183                             .isEqualTo(expectedUsageDurationMillis);
184                 }
185             }
186         }
187     }
188 
assertSameUidBatteryConsumer( android.os.UidBatteryConsumer uidConsumer, BatteryUsageStatsAtomsProto.UidBatteryConsumer uidConsumerProto, boolean expectNullBatteryConsumerData)189     private void assertSameUidBatteryConsumer(
190             android.os.UidBatteryConsumer uidConsumer,
191             BatteryUsageStatsAtomsProto.UidBatteryConsumer uidConsumerProto,
192             boolean expectNullBatteryConsumerData) {
193 
194         final int uid = uidConsumerProto.uid;
195         assertEquals("Uid consumers had mismatched uids", uid, uidConsumer.getUid());
196 
197         assertEquals("For uid " + uid,
198                 uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND),
199                 uidConsumerProto.timeInForegroundMillis);
200         assertEquals("For uid " + uid,
201                 uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND),
202                 uidConsumerProto.timeInBackgroundMillis);
203         for (int processState : UID_USAGE_TIME_PROCESS_STATES) {
204             final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState);
205             if (timeInStateMillis <= 0) {
206                 continue;
207             }
208             assertEquals("For uid " + uid + ", process state " + processState,
209                     timeInStateMillis,
210                     Arrays.stream(uidConsumerProto.timeInState)
211                             .filter(timeInState -> timeInState.processState == processState)
212                             .findFirst()
213                             .orElseThrow()
214                             .timeInStateMillis);
215         }
216 
217         if (expectNullBatteryConsumerData) {
218             assertNull("For uid " + uid, uidConsumerProto.batteryConsumerData);
219         } else {
220             assertSameBatteryConsumer("For uid " + uid,
221                     uidConsumer,
222                     uidConsumerProto.batteryConsumerData);
223         }
224     }
225 
226     /**
227      * Validates the PowerComponentModel object that matches powerComponent.
228      */
assertPowerComponentModel(int powerComponent, @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto)229     private void assertPowerComponentModel(int powerComponent,
230             @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto) {
231         boolean found = false;
232         for (BatteryUsageStatsAtomsProto.PowerComponentModel powerComponentModel :
233                 proto.componentModels) {
234             if (powerComponentModel.component == powerComponent) {
235                 if (found) {
236                     fail("Power component " + BatteryConsumer.powerComponentIdToString(
237                             powerComponent) + " found multiple times in the proto");
238                 }
239                 found = true;
240                 final int expectedPowerModel = BatteryConsumer.powerModelToProtoEnum(powerModel);
241                 assertEquals(expectedPowerModel, powerComponentModel.powerModel);
242             }
243         }
244         if (!found) {
245             final int model = BatteryConsumer.powerModelToProtoEnum(powerModel);
246             assertEquals(
247                     "Power component " + BatteryConsumer.powerComponentIdToString(powerComponent)
248                             + " was not found in the proto but has a defined power model.",
249                     BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED,
250                     model);
251         }
252     }
253 
254     /** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
convertMahToDc(double powerMah)255     private long convertMahToDc(double powerMah) {
256         return (long) (powerMah * 36 + 0.5);
257     }
258 
buildBatteryUsageStats()259     private BatteryUsageStats buildBatteryUsageStats() {
260         final BatteryUsageStats.Builder builder =
261                 new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
262                         /* includePowerModels */ true,
263                         /* includeProcessStats */ true,
264                         /* minConsumedPowerThreshold */ 0)
265                         .setDischargePercentage(20)
266                         .setDischargedPowerRange(1000, 2000)
267                         .setDischargeDurationMs(1234)
268                         .setStatsStartTimestamp(1000);
269         final UidBatteryConsumer.Builder uidBuilder = builder
270                 .getOrCreateUidBatteryConsumerBuilder(UID_0)
271                 .setPackageWithHighestDrain("myPackage0")
272                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000)
273                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 2000)
274                 .setConsumedPower(
275                         BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
276                 .setConsumedPower(
277                         BatteryConsumer.POWER_COMPONENT_CPU, 400)
278                 .setConsumedPowerForCustomComponent(
279                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 450)
280                 .setConsumedPowerForCustomComponent(
281                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 500)
282                 .setUsageDurationMillis(
283                         BatteryConsumer.POWER_COMPONENT_CPU, 600)
284                 .setUsageDurationForCustomComponentMillis(
285                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 800);
286 
287         final BatteryConsumer.Key keyFg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
288                 BatteryConsumer.PROCESS_STATE_FOREGROUND);
289         final BatteryConsumer.Key keyBg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
290                 BatteryConsumer.PROCESS_STATE_BACKGROUND);
291         final BatteryConsumer.Key keyFgs = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
292                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
293         final BatteryConsumer.Key keyCached = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
294                 BatteryConsumer.PROCESS_STATE_CACHED);
295 
296         uidBuilder.setConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
297                 .setUsageDurationMillis(keyFg, 8100)
298                 .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
299                 .setUsageDurationMillis(keyBg, 8200)
300                 .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
301                 .setUsageDurationMillis(keyFgs, 8300)
302                 .setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
303                 .setUsageDurationMillis(keyFgs, 8400);
304 
305         builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
306                 .setPackageWithHighestDrain("myPackage1")
307                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234);
308 
309         builder.getOrCreateUidBatteryConsumerBuilder(UID_2)
310                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
311                         766);
312 
313         builder.getOrCreateUidBatteryConsumerBuilder(UID_3);
314 
315         builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
316                 .setConsumedPower(30000)
317                 .setConsumedPower(
318                         BatteryConsumer.POWER_COMPONENT_CPU, 20100,
319                         BatteryConsumer.POWER_MODEL_POWER_PROFILE)
320                 .setConsumedPower(
321                         BatteryConsumer.POWER_COMPONENT_AUDIO, 0,
322                         BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
323                 .setConsumedPower(
324                         BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
325                         BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
326                 .setConsumedPowerForCustomComponent(
327                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
328                 .setUsageDurationMillis(
329                         BatteryConsumer.POWER_COMPONENT_CPU, 20300)
330                 .setUsageDurationForCustomComponentMillis(
331                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400);
332 
333         // Not used; just to make sure extraneous data doesn't mess things up.
334         builder.getAggregateBatteryConsumerBuilder(
335                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
336                 .setConsumedPower(
337                         BatteryConsumer.POWER_COMPONENT_CPU, 10100,
338                         BatteryConsumer.POWER_MODEL_POWER_PROFILE)
339                 .setConsumedPowerForCustomComponent(
340                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
341 
342         return builder.build();
343     }
344 
345     @Test
testLargeAtomTruncated()346     public void testLargeAtomTruncated() {
347         final BatteryUsageStats.Builder builder =
348                 new BatteryUsageStats.Builder(new String[0], true, false, 0);
349         // If not truncated, this BatteryUsageStats object would generate a proto buffer
350         // significantly larger than 50 Kb
351         for (int i = 0; i < 3000; i++) {
352             builder.getOrCreateUidBatteryConsumerBuilder(i)
353                     .setTimeInProcessStateMs(
354                             BatteryConsumer.PROCESS_STATE_FOREGROUND, 1 * 60 * 1000)
355                     .setTimeInProcessStateMs(
356                             BatteryConsumer.PROCESS_STATE_BACKGROUND, 2 * 60 * 1000)
357                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
358                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
359         }
360 
361         // Add a UID with much larger battery footprint
362         final int largeConsumerUid = 3001;
363         builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerUid)
364                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 10 * 60 * 1000)
365                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 20 * 60 * 1000)
366                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
367                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
368 
369         // Add a UID with much larger usage duration
370         final int highUsageUid = 3002;
371         builder.getOrCreateUidBatteryConsumerBuilder(highUsageUid)
372                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 60 * 60 * 1000)
373                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 120 * 60 * 1000)
374                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
375                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
376 
377         BatteryUsageStats batteryUsageStats = builder.build();
378         final byte[] bytes = batteryUsageStats.getStatsProto();
379         assertThat(bytes.length).isGreaterThan(40000);
380         assertThat(bytes.length).isLessThan(50000);
381 
382         BatteryUsageStatsAtomsProto proto;
383         try {
384             proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
385         } catch (InvalidProtocolBufferNanoException e) {
386             fail("Invalid proto: " + e);
387             return;
388         }
389 
390         boolean largeConsumerIncluded = false;
391         boolean highUsageAppIncluded = false;
392         for (int i = 0; i < proto.uidBatteryConsumers.length; i++) {
393             if (proto.uidBatteryConsumers[i].uid == largeConsumerUid) {
394                 largeConsumerIncluded = true;
395                 BatteryUsageStatsAtomsProto.BatteryConsumerData consumerData =
396                         proto.uidBatteryConsumers[i].batteryConsumerData;
397                 assertThat(consumerData.totalConsumedPowerDeciCoulombs / 36)
398                         .isEqualTo(300 + 400);
399             } else if (proto.uidBatteryConsumers[i].uid == highUsageUid) {
400                 highUsageAppIncluded = true;
401                 BatteryUsageStatsAtomsProto.BatteryConsumerData consumerData =
402                         proto.uidBatteryConsumers[i].batteryConsumerData;
403                 assertThat(consumerData.totalConsumedPowerDeciCoulombs / 36)
404                         .isEqualTo(3 + 4);
405             }
406         }
407 
408         assertThat(largeConsumerIncluded).isTrue();
409         assertThat(highUsageAppIncluded).isTrue();
410     }
411 }
412