1 /*
2  * Copyright (C) 2023 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.internal.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.BatteryConsumer;
22 import android.os.BatteryStats;
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.PersistableBundle;
26 import android.os.UserHandle;
27 import android.util.IndentingPrintWriter;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 
31 import com.android.modules.utils.TypedXmlPullParser;
32 import com.android.modules.utils.TypedXmlSerializer;
33 
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 
45 /**
46  * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
47  * details.
48  */
49 @android.ravenwood.annotation.RavenwoodKeepWholeClass
50 public final class PowerStats {
51     private static final String TAG = "PowerStats";
52 
53     private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
54             new BatteryStatsHistory.VarintParceler();
55     private static final byte PARCEL_FORMAT_VERSION = 2;
56 
57     private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
58     private static final int PARCEL_FORMAT_VERSION_SHIFT =
59             Integer.numberOfTrailingZeros(PARCEL_FORMAT_VERSION_MASK);
60     private static final int STATS_ARRAY_LENGTH_MASK = 0x0000FF00;
61     private static final int STATS_ARRAY_LENGTH_SHIFT =
62             Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
63     public static final int MAX_STATS_ARRAY_LENGTH =
64             (1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
65     private static final int STATE_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
66     private static final int STATE_STATS_ARRAY_LENGTH_SHIFT =
67             Integer.numberOfTrailingZeros(STATE_STATS_ARRAY_LENGTH_MASK);
68     public static final int MAX_STATE_STATS_ARRAY_LENGTH =
69             (1 << Integer.bitCount(STATE_STATS_ARRAY_LENGTH_MASK)) - 1;
70     private static final int UID_STATS_ARRAY_LENGTH_MASK = 0xFF000000;
71     private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
72             Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
73     public static final int MAX_UID_STATS_ARRAY_LENGTH =
74             (1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
75 
76     /**
77      * Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
78      * This descriptor is used for storing PowerStats and can also be used by power models
79      * to adjust the algorithm in accordance with the stats available on the device.
80      */
81     @android.ravenwood.annotation.RavenwoodKeepWholeClass
82     public static class Descriptor {
83         public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
84         public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
85         public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
86 
87         public static final String XML_TAG_DESCRIPTOR = "descriptor";
88         private static final String XML_ATTR_ID = "id";
89         private static final String XML_ATTR_NAME = "name";
90         private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
91         private static final String XML_TAG_STATE = "state";
92         private static final String XML_ATTR_STATE_KEY = "key";
93         private static final String XML_ATTR_STATE_LABEL = "label";
94         private static final String XML_ATTR_STATE_STATS_ARRAY_LENGTH = "state-stats-array-length";
95         private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
96         private static final String XML_TAG_EXTRAS = "extras";
97 
98         /**
99          * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
100          * to; or a custom power component ID (if the value
101          * is &gt;= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
102          */
103         public final int powerComponentId;
104         public final String name;
105 
106         /**
107          * Stats for the power component, such as the total usage time.
108          */
109         public final int statsArrayLength;
110 
111         /**
112          * Map of device state codes to their corresponding human-readable labels.
113          */
114         public final SparseArray<String> stateLabels;
115 
116         /**
117          * Stats for a specific state of the power component, e.g. "mobile radio in the 5G mode"
118          */
119         public final int stateStatsArrayLength;
120 
121         /**
122          * Stats for the usage of this power component by a specific UID (app)
123          */
124         public final int uidStatsArrayLength;
125 
126         /**
127          * Extra parameters specific to the power component, e.g. the availability of power
128          * monitors.
129          */
130         public final PersistableBundle extras;
131 
132         private PowerStatsFormatter mDeviceStatsFormatter;
133         private PowerStatsFormatter mStateStatsFormatter;
134         private PowerStatsFormatter mUidStatsFormatter;
135 
Descriptor(@atteryConsumer.PowerComponent int powerComponentId, int statsArrayLength, @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras)136         public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
137                 int statsArrayLength, @Nullable SparseArray<String> stateLabels,
138                 int stateStatsArrayLength, int uidStatsArrayLength,
139                 @NonNull PersistableBundle extras) {
140             this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
141                     statsArrayLength, stateLabels, stateStatsArrayLength, uidStatsArrayLength,
142                     extras);
143         }
144 
Descriptor(int customPowerComponentId, String name, int statsArrayLength, @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras)145         public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
146                 @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
147                 int uidStatsArrayLength, @NonNull PersistableBundle extras) {
148             if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
149                 throw new IllegalArgumentException(
150                         "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
151             }
152             if (stateStatsArrayLength > MAX_STATE_STATS_ARRAY_LENGTH) {
153                 throw new IllegalArgumentException(
154                         "stateStatsArrayLength is too high. Max = " + MAX_STATE_STATS_ARRAY_LENGTH);
155             }
156             if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
157                 throw new IllegalArgumentException(
158                         "uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
159             }
160             this.powerComponentId = customPowerComponentId;
161             this.name = name;
162             this.statsArrayLength = statsArrayLength;
163             this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>();
164             this.stateStatsArrayLength = stateStatsArrayLength;
165             this.uidStatsArrayLength = uidStatsArrayLength;
166             this.extras = extras;
167         }
168 
169         /**
170          * Returns a custom formatter for this type of power stats.
171          */
getDeviceStatsFormatter()172         public PowerStatsFormatter getDeviceStatsFormatter() {
173             if (mDeviceStatsFormatter == null) {
174                 mDeviceStatsFormatter = new PowerStatsFormatter(
175                         extras.getString(EXTRA_DEVICE_STATS_FORMAT));
176             }
177             return mDeviceStatsFormatter;
178         }
179 
180         /**
181          * Returns a custom formatter for this type of power stats, specifically per-state stats.
182          */
getStateStatsFormatter()183         public PowerStatsFormatter getStateStatsFormatter() {
184             if (mStateStatsFormatter == null) {
185                 mStateStatsFormatter = new PowerStatsFormatter(
186                         extras.getString(EXTRA_STATE_STATS_FORMAT));
187             }
188             return mStateStatsFormatter;
189         }
190 
191         /**
192          * Returns a custom formatter for this type of power stats, specifically per-UID stats.
193          */
getUidStatsFormatter()194         public PowerStatsFormatter getUidStatsFormatter() {
195             if (mUidStatsFormatter == null) {
196                 mUidStatsFormatter = new PowerStatsFormatter(
197                         extras.getString(EXTRA_UID_STATS_FORMAT));
198             }
199             return mUidStatsFormatter;
200         }
201 
202         /**
203          * Returns the label associated with the give state key, e.g. "5G-high" for the
204          * state of Mobile Radio representing the 5G mode and high signal power.
205          */
getStateLabel(int key)206         public String getStateLabel(int key) {
207             String label = stateLabels.get(key);
208             if (label != null) {
209                 return label;
210             }
211             return name + "-" + Integer.toHexString(key);
212         }
213 
214         /**
215          * Writes the Descriptor into the parcel.
216          */
writeSummaryToParcel(Parcel parcel)217         public void writeSummaryToParcel(Parcel parcel) {
218             int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
219                              & PARCEL_FORMAT_VERSION_MASK)
220                             | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
221                                & STATS_ARRAY_LENGTH_MASK)
222                             | ((stateStatsArrayLength << STATE_STATS_ARRAY_LENGTH_SHIFT)
223                                & STATE_STATS_ARRAY_LENGTH_MASK)
224                             | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
225                                & UID_STATS_ARRAY_LENGTH_MASK);
226             parcel.writeInt(firstWord);
227             parcel.writeInt(powerComponentId);
228             parcel.writeString(name);
229             parcel.writeInt(stateLabels.size());
230             for (int i = 0, size = stateLabels.size(); i < size; i++) {
231                 parcel.writeInt(stateLabels.keyAt(i));
232                 parcel.writeString(stateLabels.valueAt(i));
233             }
234             extras.writeToParcel(parcel, 0);
235         }
236 
237         /**
238          * Reads a Descriptor from the parcel.  If the parcel has an incompatible format,
239          * returns null.
240          */
241         @Nullable
readSummaryFromParcel(Parcel parcel)242         public static Descriptor readSummaryFromParcel(Parcel parcel) {
243             int firstWord = parcel.readInt();
244             int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
245             if (version != PARCEL_FORMAT_VERSION) {
246                 Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
247                            + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
248                 return null;
249             }
250             int statsArrayLength =
251                     (firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
252             int stateStatsArrayLength =
253                     (firstWord & STATE_STATS_ARRAY_LENGTH_MASK) >>> STATE_STATS_ARRAY_LENGTH_SHIFT;
254             int uidStatsArrayLength =
255                     (firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
256             int powerComponentId = parcel.readInt();
257             String name = parcel.readString();
258             int stateLabelCount = parcel.readInt();
259             SparseArray<String> stateLabels = new SparseArray<>(stateLabelCount);
260             for (int i = stateLabelCount; i > 0; i--) {
261                 int key = parcel.readInt();
262                 String label = parcel.readString();
263                 stateLabels.put(key, label);
264             }
265             PersistableBundle extras = parcel.readPersistableBundle();
266             return new Descriptor(powerComponentId, name, statsArrayLength, stateLabels,
267                     stateStatsArrayLength, uidStatsArrayLength, extras);
268         }
269 
270         @Override
equals(Object o)271         public boolean equals(Object o) {
272             if (this == o) return true;
273             if (!(o instanceof Descriptor)) return false;
274             Descriptor that = (Descriptor) o;
275             return powerComponentId == that.powerComponentId
276                     && statsArrayLength == that.statsArrayLength
277                     && stateLabels.contentEquals(that.stateLabels)
278                     && stateStatsArrayLength == that.stateStatsArrayLength
279                     && uidStatsArrayLength == that.uidStatsArrayLength
280                     && Objects.equals(name, that.name)
281                     && extras.size() == that.extras.size()        // Unparcel the Parcel if not yet
282                     && Bundle.kindofEquals(extras,
283                     that.extras);  // Since the Parcel is now unparceled, do a deep comparison
284         }
285 
286         /**
287          * Stores contents in an XML doc.
288          */
writeXml(TypedXmlSerializer serializer)289         public void writeXml(TypedXmlSerializer serializer) throws IOException {
290             serializer.startTag(null, XML_TAG_DESCRIPTOR);
291             serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
292             serializer.attribute(null, XML_ATTR_NAME, name);
293             serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
294             serializer.attributeInt(null, XML_ATTR_STATE_STATS_ARRAY_LENGTH, stateStatsArrayLength);
295             serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
296             for (int i = stateLabels.size() - 1; i >= 0; i--) {
297                 serializer.startTag(null, XML_TAG_STATE);
298                 serializer.attributeInt(null, XML_ATTR_STATE_KEY, stateLabels.keyAt(i));
299                 serializer.attribute(null, XML_ATTR_STATE_LABEL, stateLabels.valueAt(i));
300                 serializer.endTag(null, XML_TAG_STATE);
301             }
302             try {
303                 serializer.startTag(null, XML_TAG_EXTRAS);
304                 extras.saveToXml(serializer);
305                 serializer.endTag(null, XML_TAG_EXTRAS);
306             } catch (XmlPullParserException e) {
307                 throw new IOException(e);
308             }
309             serializer.endTag(null, XML_TAG_DESCRIPTOR);
310         }
311 
312         /**
313          * Creates a Descriptor by parsing an XML doc.  The parser is expected to be positioned
314          * on or before the opening "descriptor" tag.
315          */
createFromXml(TypedXmlPullParser parser)316         public static Descriptor createFromXml(TypedXmlPullParser parser)
317                 throws XmlPullParserException, IOException {
318             int powerComponentId = -1;
319             String name = null;
320             int statsArrayLength = 0;
321             SparseArray<String> stateLabels = new SparseArray<>();
322             int stateStatsArrayLength = 0;
323             int uidStatsArrayLength = 0;
324             PersistableBundle extras = null;
325             int eventType = parser.getEventType();
326             while (eventType != XmlPullParser.END_DOCUMENT
327                    && !(eventType == XmlPullParser.END_TAG
328                         && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
329                 if (eventType == XmlPullParser.START_TAG) {
330                     switch (parser.getName()) {
331                         case XML_TAG_DESCRIPTOR:
332                             powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
333                             name = parser.getAttributeValue(null, XML_ATTR_NAME);
334                             statsArrayLength = parser.getAttributeInt(null,
335                                     XML_ATTR_STATS_ARRAY_LENGTH);
336                             stateStatsArrayLength = parser.getAttributeInt(null,
337                                     XML_ATTR_STATE_STATS_ARRAY_LENGTH);
338                             uidStatsArrayLength = parser.getAttributeInt(null,
339                                     XML_ATTR_UID_STATS_ARRAY_LENGTH);
340                             break;
341                         case XML_TAG_STATE:
342                             int value = parser.getAttributeInt(null, XML_ATTR_STATE_KEY);
343                             String label = parser.getAttributeValue(null, XML_ATTR_STATE_LABEL);
344                             stateLabels.put(value, label);
345                             break;
346                         case XML_TAG_EXTRAS:
347                             extras = PersistableBundle.restoreFromXml(parser);
348                             break;
349                     }
350                 }
351                 eventType = parser.next();
352             }
353             if (powerComponentId == -1) {
354                 return null;
355             } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
356                 return new Descriptor(powerComponentId, name, statsArrayLength,
357                         stateLabels, stateStatsArrayLength, uidStatsArrayLength, extras);
358             } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
359                 return new Descriptor(powerComponentId, statsArrayLength, stateLabels,
360                         stateStatsArrayLength, uidStatsArrayLength, extras);
361             } else {
362                 Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
363                 return null;
364             }
365         }
366 
367         @Override
hashCode()368         public int hashCode() {
369             return Objects.hash(powerComponentId);
370         }
371 
372         @Override
toString()373         public String toString() {
374             if (extras != null) {
375                 extras.size();  // Unparcel
376             }
377             return "PowerStats.Descriptor{"
378                     + "powerComponentId=" + powerComponentId
379                     + ", name='" + name + '\''
380                     + ", statsArrayLength=" + statsArrayLength
381                     + ", stateStatsArrayLength=" + stateStatsArrayLength
382                     + ", stateLabels=" + stateLabels
383                     + ", uidStatsArrayLength=" + uidStatsArrayLength
384                     + ", extras=" + extras
385                     + '}';
386         }
387     }
388 
389     /**
390      * A registry for all supported power component types (e.g. CPU, WiFi).
391      */
392     public static class DescriptorRegistry {
393         private final SparseArray<Descriptor> mDescriptors = new SparseArray<>();
394 
395         /**
396          * Adds the specified descriptor to the registry. If the registry already
397          * contained a descriptor for the same power component, then the new one replaces
398          * the old one.
399          */
register(Descriptor descriptor)400         public void register(Descriptor descriptor) {
401             mDescriptors.put(descriptor.powerComponentId, descriptor);
402         }
403 
404         /**
405          * @param powerComponentId either a BatteryConsumer.PowerComponent or a custom power
406          *                         component ID
407          */
get(int powerComponentId)408         public Descriptor get(int powerComponentId) {
409             return mDescriptors.get(powerComponentId);
410         }
411     }
412 
413     public final Descriptor descriptor;
414 
415     /**
416      * Duration, in milliseconds, covered by this snapshot.
417      */
418     public long durationMs;
419 
420     /**
421      * Device-wide stats.
422      */
423     public long[] stats;
424 
425     /**
426      * Device-wide mode stats, used when the power component can operate in different modes,
427      * e.g. RATs such as LTE and 5G.
428      */
429     public final SparseArray<long[]> stateStats = new SparseArray<>();
430 
431     /**
432      * Per-UID CPU stats.
433      */
434     public final SparseArray<long[]> uidStats = new SparseArray<>();
435 
PowerStats(Descriptor descriptor)436     public PowerStats(Descriptor descriptor) {
437         this.descriptor = descriptor;
438         stats = new long[descriptor.statsArrayLength];
439     }
440 
441     /**
442      * Writes the object into the parcel.
443      */
writeToParcel(Parcel parcel)444     public void writeToParcel(Parcel parcel) {
445         int lengthPos = parcel.dataPosition();
446         parcel.writeInt(0);     // Placeholder for length
447 
448         int startPos = parcel.dataPosition();
449         parcel.writeInt(descriptor.powerComponentId);
450         parcel.writeLong(durationMs);
451         VARINT_PARCELER.writeLongArray(parcel, stats);
452 
453         if (descriptor.stateStatsArrayLength != 0) {
454             parcel.writeInt(stateStats.size());
455             for (int i = 0; i < stateStats.size(); i++) {
456                 parcel.writeInt(stateStats.keyAt(i));
457                 VARINT_PARCELER.writeLongArray(parcel, stateStats.valueAt(i));
458             }
459         }
460 
461         parcel.writeInt(uidStats.size());
462         for (int i = 0; i < uidStats.size(); i++) {
463             parcel.writeInt(uidStats.keyAt(i));
464             VARINT_PARCELER.writeLongArray(parcel, uidStats.valueAt(i));
465         }
466 
467         int endPos = parcel.dataPosition();
468         parcel.setDataPosition(lengthPos);
469         parcel.writeInt(endPos - startPos);
470         parcel.setDataPosition(endPos);
471     }
472 
473     /**
474      * Reads a PowerStats object from the supplied Parcel. If the parcel has an incompatible
475      * format, returns null.
476      */
477     @Nullable
readFromParcel(Parcel parcel, DescriptorRegistry registry)478     public static PowerStats readFromParcel(Parcel parcel, DescriptorRegistry registry) {
479         int length = parcel.readInt();
480         int startPos = parcel.dataPosition();
481         int endPos = startPos + length;
482 
483         try {
484             int powerComponentId = parcel.readInt();
485 
486             Descriptor descriptor = registry.get(powerComponentId);
487             if (descriptor == null) {
488                 Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
489                 return null;
490             }
491             PowerStats stats = new PowerStats(descriptor);
492             stats.durationMs = parcel.readLong();
493             stats.stats = new long[descriptor.statsArrayLength];
494             VARINT_PARCELER.readLongArray(parcel, stats.stats);
495 
496             if (descriptor.stateStatsArrayLength != 0) {
497                 int count = parcel.readInt();
498                 for (int i = 0; i < count; i++) {
499                     int state = parcel.readInt();
500                     long[] stateStats = new long[descriptor.stateStatsArrayLength];
501                     VARINT_PARCELER.readLongArray(parcel, stateStats);
502                     stats.stateStats.put(state, stateStats);
503                 }
504             }
505 
506             int uidCount = parcel.readInt();
507             for (int i = 0; i < uidCount; i++) {
508                 int uid = parcel.readInt();
509                 long[] uidStats = new long[descriptor.uidStatsArrayLength];
510                 VARINT_PARCELER.readLongArray(parcel, uidStats);
511                 stats.uidStats.put(uid, uidStats);
512             }
513             if (parcel.dataPosition() != endPos) {
514                 Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
515                            + ", actual length: " + (parcel.dataPosition() - startPos));
516                 return null;
517             }
518             return stats;
519         } finally {
520             // Unconditionally skip to the end of the written data, even if the actual parcel
521             // format is incompatible
522             if (endPos > parcel.dataPosition()) {
523                 if (endPos >= parcel.dataSize()) {
524                     throw new IndexOutOfBoundsException(
525                             "PowerStats end position: " + endPos + " is outside the parcel bounds: "
526                                     + parcel.dataSize());
527                 }
528                 parcel.setDataPosition(endPos);
529             }
530         }
531     }
532 
533     /**
534      * Formats the stats as a string suitable to be included in the Battery History dump.
535      */
formatForBatteryHistory(String uidPrefix)536     public String formatForBatteryHistory(String uidPrefix) {
537         StringBuilder sb = new StringBuilder();
538         sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
539         if (stats.length > 0) {
540             sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
541         }
542         if (descriptor.stateStatsArrayLength != 0) {
543             PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
544             for (int i = 0; i < stateStats.size(); i++) {
545                 sb.append(" (");
546                 sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
547                 sb.append(") ");
548                 sb.append(formatter.format(stateStats.valueAt(i)));
549             }
550         }
551         PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
552         for (int i = 0; i < uidStats.size(); i++) {
553             sb.append(uidPrefix)
554                     .append(UserHandle.formatUid(uidStats.keyAt(i)))
555                     .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
556         }
557         return sb.toString();
558     }
559 
560     /**
561      * Prints the contents of the stats snapshot.
562      */
dump(IndentingPrintWriter pw)563     public void dump(IndentingPrintWriter pw) {
564         pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
565         pw.increaseIndent();
566         pw.print("duration", durationMs).println();
567 
568         if (descriptor.statsArrayLength != 0) {
569             pw.println(descriptor.getDeviceStatsFormatter().format(stats));
570         }
571         if (descriptor.stateStatsArrayLength != 0) {
572             PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
573             for (int i = 0; i < stateStats.size(); i++) {
574                 pw.print(" (");
575                 pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
576                 pw.print(") ");
577                 pw.print(formatter.format(stateStats.valueAt(i)));
578                 pw.println();
579             }
580         }
581         PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
582         for (int i = 0; i < uidStats.size(); i++) {
583             pw.print("UID ");
584             pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
585             pw.print(": ");
586             pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
587             pw.println();
588         }
589         pw.decreaseIndent();
590     }
591 
592     @Override
toString()593     public String toString() {
594         return "PowerStats: " + formatForBatteryHistory(" UID ");
595     }
596 
597     public static class PowerStatsFormatter {
598         private static class Section {
599             public String label;
600             public int position;
601             public int length;
602             public boolean optional;
603             public boolean typePower;
604         }
605 
606         private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
607         private static final Pattern SECTION_PATTERN =
608                 Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
609         private final List<Section> mSections;
610 
PowerStatsFormatter(String format)611         public PowerStatsFormatter(String format) {
612             mSections = parseFormat(format);
613         }
614 
615         /**
616          * Produces a formatted string representing the supplied array, with labels
617          * and other adornments specific to the power stats layout.
618          */
format(long[] stats)619         public String format(long[] stats) {
620             return format(mSections, stats);
621         }
622 
parseFormat(String format)623         private List<Section> parseFormat(String format) {
624             if (format == null || format.isBlank()) {
625                 return null;
626             }
627 
628             ArrayList<Section> sections = new ArrayList<>();
629             Matcher matcher = SECTION_PATTERN.matcher(format);
630             for (int position = 0; position < format.length(); position = matcher.end()) {
631                 if (!matcher.find() || matcher.start() != position) {
632                     Slog.wtf(TAG, "Bad power stats format '" + format + "'");
633                     return null;
634                 }
635                 Section section = new Section();
636                 section.label = matcher.group(1);
637                 section.position = Integer.parseUnsignedInt(matcher.group(2));
638                 String length = matcher.group("L");
639                 if (length != null) {
640                     section.length = Integer.parseUnsignedInt(length);
641                 } else {
642                     section.length = 1;
643                 }
644                 String flags = matcher.group("F");
645                 if (flags != null) {
646                     for (int i = 0; i < flags.length(); i++) {
647                         char flag = flags.charAt(i);
648                         switch (flag) {
649                             case '?':
650                                 section.optional = true;
651                                 break;
652                             case 'p':
653                                 section.typePower = true;
654                                 break;
655                             default:
656                                 Slog.e(TAG,
657                                         "Unsupported format option '" + flag + "' in " + format);
658                                 break;
659                         }
660                     }
661                 }
662                 sections.add(section);
663             }
664 
665             return sections;
666         }
667 
format(List<Section> sections, long[] stats)668         private String format(List<Section> sections, long[] stats) {
669             if (sections == null) {
670                 return Arrays.toString(stats);
671             }
672 
673             StringBuilder sb = new StringBuilder();
674             for (int i = 0, count = sections.size(); i < count; i++) {
675                 Section section = sections.get(i);
676                 if (section.length == 0) {
677                     continue;
678                 }
679 
680                 if (section.optional) {
681                     boolean nonZero = false;
682                     for (int offset = 0; offset < section.length; offset++) {
683                         if (stats[section.position + offset] != 0) {
684                             nonZero = true;
685                             break;
686                         }
687                     }
688                     if (!nonZero) {
689                         continue;
690                     }
691                 }
692 
693                 if (!sb.isEmpty()) {
694                     sb.append(' ');
695                 }
696                 sb.append(section.label).append(": ");
697                 if (section.length != 1) {
698                     sb.append('[');
699                 }
700                 for (int offset = 0; offset < section.length; offset++) {
701                     if (offset != 0) {
702                         sb.append(", ");
703                     }
704                     if (section.typePower) {
705                         sb.append(BatteryStats.formatCharge(
706                                 stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
707                     } else {
708                         sb.append(stats[section.position + offset]);
709                     }
710                 }
711                 if (section.length != 1) {
712                     sb.append(']');
713                 }
714             }
715             return sb.toString();
716         }
717     }
718 }
719