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