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