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