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