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