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