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 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.hardware.power.stats.EnergyConsumer; 23 import android.hardware.power.stats.EnergyConsumerAttribution; 24 import android.hardware.power.stats.EnergyConsumerResult; 25 import android.hardware.power.stats.EnergyConsumerType; 26 import android.util.Slog; 27 import android.util.SparseArray; 28 import android.util.SparseIntArray; 29 import android.util.SparseLongArray; 30 31 import java.io.PrintWriter; 32 33 /** 34 * Keeps snapshots of data from previously pulled EnergyConsumerResults. 35 */ 36 public class EnergyConsumerSnapshot { 37 private static final String TAG = "EnergyConsumerSnapshot"; 38 39 private static final int MILLIVOLTS_PER_VOLT = 1000; 40 41 public static final long UNAVAILABLE = android.os.BatteryStats.POWER_DATA_UNAVAILABLE; 42 43 /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */ 44 private final SparseArray<EnergyConsumer> mEnergyConsumers; 45 46 /** Number of ordinals for {@link EnergyConsumerType#CPU_CLUSTER}. */ 47 private final int mNumCpuClusterOrdinals; 48 49 /** Number of ordinals for {@link EnergyConsumerType#DISPLAY}. */ 50 private final int mNumDisplayOrdinals; 51 52 /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */ 53 private final int mNumOtherOrdinals; 54 55 /** 56 * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time 57 * each {@link EnergyConsumer} was updated. 58 * 59 * Note that the snapshots for different ids may have been taken at different times. 60 * Note that energies for all existing ids are stored here, including each ordinal of type 61 * {@link EnergyConsumerType#OTHER} (tracking their total energy usage). 62 * 63 * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}). 64 */ 65 private final SparseLongArray mEnergyConsumerSnapshots; 66 67 /** 68 * Voltage snapshots, mapping {@link EnergyConsumer#id} to voltage (mV) from the last time 69 * each {@link EnergyConsumer} was updated. 70 * 71 * see {@link #mEnergyConsumerSnapshots}. 72 */ 73 private final SparseIntArray mVoltageSnapshots; 74 75 /** 76 * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type 77 * {@link EnergyConsumerType#OTHER} was updated. 78 * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each 79 * uid to an energy (UJ). That is, 80 * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer. 81 * 82 * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable). 83 * If an id is present but a uid is not present, that uid's energy is 0. 84 */ 85 private final SparseArray<SparseLongArray> mAttributionSnapshots; 86 87 /** 88 * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers 89 * exist and what their details are. 90 */ EnergyConsumerSnapshot(@onNull SparseArray<EnergyConsumer> idToConsumerMap)91 EnergyConsumerSnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) { 92 mEnergyConsumers = idToConsumerMap; 93 mEnergyConsumerSnapshots = new SparseLongArray(mEnergyConsumers.size()); 94 mVoltageSnapshots = new SparseIntArray(mEnergyConsumers.size()); 95 96 mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER, 97 idToConsumerMap); 98 mNumDisplayOrdinals = calculateNumOrdinals(EnergyConsumerType.DISPLAY, idToConsumerMap); 99 mNumOtherOrdinals = calculateNumOrdinals(EnergyConsumerType.OTHER, idToConsumerMap); 100 mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals); 101 } 102 103 /** Class for returning the relevant data calculated from the energy consumer delta */ 104 static class EnergyConsumerDeltaData { 105 /** The chargeUC for {@link EnergyConsumerType#BLUETOOTH}. */ 106 public long bluetoothChargeUC = UNAVAILABLE; 107 108 /** The chargeUC for {@link EnergyConsumerType#CPU_CLUSTER}s. */ 109 public long[] cpuClusterChargeUC = null; 110 111 /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ 112 public long[] displayChargeUC = null; 113 114 /** The chargeUC for {@link EnergyConsumerType#GNSS}. */ 115 public long gnssChargeUC = UNAVAILABLE; 116 117 /** The chargeUC for {@link EnergyConsumerType#MOBILE_RADIO}. */ 118 public long mobileRadioChargeUC = UNAVAILABLE; 119 120 /** The chargeUC for {@link EnergyConsumerType#WIFI}. */ 121 public long wifiChargeUC = UNAVAILABLE; 122 123 /** The chargeUC for {@link EnergyConsumerType#CAMERA}. */ 124 public long cameraChargeUC = UNAVAILABLE; 125 126 /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total chargeUC. */ 127 public @Nullable long[] otherTotalChargeUC = null; 128 129 /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->chargeUC} maps. */ 130 public @Nullable SparseLongArray[] otherUidChargesUC = null; 131 isEmpty()132 boolean isEmpty() { 133 return bluetoothChargeUC <= 0 134 && isEmpty(cpuClusterChargeUC) 135 && isEmpty(displayChargeUC) 136 && gnssChargeUC <= 0 137 && mobileRadioChargeUC <= 0 138 && wifiChargeUC <= 0 139 && isEmpty(otherTotalChargeUC); 140 } 141 isEmpty(long[] values)142 private boolean isEmpty(long[] values) { 143 if (values == null) { 144 return true; 145 } 146 for (long value: values) { 147 if (value > 0) { 148 return false; 149 } 150 } 151 return true; 152 } 153 } 154 155 /** 156 * Update with the freshly retrieved energy consumers and return the difference (delta) 157 * between the previously stored values and the passed-in values. 158 * 159 * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s. 160 * Consumers that are not present are ignored (they are *not* treated as 0). 161 * @param voltageMV current voltage. 162 * 163 * @return an EnergyConsumerDeltaData, containing maps from the updated consumers to 164 * their corresponding charge deltas. 165 * Fields with no interesting data (consumers not present in ecrs or with no energy 166 * difference) will generally be left as their default values. 167 * otherTotalChargeUC and otherUidChargesUC are always either both null or both of 168 * length {@link #getOtherOrdinalNames().length}. 169 * Returns null, if ecrs is null or empty. 170 */ 171 @Nullable updateAndGetDelta(EnergyConsumerResult[] ecrs, int voltageMV)172 public EnergyConsumerDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs, int voltageMV) { 173 if (ecrs == null || ecrs.length == 0) { 174 return null; 175 } 176 if (voltageMV <= 0) { 177 Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMV 178 + " mV) when taking energy consumer snapshot"); 179 // TODO (b/181685156): consider adding the nominal voltage to power profile and 180 // falling back to it if measured voltage is unavailable. 181 return null; 182 } 183 final EnergyConsumerDeltaData output = new EnergyConsumerDeltaData(); 184 185 for (final EnergyConsumerResult ecr : ecrs) { 186 // Extract the new energy data for the current consumer. 187 final int consumerId = ecr.id; 188 final long newEnergyUJ = ecr.energyUWs; 189 final EnergyConsumerAttribution[] newAttributions = ecr.attribution; 190 191 // Look up the static information about this consumer. 192 final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null); 193 if (consumer == null) { 194 Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId); 195 continue; 196 } 197 final int type = consumer.type; 198 final int ordinal = consumer.ordinal; 199 200 // Look up, and update, the old energy and voltage information about this consumer. 201 final long oldEnergyUJ = mEnergyConsumerSnapshots.get(consumerId, UNAVAILABLE); 202 final int oldVoltageMV = mVoltageSnapshots.get(consumerId); 203 mEnergyConsumerSnapshots.put(consumerId, newEnergyUJ); 204 mVoltageSnapshots.put(consumerId, voltageMV); 205 206 final int avgVoltageMV = (oldVoltageMV + voltageMV + 1) / 2; 207 final SparseLongArray otherUidCharges = 208 updateAndGetDeltaForTypeOther(consumer, newAttributions, avgVoltageMV); 209 // Everything is fully done being updated. We now calculate the delta for returning. 210 211 // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0 212 // there's no attribution either. Technically that isn't enforced at the HAL, but we 213 // can't really trust data like that anyway. 214 215 if (oldEnergyUJ < 0) continue; // Generally happens only on initialization. 216 if (newEnergyUJ == oldEnergyUJ) continue; 217 218 final long deltaUJ = newEnergyUJ - oldEnergyUJ; 219 if (deltaUJ < 0 || oldVoltageMV <= 0) { 220 Slog.e(TAG, "Bad data! EnergyConsumer " + consumer.name 221 + ": new energy (" + newEnergyUJ + ") < old energy (" + oldEnergyUJ 222 + "), new voltage (" + voltageMV + "), old voltage (" + oldVoltageMV 223 + "). Skipping. "); 224 continue; 225 } 226 227 final long deltaChargeUC = calculateChargeConsumedUC(deltaUJ, avgVoltageMV); 228 switch (type) { 229 case EnergyConsumerType.BLUETOOTH: 230 output.bluetoothChargeUC = deltaChargeUC; 231 break; 232 233 case EnergyConsumerType.CPU_CLUSTER: 234 if (output.cpuClusterChargeUC == null) { 235 output.cpuClusterChargeUC = new long[mNumCpuClusterOrdinals]; 236 } 237 output.cpuClusterChargeUC[ordinal] = deltaChargeUC; 238 break; 239 240 case EnergyConsumerType.DISPLAY: 241 if (output.displayChargeUC == null) { 242 output.displayChargeUC = new long[mNumDisplayOrdinals]; 243 } 244 output.displayChargeUC[ordinal] = deltaChargeUC; 245 break; 246 247 case EnergyConsumerType.GNSS: 248 output.gnssChargeUC = deltaChargeUC; 249 break; 250 251 case EnergyConsumerType.MOBILE_RADIO: 252 output.mobileRadioChargeUC = deltaChargeUC; 253 break; 254 255 case EnergyConsumerType.WIFI: 256 output.wifiChargeUC = deltaChargeUC; 257 break; 258 259 case EnergyConsumerType.CAMERA: 260 output.cameraChargeUC = deltaChargeUC; 261 break; 262 263 case EnergyConsumerType.OTHER: 264 if (output.otherTotalChargeUC == null) { 265 output.otherTotalChargeUC = new long[mNumOtherOrdinals]; 266 output.otherUidChargesUC = new SparseLongArray[mNumOtherOrdinals]; 267 } 268 output.otherTotalChargeUC[ordinal] = deltaChargeUC; 269 output.otherUidChargesUC[ordinal] = otherUidCharges; 270 break; 271 272 default: 273 Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type); 274 275 } 276 } 277 return output; 278 } 279 280 /** 281 * For a consumer of type {@link EnergyConsumerType#OTHER}, updates 282 * {@link #mAttributionSnapshots} with freshly retrieved energy consumers (per uid) and returns 283 * the charge consumed (in microcoulombs) between the previously stored values and the passed-in 284 * values. 285 * 286 * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}. 287 * @param newAttributions Record of uids and their new energyUJ values. 288 * Any uid not present is treated as having energy 0. 289 * If null or empty, all uids are treated as having energy 0. 290 * @param avgVoltageMV The average voltage since the last snapshot. 291 * @return A map (in the sense of {@link EnergyConsumerDeltaData#otherUidChargesUC} for this 292 * consumer) of uid -> chargeDelta, with all uids that have a non-zero chargeDelta. 293 * Returns null if no delta available to calculate. 294 */ updateAndGetDeltaForTypeOther( @onNull EnergyConsumer consumerInfo, @Nullable EnergyConsumerAttribution[] newAttributions, int avgVoltageMV)295 private @Nullable SparseLongArray updateAndGetDeltaForTypeOther( 296 @NonNull EnergyConsumer consumerInfo, 297 @Nullable EnergyConsumerAttribution[] newAttributions, int avgVoltageMV) { 298 299 if (consumerInfo.type != EnergyConsumerType.OTHER) { 300 return null; 301 } 302 if (newAttributions == null) { 303 // Treat null as empty (i.e. all uids have 0 energy). 304 newAttributions = new EnergyConsumerAttribution[0]; 305 } 306 307 // SparseLongArray mapping uid -> energyUJ (for this particular consumerId) 308 SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null); 309 310 // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return. 311 if (uidOldEnergyMap == null) { 312 uidOldEnergyMap = new SparseLongArray(newAttributions.length); 313 mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap); 314 for (EnergyConsumerAttribution newAttribution : newAttributions) { 315 uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs); 316 } 317 return null; 318 } 319 320 // Map uid -> chargeDelta. No initial capacity since many deltas might be 0. 321 final SparseLongArray uidChargeDeltas = new SparseLongArray(); 322 323 for (EnergyConsumerAttribution newAttribution : newAttributions) { 324 final int uid = newAttribution.uid; 325 final long newEnergyUJ = newAttribution.energyUWs; 326 // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy. 327 final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L); 328 uidOldEnergyMap.put(uid, newEnergyUJ); 329 330 // Everything is fully done being updated. We now calculate the delta for returning. 331 if (oldEnergyUJ < 0) continue; 332 if (newEnergyUJ == oldEnergyUJ) continue; 333 final long deltaUJ = newEnergyUJ - oldEnergyUJ; 334 if (deltaUJ < 0 || avgVoltageMV <= 0) { 335 Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ 336 + ") but old energy (" + oldEnergyUJ + "). Average voltage (" + avgVoltageMV 337 + ")Skipping. "); 338 continue; 339 } 340 341 final long deltaChargeUC = calculateChargeConsumedUC(deltaUJ, avgVoltageMV); 342 uidChargeDeltas.put(uid, deltaChargeUC); 343 } 344 return uidChargeDeltas; 345 } 346 347 /** Dump debug data. */ dump(PrintWriter pw)348 public void dump(PrintWriter pw) { 349 pw.println("Energy consumer snapshot"); 350 pw.println("List of EnergyConsumers:"); 351 for (int i = 0; i < mEnergyConsumers.size(); i++) { 352 final int id = mEnergyConsumers.keyAt(i); 353 final EnergyConsumer consumer = mEnergyConsumers.valueAt(i); 354 pw.println(String.format(" Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id, 355 consumer.id, consumer.ordinal, consumer.type, consumer.name)); 356 } 357 pw.println("Map of consumerIds to energy (in microjoules):"); 358 for (int i = 0; i < mEnergyConsumerSnapshots.size(); i++) { 359 final int id = mEnergyConsumerSnapshots.keyAt(i); 360 final long energyUJ = mEnergyConsumerSnapshots.valueAt(i); 361 final long voltageMV = mVoltageSnapshots.valueAt(i); 362 pw.println(String.format(" Consumer %d has energy %d uJ at %d mV", id, energyUJ, 363 voltageMV)); 364 } 365 pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:"); 366 pw.println(" " + mAttributionSnapshots); 367 pw.println(); 368 } 369 370 /** 371 * Returns the names of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the names of 372 * custom energy buckets supported by the device. 373 */ getOtherOrdinalNames()374 public String[] getOtherOrdinalNames() { 375 final String[] names = new String[mNumOtherOrdinals]; 376 int consumerIndex = 0; 377 final int size = mEnergyConsumers.size(); 378 for (int idx = 0; idx < size; idx++) { 379 final EnergyConsumer consumer = mEnergyConsumers.valueAt(idx); 380 if (consumer.type == (int) EnergyConsumerType.OTHER) { 381 names[consumerIndex++] = sanitizeCustomBucketName(consumer.name); 382 } 383 } 384 return names; 385 } 386 sanitizeCustomBucketName(String bucketName)387 private String sanitizeCustomBucketName(String bucketName) { 388 if (bucketName == null) { 389 return ""; 390 } 391 StringBuilder sb = new StringBuilder(bucketName.length()); 392 for (char c : bucketName.toCharArray()) { 393 if (Character.isWhitespace(c)) { 394 sb.append(' '); 395 } else if (Character.isISOControl(c)) { 396 sb.append('_'); 397 } else { 398 sb.append(c); 399 } 400 } 401 return sb.toString(); 402 } 403 404 /** Determines the number of ordinals for a given {@link EnergyConsumerType}. */ calculateNumOrdinals(@nergyConsumerType int type, SparseArray<EnergyConsumer> idToConsumer)405 private static int calculateNumOrdinals(@EnergyConsumerType int type, 406 SparseArray<EnergyConsumer> idToConsumer) { 407 if (idToConsumer == null) return 0; 408 int numOrdinals = 0; 409 final int size = idToConsumer.size(); 410 for (int idx = 0; idx < size; idx++) { 411 final EnergyConsumer consumer = idToConsumer.valueAt(idx); 412 if (consumer.type == type) numOrdinals++; 413 } 414 return numOrdinals; 415 } 416 417 /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */ calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV)418 private long calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV) { 419 // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times 420 // since the last snapshot. Round off to the nearest whole long. 421 return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV; 422 } 423 } 424