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 >= {@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