719 lines
30 KiB
Java
719 lines
30 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2023 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package com.android.internal.os;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.os.BatteryConsumer;
|
||
|
import android.os.BatteryStats;
|
||
|
import android.os.Bundle;
|
||
|
import android.os.Parcel;
|
||
|
import android.os.PersistableBundle;
|
||
|
import android.os.UserHandle;
|
||
|
import android.util.IndentingPrintWriter;
|
||
|
import android.util.Slog;
|
||
|
import android.util.SparseArray;
|
||
|
|
||
|
import com.android.modules.utils.TypedXmlPullParser;
|
||
|
import com.android.modules.utils.TypedXmlSerializer;
|
||
|
|
||
|
import org.xmlpull.v1.XmlPullParser;
|
||
|
import org.xmlpull.v1.XmlPullParserException;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
import java.util.regex.Matcher;
|
||
|
import java.util.regex.Pattern;
|
||
|
|
||
|
/**
|
||
|
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
|
||
|
* details.
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
public final class PowerStats {
|
||
|
private static final String TAG = "PowerStats";
|
||
|
|
||
|
private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
|
||
|
new BatteryStatsHistory.VarintParceler();
|
||
|
private static final byte PARCEL_FORMAT_VERSION = 2;
|
||
|
|
||
|
private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
|
||
|
private static final int PARCEL_FORMAT_VERSION_SHIFT =
|
||
|
Integer.numberOfTrailingZeros(PARCEL_FORMAT_VERSION_MASK);
|
||
|
private static final int STATS_ARRAY_LENGTH_MASK = 0x0000FF00;
|
||
|
private static final int STATS_ARRAY_LENGTH_SHIFT =
|
||
|
Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
|
||
|
public static final int MAX_STATS_ARRAY_LENGTH =
|
||
|
(1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
|
||
|
private static final int STATE_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
|
||
|
private static final int STATE_STATS_ARRAY_LENGTH_SHIFT =
|
||
|
Integer.numberOfTrailingZeros(STATE_STATS_ARRAY_LENGTH_MASK);
|
||
|
public static final int MAX_STATE_STATS_ARRAY_LENGTH =
|
||
|
(1 << Integer.bitCount(STATE_STATS_ARRAY_LENGTH_MASK)) - 1;
|
||
|
private static final int UID_STATS_ARRAY_LENGTH_MASK = 0xFF000000;
|
||
|
private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
|
||
|
Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
|
||
|
public static final int MAX_UID_STATS_ARRAY_LENGTH =
|
||
|
(1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
|
||
|
|
||
|
/**
|
||
|
* Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
|
||
|
* This descriptor is used for storing PowerStats and can also be used by power models
|
||
|
* to adjust the algorithm in accordance with the stats available on the device.
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
public static class Descriptor {
|
||
|
public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
|
||
|
public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
|
||
|
public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
|
||
|
|
||
|
public static final String XML_TAG_DESCRIPTOR = "descriptor";
|
||
|
private static final String XML_ATTR_ID = "id";
|
||
|
private static final String XML_ATTR_NAME = "name";
|
||
|
private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
|
||
|
private static final String XML_TAG_STATE = "state";
|
||
|
private static final String XML_ATTR_STATE_KEY = "key";
|
||
|
private static final String XML_ATTR_STATE_LABEL = "label";
|
||
|
private static final String XML_ATTR_STATE_STATS_ARRAY_LENGTH = "state-stats-array-length";
|
||
|
private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
|
||
|
private static final String XML_TAG_EXTRAS = "extras";
|
||
|
|
||
|
/**
|
||
|
* {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
|
||
|
* to; or a custom power component ID (if the value
|
||
|
* is >= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
|
||
|
*/
|
||
|
public final int powerComponentId;
|
||
|
public final String name;
|
||
|
|
||
|
/**
|
||
|
* Stats for the power component, such as the total usage time.
|
||
|
*/
|
||
|
public final int statsArrayLength;
|
||
|
|
||
|
/**
|
||
|
* Map of device state codes to their corresponding human-readable labels.
|
||
|
*/
|
||
|
public final SparseArray<String> stateLabels;
|
||
|
|
||
|
/**
|
||
|
* Stats for a specific state of the power component, e.g. "mobile radio in the 5G mode"
|
||
|
*/
|
||
|
public final int stateStatsArrayLength;
|
||
|
|
||
|
/**
|
||
|
* Stats for the usage of this power component by a specific UID (app)
|
||
|
*/
|
||
|
public final int uidStatsArrayLength;
|
||
|
|
||
|
/**
|
||
|
* Extra parameters specific to the power component, e.g. the availability of power
|
||
|
* monitors.
|
||
|
*/
|
||
|
public final PersistableBundle extras;
|
||
|
|
||
|
private PowerStatsFormatter mDeviceStatsFormatter;
|
||
|
private PowerStatsFormatter mStateStatsFormatter;
|
||
|
private PowerStatsFormatter mUidStatsFormatter;
|
||
|
|
||
|
public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
|
||
|
int statsArrayLength, @Nullable SparseArray<String> stateLabels,
|
||
|
int stateStatsArrayLength, int uidStatsArrayLength,
|
||
|
@NonNull PersistableBundle extras) {
|
||
|
this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
|
||
|
statsArrayLength, stateLabels, stateStatsArrayLength, uidStatsArrayLength,
|
||
|
extras);
|
||
|
}
|
||
|
|
||
|
public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
|
||
|
@Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
|
||
|
int uidStatsArrayLength, @NonNull PersistableBundle extras) {
|
||
|
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
|
||
|
}
|
||
|
if (stateStatsArrayLength > MAX_STATE_STATS_ARRAY_LENGTH) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"stateStatsArrayLength is too high. Max = " + MAX_STATE_STATS_ARRAY_LENGTH);
|
||
|
}
|
||
|
if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
|
||
|
}
|
||
|
this.powerComponentId = customPowerComponentId;
|
||
|
this.name = name;
|
||
|
this.statsArrayLength = statsArrayLength;
|
||
|
this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>();
|
||
|
this.stateStatsArrayLength = stateStatsArrayLength;
|
||
|
this.uidStatsArrayLength = uidStatsArrayLength;
|
||
|
this.extras = extras;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a custom formatter for this type of power stats.
|
||
|
*/
|
||
|
public PowerStatsFormatter getDeviceStatsFormatter() {
|
||
|
if (mDeviceStatsFormatter == null) {
|
||
|
mDeviceStatsFormatter = new PowerStatsFormatter(
|
||
|
extras.getString(EXTRA_DEVICE_STATS_FORMAT));
|
||
|
}
|
||
|
return mDeviceStatsFormatter;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a custom formatter for this type of power stats, specifically per-state stats.
|
||
|
*/
|
||
|
public PowerStatsFormatter getStateStatsFormatter() {
|
||
|
if (mStateStatsFormatter == null) {
|
||
|
mStateStatsFormatter = new PowerStatsFormatter(
|
||
|
extras.getString(EXTRA_STATE_STATS_FORMAT));
|
||
|
}
|
||
|
return mStateStatsFormatter;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a custom formatter for this type of power stats, specifically per-UID stats.
|
||
|
*/
|
||
|
public PowerStatsFormatter getUidStatsFormatter() {
|
||
|
if (mUidStatsFormatter == null) {
|
||
|
mUidStatsFormatter = new PowerStatsFormatter(
|
||
|
extras.getString(EXTRA_UID_STATS_FORMAT));
|
||
|
}
|
||
|
return mUidStatsFormatter;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the label associated with the give state key, e.g. "5G-high" for the
|
||
|
* state of Mobile Radio representing the 5G mode and high signal power.
|
||
|
*/
|
||
|
public String getStateLabel(int key) {
|
||
|
String label = stateLabels.get(key);
|
||
|
if (label != null) {
|
||
|
return label;
|
||
|
}
|
||
|
return name + "-" + Integer.toHexString(key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes the Descriptor into the parcel.
|
||
|
*/
|
||
|
public void writeSummaryToParcel(Parcel parcel) {
|
||
|
int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
|
||
|
& PARCEL_FORMAT_VERSION_MASK)
|
||
|
| ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
|
||
|
& STATS_ARRAY_LENGTH_MASK)
|
||
|
| ((stateStatsArrayLength << STATE_STATS_ARRAY_LENGTH_SHIFT)
|
||
|
& STATE_STATS_ARRAY_LENGTH_MASK)
|
||
|
| ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
|
||
|
& UID_STATS_ARRAY_LENGTH_MASK);
|
||
|
parcel.writeInt(firstWord);
|
||
|
parcel.writeInt(powerComponentId);
|
||
|
parcel.writeString(name);
|
||
|
parcel.writeInt(stateLabels.size());
|
||
|
for (int i = 0, size = stateLabels.size(); i < size; i++) {
|
||
|
parcel.writeInt(stateLabels.keyAt(i));
|
||
|
parcel.writeString(stateLabels.valueAt(i));
|
||
|
}
|
||
|
extras.writeToParcel(parcel, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads a Descriptor from the parcel. If the parcel has an incompatible format,
|
||
|
* returns null.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public static Descriptor readSummaryFromParcel(Parcel parcel) {
|
||
|
int firstWord = parcel.readInt();
|
||
|
int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
|
||
|
if (version != PARCEL_FORMAT_VERSION) {
|
||
|
Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
|
||
|
+ "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
|
||
|
return null;
|
||
|
}
|
||
|
int statsArrayLength =
|
||
|
(firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
|
||
|
int stateStatsArrayLength =
|
||
|
(firstWord & STATE_STATS_ARRAY_LENGTH_MASK) >>> STATE_STATS_ARRAY_LENGTH_SHIFT;
|
||
|
int uidStatsArrayLength =
|
||
|
(firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
|
||
|
int powerComponentId = parcel.readInt();
|
||
|
String name = parcel.readString();
|
||
|
int stateLabelCount = parcel.readInt();
|
||
|
SparseArray<String> stateLabels = new SparseArray<>(stateLabelCount);
|
||
|
for (int i = stateLabelCount; i > 0; i--) {
|
||
|
int key = parcel.readInt();
|
||
|
String label = parcel.readString();
|
||
|
stateLabels.put(key, label);
|
||
|
}
|
||
|
PersistableBundle extras = parcel.readPersistableBundle();
|
||
|
return new Descriptor(powerComponentId, name, statsArrayLength, stateLabels,
|
||
|
stateStatsArrayLength, uidStatsArrayLength, extras);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(Object o) {
|
||
|
if (this == o) return true;
|
||
|
if (!(o instanceof Descriptor)) return false;
|
||
|
Descriptor that = (Descriptor) o;
|
||
|
return powerComponentId == that.powerComponentId
|
||
|
&& statsArrayLength == that.statsArrayLength
|
||
|
&& stateLabels.contentEquals(that.stateLabels)
|
||
|
&& stateStatsArrayLength == that.stateStatsArrayLength
|
||
|
&& uidStatsArrayLength == that.uidStatsArrayLength
|
||
|
&& Objects.equals(name, that.name)
|
||
|
&& extras.size() == that.extras.size() // Unparcel the Parcel if not yet
|
||
|
&& Bundle.kindofEquals(extras,
|
||
|
that.extras); // Since the Parcel is now unparceled, do a deep comparison
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stores contents in an XML doc.
|
||
|
*/
|
||
|
public void writeXml(TypedXmlSerializer serializer) throws IOException {
|
||
|
serializer.startTag(null, XML_TAG_DESCRIPTOR);
|
||
|
serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
|
||
|
serializer.attribute(null, XML_ATTR_NAME, name);
|
||
|
serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
|
||
|
serializer.attributeInt(null, XML_ATTR_STATE_STATS_ARRAY_LENGTH, stateStatsArrayLength);
|
||
|
serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
|
||
|
for (int i = stateLabels.size() - 1; i >= 0; i--) {
|
||
|
serializer.startTag(null, XML_TAG_STATE);
|
||
|
serializer.attributeInt(null, XML_ATTR_STATE_KEY, stateLabels.keyAt(i));
|
||
|
serializer.attribute(null, XML_ATTR_STATE_LABEL, stateLabels.valueAt(i));
|
||
|
serializer.endTag(null, XML_TAG_STATE);
|
||
|
}
|
||
|
try {
|
||
|
serializer.startTag(null, XML_TAG_EXTRAS);
|
||
|
extras.saveToXml(serializer);
|
||
|
serializer.endTag(null, XML_TAG_EXTRAS);
|
||
|
} catch (XmlPullParserException e) {
|
||
|
throw new IOException(e);
|
||
|
}
|
||
|
serializer.endTag(null, XML_TAG_DESCRIPTOR);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned
|
||
|
* on or before the opening "descriptor" tag.
|
||
|
*/
|
||
|
public static Descriptor createFromXml(TypedXmlPullParser parser)
|
||
|
throws XmlPullParserException, IOException {
|
||
|
int powerComponentId = -1;
|
||
|
String name = null;
|
||
|
int statsArrayLength = 0;
|
||
|
SparseArray<String> stateLabels = new SparseArray<>();
|
||
|
int stateStatsArrayLength = 0;
|
||
|
int uidStatsArrayLength = 0;
|
||
|
PersistableBundle extras = null;
|
||
|
int eventType = parser.getEventType();
|
||
|
while (eventType != XmlPullParser.END_DOCUMENT
|
||
|
&& !(eventType == XmlPullParser.END_TAG
|
||
|
&& parser.getName().equals(XML_TAG_DESCRIPTOR))) {
|
||
|
if (eventType == XmlPullParser.START_TAG) {
|
||
|
switch (parser.getName()) {
|
||
|
case XML_TAG_DESCRIPTOR:
|
||
|
powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
|
||
|
name = parser.getAttributeValue(null, XML_ATTR_NAME);
|
||
|
statsArrayLength = parser.getAttributeInt(null,
|
||
|
XML_ATTR_STATS_ARRAY_LENGTH);
|
||
|
stateStatsArrayLength = parser.getAttributeInt(null,
|
||
|
XML_ATTR_STATE_STATS_ARRAY_LENGTH);
|
||
|
uidStatsArrayLength = parser.getAttributeInt(null,
|
||
|
XML_ATTR_UID_STATS_ARRAY_LENGTH);
|
||
|
break;
|
||
|
case XML_TAG_STATE:
|
||
|
int value = parser.getAttributeInt(null, XML_ATTR_STATE_KEY);
|
||
|
String label = parser.getAttributeValue(null, XML_ATTR_STATE_LABEL);
|
||
|
stateLabels.put(value, label);
|
||
|
break;
|
||
|
case XML_TAG_EXTRAS:
|
||
|
extras = PersistableBundle.restoreFromXml(parser);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
eventType = parser.next();
|
||
|
}
|
||
|
if (powerComponentId == -1) {
|
||
|
return null;
|
||
|
} else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
|
||
|
return new Descriptor(powerComponentId, name, statsArrayLength,
|
||
|
stateLabels, stateStatsArrayLength, uidStatsArrayLength, extras);
|
||
|
} else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
|
||
|
return new Descriptor(powerComponentId, statsArrayLength, stateLabels,
|
||
|
stateStatsArrayLength, uidStatsArrayLength, extras);
|
||
|
} else {
|
||
|
Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return Objects.hash(powerComponentId);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
if (extras != null) {
|
||
|
extras.size(); // Unparcel
|
||
|
}
|
||
|
return "PowerStats.Descriptor{"
|
||
|
+ "powerComponentId=" + powerComponentId
|
||
|
+ ", name='" + name + '\''
|
||
|
+ ", statsArrayLength=" + statsArrayLength
|
||
|
+ ", stateStatsArrayLength=" + stateStatsArrayLength
|
||
|
+ ", stateLabels=" + stateLabels
|
||
|
+ ", uidStatsArrayLength=" + uidStatsArrayLength
|
||
|
+ ", extras=" + extras
|
||
|
+ '}';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A registry for all supported power component types (e.g. CPU, WiFi).
|
||
|
*/
|
||
|
public static class DescriptorRegistry {
|
||
|
private final SparseArray<Descriptor> mDescriptors = new SparseArray<>();
|
||
|
|
||
|
/**
|
||
|
* Adds the specified descriptor to the registry. If the registry already
|
||
|
* contained a descriptor for the same power component, then the new one replaces
|
||
|
* the old one.
|
||
|
*/
|
||
|
public void register(Descriptor descriptor) {
|
||
|
mDescriptors.put(descriptor.powerComponentId, descriptor);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param powerComponentId either a BatteryConsumer.PowerComponent or a custom power
|
||
|
* component ID
|
||
|
*/
|
||
|
public Descriptor get(int powerComponentId) {
|
||
|
return mDescriptors.get(powerComponentId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public final Descriptor descriptor;
|
||
|
|
||
|
/**
|
||
|
* Duration, in milliseconds, covered by this snapshot.
|
||
|
*/
|
||
|
public long durationMs;
|
||
|
|
||
|
/**
|
||
|
* Device-wide stats.
|
||
|
*/
|
||
|
public long[] stats;
|
||
|
|
||
|
/**
|
||
|
* Device-wide mode stats, used when the power component can operate in different modes,
|
||
|
* e.g. RATs such as LTE and 5G.
|
||
|
*/
|
||
|
public final SparseArray<long[]> stateStats = new SparseArray<>();
|
||
|
|
||
|
/**
|
||
|
* Per-UID CPU stats.
|
||
|
*/
|
||
|
public final SparseArray<long[]> uidStats = new SparseArray<>();
|
||
|
|
||
|
public PowerStats(Descriptor descriptor) {
|
||
|
this.descriptor = descriptor;
|
||
|
stats = new long[descriptor.statsArrayLength];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes the object into the parcel.
|
||
|
*/
|
||
|
public void writeToParcel(Parcel parcel) {
|
||
|
int lengthPos = parcel.dataPosition();
|
||
|
parcel.writeInt(0); // Placeholder for length
|
||
|
|
||
|
int startPos = parcel.dataPosition();
|
||
|
parcel.writeInt(descriptor.powerComponentId);
|
||
|
parcel.writeLong(durationMs);
|
||
|
VARINT_PARCELER.writeLongArray(parcel, stats);
|
||
|
|
||
|
if (descriptor.stateStatsArrayLength != 0) {
|
||
|
parcel.writeInt(stateStats.size());
|
||
|
for (int i = 0; i < stateStats.size(); i++) {
|
||
|
parcel.writeInt(stateStats.keyAt(i));
|
||
|
VARINT_PARCELER.writeLongArray(parcel, stateStats.valueAt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
parcel.writeInt(uidStats.size());
|
||
|
for (int i = 0; i < uidStats.size(); i++) {
|
||
|
parcel.writeInt(uidStats.keyAt(i));
|
||
|
VARINT_PARCELER.writeLongArray(parcel, uidStats.valueAt(i));
|
||
|
}
|
||
|
|
||
|
int endPos = parcel.dataPosition();
|
||
|
parcel.setDataPosition(lengthPos);
|
||
|
parcel.writeInt(endPos - startPos);
|
||
|
parcel.setDataPosition(endPos);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads a PowerStats object from the supplied Parcel. If the parcel has an incompatible
|
||
|
* format, returns null.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public static PowerStats readFromParcel(Parcel parcel, DescriptorRegistry registry) {
|
||
|
int length = parcel.readInt();
|
||
|
int startPos = parcel.dataPosition();
|
||
|
int endPos = startPos + length;
|
||
|
|
||
|
try {
|
||
|
int powerComponentId = parcel.readInt();
|
||
|
|
||
|
Descriptor descriptor = registry.get(powerComponentId);
|
||
|
if (descriptor == null) {
|
||
|
Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
|
||
|
return null;
|
||
|
}
|
||
|
PowerStats stats = new PowerStats(descriptor);
|
||
|
stats.durationMs = parcel.readLong();
|
||
|
stats.stats = new long[descriptor.statsArrayLength];
|
||
|
VARINT_PARCELER.readLongArray(parcel, stats.stats);
|
||
|
|
||
|
if (descriptor.stateStatsArrayLength != 0) {
|
||
|
int count = parcel.readInt();
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
int state = parcel.readInt();
|
||
|
long[] stateStats = new long[descriptor.stateStatsArrayLength];
|
||
|
VARINT_PARCELER.readLongArray(parcel, stateStats);
|
||
|
stats.stateStats.put(state, stateStats);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int uidCount = parcel.readInt();
|
||
|
for (int i = 0; i < uidCount; i++) {
|
||
|
int uid = parcel.readInt();
|
||
|
long[] uidStats = new long[descriptor.uidStatsArrayLength];
|
||
|
VARINT_PARCELER.readLongArray(parcel, uidStats);
|
||
|
stats.uidStats.put(uid, uidStats);
|
||
|
}
|
||
|
if (parcel.dataPosition() != endPos) {
|
||
|
Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
|
||
|
+ ", actual length: " + (parcel.dataPosition() - startPos));
|
||
|
return null;
|
||
|
}
|
||
|
return stats;
|
||
|
} finally {
|
||
|
// Unconditionally skip to the end of the written data, even if the actual parcel
|
||
|
// format is incompatible
|
||
|
if (endPos > parcel.dataPosition()) {
|
||
|
if (endPos >= parcel.dataSize()) {
|
||
|
throw new IndexOutOfBoundsException(
|
||
|
"PowerStats end position: " + endPos + " is outside the parcel bounds: "
|
||
|
+ parcel.dataSize());
|
||
|
}
|
||
|
parcel.setDataPosition(endPos);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Formats the stats as a string suitable to be included in the Battery History dump.
|
||
|
*/
|
||
|
public String formatForBatteryHistory(String uidPrefix) {
|
||
|
StringBuilder sb = new StringBuilder();
|
||
|
sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
|
||
|
if (stats.length > 0) {
|
||
|
sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
|
||
|
}
|
||
|
if (descriptor.stateStatsArrayLength != 0) {
|
||
|
PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
|
||
|
for (int i = 0; i < stateStats.size(); i++) {
|
||
|
sb.append(" (");
|
||
|
sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
|
||
|
sb.append(") ");
|
||
|
sb.append(formatter.format(stateStats.valueAt(i)));
|
||
|
}
|
||
|
}
|
||
|
PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
|
||
|
for (int i = 0; i < uidStats.size(); i++) {
|
||
|
sb.append(uidPrefix)
|
||
|
.append(UserHandle.formatUid(uidStats.keyAt(i)))
|
||
|
.append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
|
||
|
}
|
||
|
return sb.toString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prints the contents of the stats snapshot.
|
||
|
*/
|
||
|
public void dump(IndentingPrintWriter pw) {
|
||
|
pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
|
||
|
pw.increaseIndent();
|
||
|
pw.print("duration", durationMs).println();
|
||
|
|
||
|
if (descriptor.statsArrayLength != 0) {
|
||
|
pw.println(descriptor.getDeviceStatsFormatter().format(stats));
|
||
|
}
|
||
|
if (descriptor.stateStatsArrayLength != 0) {
|
||
|
PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
|
||
|
for (int i = 0; i < stateStats.size(); i++) {
|
||
|
pw.print(" (");
|
||
|
pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
|
||
|
pw.print(") ");
|
||
|
pw.print(formatter.format(stateStats.valueAt(i)));
|
||
|
pw.println();
|
||
|
}
|
||
|
}
|
||
|
PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
|
||
|
for (int i = 0; i < uidStats.size(); i++) {
|
||
|
pw.print("UID ");
|
||
|
pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
|
||
|
pw.print(": ");
|
||
|
pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
|
||
|
pw.println();
|
||
|
}
|
||
|
pw.decreaseIndent();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "PowerStats: " + formatForBatteryHistory(" UID ");
|
||
|
}
|
||
|
|
||
|
public static class PowerStatsFormatter {
|
||
|
private static class Section {
|
||
|
public String label;
|
||
|
public int position;
|
||
|
public int length;
|
||
|
public boolean optional;
|
||
|
public boolean typePower;
|
||
|
}
|
||
|
|
||
|
private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
|
||
|
private static final Pattern SECTION_PATTERN =
|
||
|
Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
|
||
|
private final List<Section> mSections;
|
||
|
|
||
|
public PowerStatsFormatter(String format) {
|
||
|
mSections = parseFormat(format);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Produces a formatted string representing the supplied array, with labels
|
||
|
* and other adornments specific to the power stats layout.
|
||
|
*/
|
||
|
public String format(long[] stats) {
|
||
|
return format(mSections, stats);
|
||
|
}
|
||
|
|
||
|
private List<Section> parseFormat(String format) {
|
||
|
if (format == null || format.isBlank()) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
ArrayList<Section> sections = new ArrayList<>();
|
||
|
Matcher matcher = SECTION_PATTERN.matcher(format);
|
||
|
for (int position = 0; position < format.length(); position = matcher.end()) {
|
||
|
if (!matcher.find() || matcher.start() != position) {
|
||
|
Slog.wtf(TAG, "Bad power stats format '" + format + "'");
|
||
|
return null;
|
||
|
}
|
||
|
Section section = new Section();
|
||
|
section.label = matcher.group(1);
|
||
|
section.position = Integer.parseUnsignedInt(matcher.group(2));
|
||
|
String length = matcher.group("L");
|
||
|
if (length != null) {
|
||
|
section.length = Integer.parseUnsignedInt(length);
|
||
|
} else {
|
||
|
section.length = 1;
|
||
|
}
|
||
|
String flags = matcher.group("F");
|
||
|
if (flags != null) {
|
||
|
for (int i = 0; i < flags.length(); i++) {
|
||
|
char flag = flags.charAt(i);
|
||
|
switch (flag) {
|
||
|
case '?':
|
||
|
section.optional = true;
|
||
|
break;
|
||
|
case 'p':
|
||
|
section.typePower = true;
|
||
|
break;
|
||
|
default:
|
||
|
Slog.e(TAG,
|
||
|
"Unsupported format option '" + flag + "' in " + format);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
sections.add(section);
|
||
|
}
|
||
|
|
||
|
return sections;
|
||
|
}
|
||
|
|
||
|
private String format(List<Section> sections, long[] stats) {
|
||
|
if (sections == null) {
|
||
|
return Arrays.toString(stats);
|
||
|
}
|
||
|
|
||
|
StringBuilder sb = new StringBuilder();
|
||
|
for (int i = 0, count = sections.size(); i < count; i++) {
|
||
|
Section section = sections.get(i);
|
||
|
if (section.length == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (section.optional) {
|
||
|
boolean nonZero = false;
|
||
|
for (int offset = 0; offset < section.length; offset++) {
|
||
|
if (stats[section.position + offset] != 0) {
|
||
|
nonZero = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!nonZero) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!sb.isEmpty()) {
|
||
|
sb.append(' ');
|
||
|
}
|
||
|
sb.append(section.label).append(": ");
|
||
|
if (section.length != 1) {
|
||
|
sb.append('[');
|
||
|
}
|
||
|
for (int offset = 0; offset < section.length; offset++) {
|
||
|
if (offset != 0) {
|
||
|
sb.append(", ");
|
||
|
}
|
||
|
if (section.typePower) {
|
||
|
sb.append(BatteryStats.formatCharge(
|
||
|
stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
|
||
|
} else {
|
||
|
sb.append(stats[section.position + offset]);
|
||
|
}
|
||
|
}
|
||
|
if (section.length != 1) {
|
||
|
sb.append(']');
|
||
|
}
|
||
|
}
|
||
|
return sb.toString();
|
||
|
}
|
||
|
}
|
||
|
}
|