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