• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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