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