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