summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/BatteryStats.java202
-rw-r--r--core/java/android/os/BatteryUsageStats.java2
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java355
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistoryIterator.java157
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java32
-rw-r--r--core/java/com/android/internal/os/MultiStateStats.java303
-rw-r--r--core/java/com/android/internal/os/PowerStats.java268
-rw-r--r--core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp12
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java132
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java7
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStats.java147
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java6
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java132
-rw-r--r--services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java29
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java8
-rw-r--r--services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java121
-rw-r--r--services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java232
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsAggregator.java226
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/powerstatstests/AndroidManifest.xml1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java89
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java335
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java18
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java267
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java232
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java3
28 files changed, 2548 insertions, 780 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 42c56265bb4a..8482945dc1f0 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -19,6 +19,7 @@ package android.os;
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.PowerStats;
import com.google.android.collect.Lists;
@@ -1793,75 +1795,55 @@ public abstract class BatteryStats {
}
/**
- * Measured energy delta from the previous reading.
+ * An extension to the history item describing a proc state change for a UID.
*/
- public static final class EnergyConsumerDetails {
+ public static final class ProcessStateChange {
+ public int uid;
+ public @BatteryConsumer.ProcessState int processState;
+
+ private static final int LARGE_UID_FLAG = 0x80000000;
+ private static final int SMALL_UID_MASK = 0x00FFFFFF;
+ private static final int PROC_STATE_MASK = 0x7F000000;
+ private static final int PROC_STATE_SHIFT = Integer.numberOfTrailingZeros(PROC_STATE_MASK);
+
/**
- * Description of the energy consumer, such as CPU, DISPLAY etc
+ * Writes this object to the supplied parcel.
*/
- public static final class EnergyConsumer {
- /**
- * See android.hardware.power.stats.EnergyConsumerType
- */
- public int type;
- /**
- * Used when there are multipe energy consumers of the same type, such
- * as CPU clusters, multiple displays on foldable devices etc.
- */
- public int ordinal;
- /**
- * Human-readable name of the energy consumer, e.g. "CPU"
- */
- public String name;
- }
- public EnergyConsumer[] consumers;
- public long[] chargeUC;
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < consumers.length; i++) {
- if (chargeUC[i] == POWER_DATA_UNAVAILABLE) {
- continue;
- }
- if (sb.length() != 0) {
- sb.append(' ');
- }
- sb.append(consumers[i].name);
- sb.append('=');
- sb.append(chargeUC[i]);
+ public void writeToParcel(Parcel out) {
+ int bits = processState << PROC_STATE_SHIFT;
+ if ((uid & ~SMALL_UID_MASK) == 0) {
+ bits |= uid;
+ out.writeInt(bits);
+ } else {
+ bits |= LARGE_UID_FLAG;
+ out.writeInt(bits);
+ out.writeInt(uid);
}
- return sb.toString();
}
- }
- /**
- * CPU usage for a given UID.
- */
- public static final class CpuUsageDetails {
/**
- * Descriptions of CPU power brackets, see PowerProfile.getCpuPowerBracketDescription
+ * Reads this object from the supplied parcel.
*/
- public String[] cpuBracketDescriptions;
- public int uid;
+ public void readFromParcel(Parcel in) {
+ int bits = in.readInt();
+ processState = (bits & PROC_STATE_MASK) >>> PROC_STATE_SHIFT;
+ if (processState >= BatteryConsumer.PROCESS_STATE_COUNT) {
+ Slog.e(TAG, "Unrecognized proc state in battery history: " + processState);
+ processState = BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+ }
+ if ((bits & LARGE_UID_FLAG) == 0) {
+ uid = bits & ~PROC_STATE_MASK;
+ } else {
+ uid = in.readInt();
+ }
+ }
+
/**
- * The delta, in milliseconds, per CPU power bracket, from the previous record for the
- * same UID.
+ * String representation for inclusion in the battery history dump.
*/
- public long[] cpuUsageMs;
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- UserHandle.formatUid(sb, uid);
- sb.append(": ");
- for (int bracket = 0; bracket < cpuUsageMs.length; bracket++) {
- if (bracket != 0) {
- sb.append(", ");
- }
- sb.append(cpuUsageMs[bracket]);
- }
- return sb.toString();
+ public String formatForBatteryHistory() {
+ return UserHandle.formatUid(uid) + ": "
+ + BatteryConsumer.processStateToString(processState);
}
}
@@ -2008,11 +1990,11 @@ public abstract class BatteryStats {
// Non-null when there is more detailed information at this step.
public HistoryStepDetails stepDetails;
- // Non-null when there is energy consumer information
- public EnergyConsumerDetails energyConsumerDetails;
+ // Non-null when there are power stats to be written to history
+ public PowerStats powerStats;
- // Non-null when there is CPU usage information
- public CpuUsageDetails cpuUsageDetails;
+ // Non-null when there is procstate change to be written to history
+ public ProcessStateChange processStateChange;
public static final int EVENT_FLAG_START = 0x8000;
public static final int EVENT_FLAG_FINISH = 0x4000;
@@ -2110,6 +2092,7 @@ public abstract class BatteryStats {
public final HistoryTag localWakelockTag = new HistoryTag();
public final HistoryTag localWakeReasonTag = new HistoryTag();
public final HistoryTag localEventTag = new HistoryTag();
+ public final ProcessStateChange localProcessStateChange = new ProcessStateChange();
// Includes a tag's first occurrence in the parcel, so the value of the tag is written
// rather than just its index in the history tag pool.
@@ -2222,8 +2205,8 @@ public abstract class BatteryStats {
eventCode = EVENT_NONE;
eventTag = null;
tagsFirstOccurrence = false;
- energyConsumerDetails = null;
- cpuUsageDetails = null;
+ powerStats = null;
+ processStateChange = null;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2273,8 +2256,8 @@ public abstract class BatteryStats {
}
tagsFirstOccurrence = o.tagsFirstOccurrence;
currentTime = o.currentTime;
- energyConsumerDetails = o.energyConsumerDetails;
- cpuUsageDetails = o.cpuUsageDetails;
+ powerStats = o.powerStats;
+ processStateChange = o.processStateChange;
}
public boolean sameNonEvent(HistoryItem o) {
@@ -2434,8 +2417,14 @@ public abstract class BatteryStats {
* Returns a BatteryStatsHistoryIterator. Battery history will continue being writable,
* but the iterator will continue iterating over the snapshot taken at the time this method
* is called.
+ *
+ * @param startTimeMs wall-clock time to start iterating from, inclusive
+ * @param endTimeMs wall-clock time to stop iterating, exclusive.
+ * Pass 0 to indicate current time.
*/
- public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();
+ public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory(
+ @CurrentTimeMillisLong long startTimeMs,
+ @CurrentTimeMillisLong long endTimeMs);
/**
* Returns the number of times the device has been started.
@@ -6911,25 +6900,6 @@ public abstract class BatteryStats {
private String printNextItem(HistoryItem rec, long baseTime, boolean checkin,
boolean verbose) {
StringBuilder item = new StringBuilder();
-
- if (rec.cpuUsageDetails != null
- && rec.cpuUsageDetails.cpuBracketDescriptions != null
- && checkin) {
- String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
- for (int bracket = 0; bracket < descriptions.length; bracket++) {
- item.append(BATTERY_STATS_CHECKIN_VERSION);
- item.append(',');
- item.append(HISTORY_DATA);
- item.append(",0,XB,");
- item.append(descriptions.length);
- item.append(',');
- item.append(bracket);
- item.append(',');
- item.append(descriptions[bracket]);
- item.append("\n");
- }
- }
-
if (!checkin) {
item.append(" ");
TimeUtils.formatDuration(
@@ -7165,57 +7135,19 @@ public abstract class BatteryStats {
item.append("\"");
}
}
- boolean firstExtension = true;
- if (rec.energyConsumerDetails != null) {
- firstExtension = false;
+ if (rec.powerStats != null && verbose) {
if (!checkin) {
- item.append(" ext=energy:");
- item.append(rec.energyConsumerDetails);
- } else {
- item.append(",XE");
- for (int i = 0; i < rec.energyConsumerDetails.consumers.length; i++) {
- if (rec.energyConsumerDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
- item.append(',');
- item.append(rec.energyConsumerDetails.consumers[i].name);
- item.append('=');
- item.append(rec.energyConsumerDetails.chargeUC[i]);
- }
- }
+ item.append(
+ "\n Stats: ");
+ item.append(rec.powerStats.formatForBatteryHistory(
+ "\n "));
}
}
- if (rec.cpuUsageDetails != null) {
+ if (rec.processStateChange != null && verbose) {
if (!checkin) {
- if (!firstExtension) {
- item.append("\n ");
- }
- String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
- if (descriptions != null) {
- for (int bracket = 0; bracket < descriptions.length; bracket++) {
- item.append(" ext=cpu-bracket:");
- item.append(bracket);
- item.append(":");
- item.append(descriptions[bracket]);
- item.append("\n ");
- }
- }
- item.append(" ext=cpu:");
- item.append(rec.cpuUsageDetails);
- } else {
- if (!firstExtension) {
- item.append('\n');
- item.append(BATTERY_STATS_CHECKIN_VERSION);
- item.append(',');
- item.append(HISTORY_DATA);
- item.append(",0");
- }
- item.append(",XC,");
- item.append(rec.cpuUsageDetails.uid);
- for (int i = 0; i < rec.cpuUsageDetails.cpuUsageMs.length; i++) {
- item.append(',');
- item.append(rec.cpuUsageDetails.cpuUsageMs[i]);
- }
+ item.append(" procstate: ");
+ item.append(rec.processStateChange.formatForBatteryHistory());
}
- firstExtension = false;
}
item.append("\n");
if (rec.stepDetails != null) {
@@ -7537,7 +7469,7 @@ public abstract class BatteryStats {
long baseTime = -1;
boolean printed = false;
HistoryEventTracker tracker = null;
- try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory(0, 0)) {
HistoryItem rec;
while ((rec = iterator.next()) != null) {
try {
@@ -8460,7 +8392,7 @@ public abstract class BatteryStats {
long baseTime = -1;
boolean printed = false;
HistoryEventTracker tracker = null;
- try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory(0, 0)) {
HistoryItem rec;
while ((rec = iterator.next()) != null) {
lastTime = rec.time;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index e2c52cecc2b1..7586bf7700d9 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -315,7 +315,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
throw new IllegalStateException(
"Battery history was not requested in the BatteryUsageStatsQuery");
}
- return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
+ return new BatteryStatsHistoryIterator(mBatteryStatsHistory, 0, 0);
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 79152b4b618f..d3103f1ed24b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -18,11 +18,10 @@ package com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.BatteryStats.BitDescription;
-import android.os.BatteryStats.CpuUsageDetails;
-import android.os.BatteryStats.EnergyConsumerDetails;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.HistoryStepDetails;
import android.os.BatteryStats.HistoryTag;
@@ -42,7 +41,6 @@ import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ParseUtils;
import java.io.File;
import java.io.FileOutputStream;
@@ -83,7 +81,7 @@ public class BatteryStatsHistory {
private static final int VERSION = 209;
private static final String HISTORY_DIR = "battery-history";
- private static final String FILE_SUFFIX = ".bin";
+ private static final String FILE_SUFFIX = ".bh";
private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
// Part of initial delta int that specifies the time delta.
@@ -124,10 +122,9 @@ public class BatteryStatsHistory {
// therefore the tag value is written in the parcel
static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
- static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001;
- static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002;
- static final int EXTENSION_CPU_USAGE_HEADER_FLAG = 0x00000004;
- static final int EXTENSION_CPU_USAGE_FLAG = 0x00000008;
+ static final int EXTENSION_POWER_STATS_DESCRIPTOR_FLAG = 0x00000001;
+ static final int EXTENSION_POWER_STATS_FLAG = 0x00000002;
+ static final int EXTENSION_PROCESS_STATE_CHANGE_FLAG = 0x00000004;
// For state1, trace everything except the wakelock bit (which can race with
// suspend) and the running bit (which isn't meaningful in traces).
@@ -149,10 +146,11 @@ public class BatteryStatsHistory {
* The active history file that the history buffer is backed up into.
*/
private AtomicFile mActiveFile;
+
/**
- * A list of history files with incremental indexes.
+ * A list of history files with increasing timestamps.
*/
- private final List<Integer> mFileNumbers = new ArrayList<>();
+ private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>();
/**
* A list of small history parcels, used when BatteryStatsImpl object is created from
@@ -200,14 +198,42 @@ public class BatteryStatsHistory {
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
private long mHistoryBaseTimeMs;
- private boolean mMeasuredEnergyHeaderWritten = false;
- private boolean mCpuUsageHeaderWritten = false;
- private final VarintParceler mVarintParceler = new VarintParceler();
+ private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
private final BatteryStatsHistory mWritableHistory;
private boolean mCleanupEnabled = true;
+ private static class BatteryHistoryFile implements Comparable<BatteryHistoryFile> {
+ public final long monotonicTimeMs;
+ public final AtomicFile atomicFile;
+
+ private BatteryHistoryFile(File directory, long monotonicTimeMs) {
+ this.monotonicTimeMs = monotonicTimeMs;
+ atomicFile = new AtomicFile(new File(directory, monotonicTimeMs + FILE_SUFFIX));
+ }
+
+ @Override
+ public int compareTo(BatteryHistoryFile o) {
+ return Long.compare(monotonicTimeMs, o.monotonicTimeMs);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return monotonicTimeMs == ((BatteryHistoryFile) o).monotonicTimeMs;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(monotonicTimeMs);
+ }
+
+ @Override
+ public String toString() {
+ return atomicFile.getBaseFile().toString();
+ }
+ }
+
/**
* A delegate responsible for computing additional details for a step in battery history.
*/
@@ -317,32 +343,47 @@ public class BatteryStatsHistory {
Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
}
- final Set<Integer> dedup = new ArraySet<>();
- // scan directory, fill mFileNumbers and mActiveFile.
+ final List<File> toRemove = new ArrayList<>();
+ final Set<BatteryHistoryFile> dedup = new ArraySet<>();
mHistoryDir.listFiles((dir, name) -> {
final int b = name.lastIndexOf(FILE_SUFFIX);
if (b <= 0) {
+ toRemove.add(new File(dir, name));
return false;
}
- final int c = ParseUtils.parseInt(name.substring(0, b), -1);
- if (c != -1) {
- dedup.add(c);
- return true;
- } else {
+ try {
+ long monotonicTime = Long.parseLong(name.substring(0, b));
+ dedup.add(new BatteryHistoryFile(mHistoryDir, monotonicTime));
+ } catch (NumberFormatException e) {
+ toRemove.add(new File(dir, name));
return false;
}
+ return true;
});
if (!dedup.isEmpty()) {
- mFileNumbers.addAll(dedup);
- Collections.sort(mFileNumbers);
- setActiveFile(mFileNumbers.get(mFileNumbers.size() - 1));
- } else {
- // No file found, default to have file 0.
- mFileNumbers.add(0);
- setActiveFile(0);
+ mHistoryFiles.addAll(dedup);
+ Collections.sort(mHistoryFiles);
+ setActiveFile(mHistoryFiles.get(mHistoryFiles.size() - 1));
+ } else if (mMutable) {
+ // No file found, default to have the initial file.
+ BatteryHistoryFile name = makeBatteryHistoryFile();
+ mHistoryFiles.add(name);
+ setActiveFile(name);
+ }
+ if (!toRemove.isEmpty()) {
+ // Clear out legacy history files, which did not follow the X-Y.bin naming format.
+ BackgroundThread.getHandler().post(() -> {
+ for (File file : toRemove) {
+ file.delete();
+ }
+ });
}
}
+ private BatteryHistoryFile makeBatteryHistoryFile() {
+ return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+ }
+
public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
mMaxHistoryFiles = maxHistoryFiles;
@@ -384,8 +425,7 @@ public class BatteryStatsHistory {
mLastHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
- mMeasuredEnergyHeaderWritten = false;
- mCpuUsageHeaderWritten = false;
+ mWrittenPowerStatsDescriptors.clear();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -439,28 +479,15 @@ public class BatteryStatsHistory {
/**
* Set the active file that mHistoryBuffer is backed up into.
- *
- * @param fileNumber the history file that mHistoryBuffer is backed up into.
*/
- private void setActiveFile(int fileNumber) {
- mActiveFile = getFile(fileNumber);
+ private void setActiveFile(BatteryHistoryFile file) {
+ mActiveFile = file.atomicFile;
if (DEBUG) {
Slog.d(TAG, "activeHistoryFile:" + mActiveFile.getBaseFile().getPath());
}
}
/**
- * Create history AtomicFile from file number.
- *
- * @param num file number.
- * @return AtomicFile object.
- */
- private AtomicFile getFile(int num) {
- return new AtomicFile(
- new File(mHistoryDir, num + FILE_SUFFIX));
- }
-
- /**
* When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
* create next history file.
*/
@@ -470,15 +497,19 @@ public class BatteryStatsHistory {
return;
}
- if (mFileNumbers.isEmpty()) {
+ if (mHistoryFiles.isEmpty()) {
Slog.wtf(TAG, "mFileNumbers should never be empty");
return;
}
- // The last number in mFileNumbers is the highest number. The next file number is highest
- // number plus one.
- final int next = mFileNumbers.get(mFileNumbers.size() - 1) + 1;
- mFileNumbers.add(next);
+ final long start = SystemClock.uptimeMillis();
+ writeHistory();
+ if (DEBUG) {
+ Slog.d(TAG, "writeHistory took ms:" + (SystemClock.uptimeMillis() - start));
+ }
+
+ final BatteryHistoryFile next = makeBatteryHistoryFile();
+ mHistoryFiles.add(next);
setActiveFile(next);
try {
mActiveFile.getBaseFile().createNewFile();
@@ -486,6 +517,21 @@ public class BatteryStatsHistory {
Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
}
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryBufferLastPos = -1;
+ mHistoryLastWritten.clear();
+ mHistoryLastLastWritten.clear();
+
+ // Mark every entry in the pool with a flag indicating that the tag
+ // has not yet been encountered while writing the current history buffer.
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+
+ mWrittenPowerStatsDescriptors.clear();
+
synchronized (this) {
cleanupLocked();
}
@@ -507,17 +553,17 @@ public class BatteryStatsHistory {
// if free disk space is less than 100MB, delete oldest history file.
if (!hasFreeDiskSpace()) {
- int oldest = mFileNumbers.remove(0);
- getFile(oldest).delete();
+ BatteryHistoryFile oldest = mHistoryFiles.remove(0);
+ oldest.atomicFile.delete();
}
// if there are more history files than allowed, delete oldest history files.
// mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
// config at run time.
- while (mFileNumbers.size() > mMaxHistoryFiles) {
- int oldest = mFileNumbers.get(0);
- getFile(oldest).delete();
- mFileNumbers.remove(0);
+ while (mHistoryFiles.size() > mMaxHistoryFiles) {
+ BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ oldest.atomicFile.delete();
+ mHistoryFiles.remove(0);
}
}
@@ -537,12 +583,14 @@ public class BatteryStatsHistory {
*/
public void reset() {
if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
- for (Integer i : mFileNumbers) {
- getFile(i).delete();
+ for (BatteryHistoryFile file : mHistoryFiles) {
+ file.atomicFile.delete();
}
- mFileNumbers.clear();
- mFileNumbers.add(0);
- setActiveFile(0);
+ mHistoryFiles.clear();
+
+ BatteryHistoryFile name = makeBatteryHistoryFile();
+ mHistoryFiles.add(name);
+ setActiveFile(name);
initHistoryBuffer();
}
@@ -550,9 +598,13 @@ public class BatteryStatsHistory {
/**
* Start iterating history files and history buffer.
*
- * @return always return true.
+ * @param startTimeMs monotonic time (the HistoryItem.time field) to start iterating from,
+ * inclusive
+ * @param endTimeMs monotonic time to stop iterating, exclusive.
+ * Pass 0 to indicate current time.
*/
- public BatteryStatsHistoryIterator iterate() {
+ @NonNull
+ public BatteryStatsHistoryIterator iterate(long startTimeMs, long endTimeMs) {
mCurrentFileIndex = 0;
mCurrentParcel = null;
mCurrentParcelEnd = 0;
@@ -563,7 +615,7 @@ public class BatteryStatsHistory {
mWritableHistory.setCleanupEnabledLocked(false);
}
}
- return new BatteryStatsHistoryIterator(this);
+ return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
}
/**
@@ -590,7 +642,7 @@ public class BatteryStatsHistory {
* buffer
*/
@Nullable
- public Parcel getNextParcel() {
+ public Parcel getNextParcel(long startTimeMs, long endTimeMs) {
// First iterate through all records in current parcel.
if (mCurrentParcel != null) {
if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -606,13 +658,29 @@ public class BatteryStatsHistory {
}
}
- // Try next available history file.
+ int firstFileIndex = 0;
// skip the last file because its data is in history buffer.
- while (mCurrentFileIndex < mFileNumbers.size() - 1) {
+ int lastFileIndex = mHistoryFiles.size() - 1;
+ for (int i = mHistoryFiles.size() - 1; i >= 0; i--) {
+ BatteryHistoryFile file = mHistoryFiles.get(i);
+ if (file.monotonicTimeMs >= endTimeMs) {
+ lastFileIndex = i;
+ }
+ if (file.monotonicTimeMs <= startTimeMs) {
+ firstFileIndex = i;
+ break;
+ }
+ }
+
+ if (mCurrentFileIndex < firstFileIndex) {
+ mCurrentFileIndex = firstFileIndex;
+ }
+
+ while (mCurrentFileIndex < lastFileIndex) {
mCurrentParcel = null;
mCurrentParcelEnd = 0;
final Parcel p = Parcel.obtain();
- AtomicFile file = getFile(mFileNumbers.get(mCurrentFileIndex++));
+ AtomicFile file = mHistoryFiles.get(mCurrentFileIndex++).atomicFile;
if (readFileToParcel(p, file)) {
int bufSize = p.readInt();
int curPos = p.dataPosition();
@@ -769,9 +837,9 @@ public class BatteryStatsHistory {
private void writeToParcel(Parcel out, boolean useBlobs) {
final long start = SystemClock.uptimeMillis();
- out.writeInt(mFileNumbers.size() - 1);
- for (int i = 0; i < mFileNumbers.size() - 1; i++) {
- AtomicFile file = getFile(mFileNumbers.get(i));
+ out.writeInt(mHistoryFiles.size() - 1);
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ AtomicFile file = mHistoryFiles.get(i).atomicFile;
byte[] raw = new byte[0];
try {
raw = file.readFully();
@@ -872,8 +940,12 @@ public class BatteryStatsHistory {
}
@VisibleForTesting
- public List<Integer> getFilesNumbers() {
- return mFileNumbers;
+ public List<String> getFilesNames() {
+ List<String> names = new ArrayList<>();
+ for (BatteryHistoryFile historyFile : mHistoryFiles) {
+ names.add(historyFile.atomicFile.getBaseFile().getName());
+ }
+ return names;
}
@VisibleForTesting
@@ -886,8 +958,8 @@ public class BatteryStatsHistory {
*/
public int getHistoryUsedSize() {
int ret = 0;
- for (int i = 0; i < mFileNumbers.size() - 1; i++) {
- ret += getFile(mFileNumbers.get(i)).getBaseFile().length();
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ ret += mHistoryFiles.get(i).atomicFile.getBaseFile().length();
}
ret += mHistoryBuffer.dataSize();
if (mHistoryParcels != null) {
@@ -937,7 +1009,7 @@ public class BatteryStatsHistory {
* Prepares to continue recording after restoring previous history from persistent storage.
*/
public void continueRecordingHistory() {
- if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) {
+ if (mHistoryBuffer.dataPosition() <= 0 && mHistoryFiles.size() <= 1) {
return;
}
@@ -1050,11 +1122,23 @@ public class BatteryStatsHistory {
}
/**
- * Records measured energy data.
+ * Records a PowerStats snapshot.
*/
- public void recordEnergyConsumerDetails(long elapsedRealtimeMs, long uptimeMs,
- EnergyConsumerDetails energyConsumerDetails) {
- mHistoryCur.energyConsumerDetails = energyConsumerDetails;
+ public void recordPowerStats(long elapsedRealtimeMs, long uptimeMs,
+ PowerStats powerStats) {
+ mHistoryCur.powerStats = powerStats;
+ mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records the change of a UID's proc state.
+ */
+ public void recordProcessStateChange(long elapsedRealtimeMs, long uptimeMs,
+ int uid, @BatteryConsumer.ProcessState int processState) {
+ mHistoryCur.processStateChange = mHistoryCur.localProcessStateChange;
+ mHistoryCur.processStateChange.uid = uid;
+ mHistoryCur.processStateChange.processState = processState;
mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1279,17 +1363,6 @@ public class BatteryStatsHistory {
}
/**
- * Records CPU usage by a specific UID. The recorded data is the delta from
- * the previous record for the same UID.
- */
- public void recordCpuUsage(long elapsedRealtimeMs, long uptimeMs,
- CpuUsageDetails cpuUsageDetails) {
- mHistoryCur.cpuUsageDetails = cpuUsageDetails;
- mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
- writeHistoryItem(elapsedRealtimeMs, uptimeMs);
- }
-
- /**
* Writes changes to a HistoryItem state bitmap to Atrace.
*/
private void recordTraceCounters(int oldval, int newval, int mask,
@@ -1355,7 +1428,9 @@ public class BatteryStatsHistory {
mTraceLastState2 = cur.states2;
}
- if (!mHaveBatteryLevel || !mRecordingHistory) {
+ if ((!mHaveBatteryLevel || !mRecordingHistory)
+ && cur.powerStats == null
+ && cur.processStateChange == null) {
return;
}
@@ -1391,8 +1466,8 @@ public class BatteryStatsHistory {
&& mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
&& mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
&& mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
- && mHistoryLastWritten.energyConsumerDetails == null
- && mHistoryLastWritten.cpuUsageDetails == null) {
+ && mHistoryLastWritten.powerStats == null
+ && mHistoryLastWritten.processStateChange == null) {
// We can merge this new change in with the last one. Merging is
// allowed as long as only the states have changed, and within those states
// as long as no bit has changed both between now and the last entry, as
@@ -1434,34 +1509,15 @@ public class BatteryStatsHistory {
mMaxHistoryBufferSize = 1024;
}
- //open a new history file.
- final long start = SystemClock.uptimeMillis();
- writeHistory();
- if (DEBUG) {
- Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:"
- + (SystemClock.uptimeMillis() - start));
- }
- startNextFile();
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
- mHistoryBufferLastPos = -1;
- mHistoryLastWritten.clear();
- mHistoryLastLastWritten.clear();
-
- // Mark every entry in the pool with a flag indicating that the tag
- // has not yet been encountered while writing the current history buffer.
- for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
- entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
- }
- mMeasuredEnergyHeaderWritten = false;
- mCpuUsageHeaderWritten = false;
-
// Make a copy of mHistoryCur.
HistoryItem copy = new HistoryItem();
copy.setTo(cur);
+
+ startNextFile();
+
// startRecordingHistory will reset mHistoryCur.
startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+
// Add the copy into history buffer.
writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
return;
@@ -1477,8 +1533,8 @@ public class BatteryStatsHistory {
copy.eventCode = HistoryItem.EVENT_NONE;
copy.eventTag = null;
copy.tagsFirstOccurrence = false;
- copy.energyConsumerDetails = null;
- copy.cpuUsageDetails = null;
+ copy.powerStats = null;
+ copy.processStateChange = null;
writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_RESET);
}
writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
@@ -1506,8 +1562,8 @@ public class BatteryStatsHistory {
cur.eventCode = HistoryItem.EVENT_NONE;
cur.eventTag = null;
cur.tagsFirstOccurrence = false;
- cur.energyConsumerDetails = null;
- cur.cpuUsageDetails = null;
+ cur.powerStats = null;
+ cur.processStateChange = null;
if (DEBUG) {
Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ " now " + mHistoryBuffer.dataPosition()
@@ -1638,17 +1694,14 @@ public class BatteryStatsHistory {
if (stateIntChanged) {
firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
}
- if (cur.energyConsumerDetails != null) {
- extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG;
- if (!mMeasuredEnergyHeaderWritten) {
- extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
+ if (cur.powerStats != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_POWER_STATS_FLAG;
+ if (!mWrittenPowerStatsDescriptors.contains(cur.powerStats.descriptor)) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG;
}
}
- if (cur.cpuUsageDetails != null) {
- extensionFlags |= EXTENSION_CPU_USAGE_FLAG;
- if (!mCpuUsageHeaderWritten) {
- extensionFlags |= BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG;
- }
+ if (cur.processStateChange != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_PROCESS_STATE_CHANGE_FLAG;
}
if (extensionFlags != 0) {
cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
@@ -1773,37 +1826,16 @@ public class BatteryStatsHistory {
dest.writeDouble(cur.wifiRailChargeMah);
if (extensionFlags != 0) {
dest.writeInt(extensionFlags);
- if (cur.energyConsumerDetails != null) {
- if (DEBUG) {
- Slog.i(TAG, "WRITE DELTA: measuredEnergyDetails=" + cur.energyConsumerDetails);
- }
- if (!mMeasuredEnergyHeaderWritten) {
- EnergyConsumerDetails.EnergyConsumer[] consumers =
- cur.energyConsumerDetails.consumers;
- dest.writeInt(consumers.length);
- for (EnergyConsumerDetails.EnergyConsumer consumer : consumers) {
- dest.writeInt(consumer.type);
- dest.writeInt(consumer.ordinal);
- dest.writeString(consumer.name);
- }
- mMeasuredEnergyHeaderWritten = true;
+ if (cur.powerStats != null) {
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG)
+ != 0) {
+ cur.powerStats.descriptor.writeSummaryToParcel(dest);
+ mWrittenPowerStatsDescriptors.add(cur.powerStats.descriptor);
}
- mVarintParceler.writeLongArray(dest, cur.energyConsumerDetails.chargeUC);
+ cur.powerStats.writeToParcel(dest);
}
-
- if (cur.cpuUsageDetails != null) {
- if (DEBUG) {
- Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
- }
- if (!mCpuUsageHeaderWritten) {
- dest.writeInt(cur.cpuUsageDetails.cpuBracketDescriptions.length);
- for (String desc: cur.cpuUsageDetails.cpuBracketDescriptions) {
- dest.writeString(desc);
- }
- mCpuUsageHeaderWritten = true;
- }
- dest.writeInt(cur.cpuUsageDetails.uid);
- mVarintParceler.writeLongArray(dest, cur.cpuUsageDetails.cpuUsageMs);
+ if (cur.processStateChange != null) {
+ cur.processStateChange.writeToParcel(dest);
}
}
}
@@ -2054,13 +2086,15 @@ public class BatteryStatsHistory {
* fewer bytes. It is a bit more expensive than just writing the long into the parcel,
* but at scale saves a lot of storage and allows recording of longer battery history.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final class VarintParceler {
/**
* Writes an array of longs into Parcel using the varint format, see
* https://developers.google.com/protocol-buffers/docs/encoding#varints
*/
public void writeLongArray(Parcel parcel, long[] values) {
+ if (values.length == 0) {
+ return;
+ }
int out = 0;
int shift = 0;
for (long value : values) {
@@ -2092,6 +2126,9 @@ public class BatteryStatsHistory {
* Reads a long written with {@link #writeLongArray}
*/
public void readLongArray(Parcel parcel, long[] values) {
+ if (values.length == 0) {
+ return;
+ }
int in = parcel.readInt();
int available = 4;
for (int i = 0; i < values.length; i++) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 4c2b2854df88..a5d2d0fc1a01 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -33,32 +34,32 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistoryItr";
private final BatteryStatsHistory mBatteryStatsHistory;
+ private final @CurrentTimeMillisLong long mStartTimeMs;
+ private final @CurrentTimeMillisLong long mEndTimeMs;
private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
- private BatteryStats.EnergyConsumerDetails mEnergyConsumerDetails;
- private BatteryStats.CpuUsageDetails mCpuUsageDetails;
- private final BatteryStatsHistory.VarintParceler mVarintParceler =
- new BatteryStatsHistory.VarintParceler();
+ private final PowerStats.DescriptorRegistry mDescriptorRegistry =
+ new PowerStats.DescriptorRegistry();
+ private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
+ private boolean mNextItemReady;
- private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
-
- private static final int MAX_ENERGY_CONSUMER_COUNT = 100;
- private static final int MAX_CPU_BRACKET_COUNT = 100;
-
- public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
+ public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
+ @CurrentTimeMillisLong long startTimeMs,
+ @CurrentTimeMillisLong long endTimeMs) {
mBatteryStatsHistory = history;
+ mStartTimeMs = startTimeMs;
+ mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
mHistoryItem.clear();
}
@Override
public boolean hasNext() {
- Parcel p = mBatteryStatsHistory.getNextParcel();
- if (p == null) {
- close();
- return false;
+ if (!mNextItemReady) {
+ advance();
}
- return true;
+
+ return mHistoryItem != null;
}
/**
@@ -67,25 +68,45 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
*/
@Override
public BatteryStats.HistoryItem next() {
- Parcel p = mBatteryStatsHistory.getNextParcel();
- if (p == null) {
- close();
- return null;
+ if (!mNextItemReady) {
+ advance();
}
+ mNextItemReady = false;
+ return mHistoryItem;
+ }
- final long lastRealtimeMs = mHistoryItem.time;
- final long lastWalltimeMs = mHistoryItem.currentTime;
- try {
- readHistoryDelta(p, mHistoryItem);
- } catch (Throwable t) {
- Slog.wtf(TAG, "Corrupted battery history", t);
- return null;
- }
- if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
- && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
- mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ private void advance() {
+ while (true) {
+ Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs);
+ if (p == null) {
+ break;
+ }
+
+ final long lastRealtimeMs = mHistoryItem.time;
+ final long lastWalltimeMs = mHistoryItem.currentTime;
+ try {
+ readHistoryDelta(p, mHistoryItem);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Corrupted battery history", t);
+ break;
+ }
+ if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
+ && lastWalltimeMs != 0) {
+ mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ }
+ if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+ break;
+ }
+ if (mHistoryItem.currentTime >= mStartTimeMs) {
+ mNextItemReady = true;
+ return;
+ }
}
- return mHistoryItem;
+
+ mHistoryItem = null;
+ mNextItemReady = true;
+ close();
}
private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
@@ -229,74 +250,24 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
cur.wifiRailChargeMah = src.readDouble();
if ((cur.states2 & BatteryStats.HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
final int extensionFlags = src.readInt();
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG) != 0) {
- if (mEnergyConsumerDetails == null) {
- mEnergyConsumerDetails = new BatteryStats.EnergyConsumerDetails();
- }
-
- final int consumerCount = src.readInt();
- if (consumerCount > MAX_ENERGY_CONSUMER_COUNT) {
- // Check to avoid a heap explosion in case the parcel is corrupted
- throw new IllegalStateException(
- "EnergyConsumer count too high: " + consumerCount
- + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
- }
- mEnergyConsumerDetails.consumers =
- new BatteryStats.EnergyConsumerDetails.EnergyConsumer[consumerCount];
- mEnergyConsumerDetails.chargeUC = new long[consumerCount];
- for (int i = 0; i < consumerCount; i++) {
- BatteryStats.EnergyConsumerDetails.EnergyConsumer consumer =
- new BatteryStats.EnergyConsumerDetails.EnergyConsumer();
- consumer.type = src.readInt();
- consumer.ordinal = src.readInt();
- consumer.name = src.readString();
- mEnergyConsumerDetails.consumers[i] = consumer;
- }
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG) != 0) {
+ PowerStats.Descriptor descriptor = PowerStats.Descriptor.readSummaryFromParcel(src);
+ mDescriptorRegistry.register(descriptor);
}
-
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG) != 0) {
- if (mEnergyConsumerDetails == null) {
- throw new IllegalStateException("MeasuredEnergyDetails without a header");
- }
-
- mVarintParceler.readLongArray(src, mEnergyConsumerDetails.chargeUC);
- cur.energyConsumerDetails = mEnergyConsumerDetails;
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_FLAG) != 0) {
+ cur.powerStats = PowerStats.readFromParcel(src, mDescriptorRegistry);
} else {
- cur.energyConsumerDetails = null;
- }
-
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
- mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
- final int cpuBracketCount = src.readInt();
- if (cpuBracketCount > MAX_CPU_BRACKET_COUNT) {
- // Check to avoid a heap explosion in case the parcel is corrupted
- throw new IllegalStateException("Too many CPU brackets: " + cpuBracketCount
- + ". Max = " + MAX_CPU_BRACKET_COUNT);
- }
- mCpuUsageDetails.cpuBracketDescriptions = new String[cpuBracketCount];
- for (int i = 0; i < cpuBracketCount; i++) {
- mCpuUsageDetails.cpuBracketDescriptions[i] = src.readString();
- }
- mCpuUsageDetails.cpuUsageMs =
- new long[mCpuUsageDetails.cpuBracketDescriptions.length];
- } else if (mCpuUsageDetails != null) {
- mCpuUsageDetails.cpuBracketDescriptions = null;
+ cur.powerStats = null;
}
-
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_FLAG) != 0) {
- if (mCpuUsageDetails == null) {
- throw new IllegalStateException("CpuUsageDetails without a header");
- }
-
- mCpuUsageDetails.uid = src.readInt();
- mVarintParceler.readLongArray(src, mCpuUsageDetails.cpuUsageMs);
- cur.cpuUsageDetails = mCpuUsageDetails;
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_PROCESS_STATE_CHANGE_FLAG) != 0) {
+ cur.processStateChange = cur.localProcessStateChange;
+ cur.processStateChange.readFromParcel(src);
} else {
- cur.cpuUsageDetails = null;
+ cur.processStateChange = null;
}
} else {
- cur.energyConsumerDetails = null;
- cur.cpuUsageDetails = null;
+ cur.powerStats = null;
+ cur.processStateChange = null;
}
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 664aeee6e299..5ea6ba86da71 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -195,6 +195,34 @@ public final class LongArrayMultiStateCounter implements Parcelable {
* is distributed among the state according to the time the object spent in those states
* since the previous call to updateValues.
*/
+ public void updateValues(long[] values, long timestampMs) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ updateValues(container, timestampMs);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void incrementValues(long[] values, long timestampMs) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Sets the new values. The delta between the previously set values and these values
+ * is distributed among the state according to the time the object spent in those states
+ * since the previous call to updateValues.
+ */
public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
if (longArrayContainer.mLength != mLength) {
throw new IllegalArgumentException(
@@ -293,6 +321,10 @@ public final class LongArrayMultiStateCounter implements Parcelable {
long longArrayContainerNativeObject, long timestampMs);
@CriticalNative
+ private static native void native_incrementValues(long nativeObject,
+ long longArrayContainerNativeObject, long timestampMs);
+
+ @CriticalNative
private static native void native_addCounts(long nativeObject,
long longArrayContainerNativeObject);
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
new file mode 100644
index 000000000000..f971849987dd
--- /dev/null
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -0,0 +1,303 @@
+/*
+ * 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 com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Maintains multidimensional multi-state stats. States could be something like on-battery (0,1),
+ * screen-on (0,1), process state etc. Dimensions refer to the metrics themselves, e.g.
+ * CPU residency, Network packet counts etc. All metrics must be represented as <code>long</code>
+ * values;
+ */
+public class MultiStateStats {
+ /**
+ * A set of states, e.g. on-battery, screen-on, procstate. The state values are integers
+ * from 0 to States.mLabels.length
+ */
+ public static class States {
+ final boolean mTracked;
+ final String[] mLabels;
+
+ public States(boolean tracked, String... labels) {
+ this.mTracked = tracked;
+ this.mLabels = labels;
+ }
+
+ public boolean isTracked() {
+ return mTracked;
+ }
+ }
+
+ /**
+ * Factory for MultiStateStats containers. All generated containers retain their connection
+ * to the Factory and the corresponding configuration.
+ */
+ public static class Factory {
+ private static final int INVALID_SERIAL_STATE = -1;
+ final int mDimensionCount;
+ final States[] mStates;
+ /*
+ * The LongArrayMultiStateCounter object that is used for accumulation of per-state
+ * stats thinks of "state" as a simple 0-based index. This Factory object's job is to
+ * map a combination of individual states (e.g. on-battery, process state etc) to
+ * such a simple index.
+ *
+ * This task is performed in two steps:
+ * 1) We define "composite state" as an integer that combines all constituent States
+ * into one integer as bit fields. This gives us a convenient mechanism for updating a
+ * single constituent State at a time. We maintain an array of bit field masks
+ * corresponding to each constituent State.
+ *
+ * 2) We map composite states to "serial states", i.e. simple integer indexes, taking
+ * into account which constituent states are configured as tracked. If a state is not
+ * tracked, there is no need to maintain separate counts for its values, thus
+ * all values of that constituent state can be mapped to the same serial state.
+ */
+ private final int[] mStateBitFieldMasks;
+ private final short[] mStateBitFieldShifts;
+ final int[] mCompositeToSerialState;
+ final int mSerialStateCount;
+
+ public Factory(int dimensionCount, States... states) {
+ mDimensionCount = dimensionCount;
+ mStates = states;
+
+ int serialStateCount = 1;
+ for (States state : mStates) {
+ if (state.mTracked) {
+ serialStateCount *= state.mLabels.length;
+ }
+ }
+ mSerialStateCount = serialStateCount;
+
+ mStateBitFieldMasks = new int[mStates.length];
+ mStateBitFieldShifts = new short[mStates.length];
+
+ int shift = 0;
+ for (int i = 0; i < mStates.length; i++) {
+ mStateBitFieldShifts[i] = (short) shift;
+ if (mStates[i].mLabels.length < 2) {
+ throw new IllegalArgumentException("Invalid state: " + Arrays.toString(
+ mStates[i].mLabels) + ". Should have at least two values.");
+ }
+ int max = mStates[i].mLabels.length - 1;
+ int bitcount = Integer.SIZE - Integer.numberOfLeadingZeros(max);
+ mStateBitFieldMasks[i] = ((1 << bitcount) - 1) << shift;
+ shift = shift + bitcount;
+ }
+
+ if (shift >= Integer.SIZE - 1) {
+ throw new IllegalArgumentException("Too many states: " + shift
+ + " bits are required to represent the composite state, but only "
+ + (Integer.SIZE - 1) + " are available");
+ }
+
+ // Create a mask that filters out all non tracked states
+ int trackedMask = 0xFFFFFFFF;
+ for (int state = 0; state < mStates.length; state++) {
+ if (!mStates[state].mTracked) {
+ trackedMask &= ~mStateBitFieldMasks[state];
+ }
+ }
+
+ mCompositeToSerialState = new int[1 << shift];
+ Arrays.fill(mCompositeToSerialState, INVALID_SERIAL_STATE);
+
+ int nextSerialState = 0;
+ for (int composite = 0; composite < mCompositeToSerialState.length; composite++) {
+ if (!isValidCompositeState(composite)) continue;
+
+ // Values of an untracked State map to different composite states, but must map to
+ // the same serial state. Achieve that by computing a "base composite", which
+ // is equivalent to the current composite, but has 0 for all untracked States.
+ // See if the base composite already has a serial state assigned. If so, just use
+ // the same one for the current composite.
+ int baseComposite = composite & trackedMask;
+ if (mCompositeToSerialState[baseComposite] != INVALID_SERIAL_STATE) {
+ mCompositeToSerialState[composite] = mCompositeToSerialState[baseComposite];
+ } else {
+ mCompositeToSerialState[composite] = nextSerialState++;
+ }
+ }
+ }
+
+ private boolean isValidCompositeState(int composite) {
+ for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+ int state = extractStateFromComposite(composite, stateIndex);
+ if (state >= mStates[stateIndex].mLabels.length) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private int extractStateFromComposite(int compositeState, int stateIndex) {
+ return (compositeState & mStateBitFieldMasks[stateIndex])
+ >>> mStateBitFieldShifts[stateIndex];
+ }
+
+ private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+ return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
+ | (value << mStateBitFieldShifts[stateIndex]);
+ }
+
+ /**
+ * Allocates a new stats container using this Factory's configuration.
+ */
+ public MultiStateStats create() {
+ return new MultiStateStats(this, mDimensionCount);
+ }
+
+ /**
+ * Returns the total number of composite states handled by this container. For example,
+ * if there are two states: on-battery (0,1) and screen-on (0,1), both tracked, then the
+ * serial state count will be 2 * 2 = 4
+ */
+ @VisibleForTesting
+ public int getSerialStateCount() {
+ return mSerialStateCount;
+ }
+
+ /**
+ * Returns the integer index used by this container to represent the supplied composite
+ * state.
+ */
+ @VisibleForTesting
+ public int getSerialState(int[] states) {
+ Preconditions.checkArgument(states.length == mStates.length);
+ int compositeState = 0;
+ for (int i = 0; i < states.length; i++) {
+ compositeState = setStateInComposite(compositeState, i, states[i]);
+ }
+ int serialState = mCompositeToSerialState[compositeState];
+ if (serialState == INVALID_SERIAL_STATE) {
+ throw new IllegalArgumentException("State values out of bounds: "
+ + Arrays.toString(states));
+ }
+ return serialState;
+ }
+ }
+
+ private final Factory mFactory;
+ private final LongArrayMultiStateCounter mCounter;
+ private int mCompositeState;
+ private boolean mTracking;
+
+ public MultiStateStats(Factory factory, int dimensionCount) {
+ this.mFactory = factory;
+ mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount);
+ }
+
+ /**
+ * Updates the current composite state by changing one of the States supplied to the Factory
+ * constructor.
+ *
+ * @param stateIndex Corresponds to the index of the States supplied to the Factory constructor
+ * @param state The new value of the state (e.g. 0 or 1 for "on-battery")
+ * @param timestampMs The time when the state change occurred
+ */
+ public void setState(int stateIndex, int state, long timestampMs) {
+ if (!mTracking) {
+ mCounter.updateValues(new long[mCounter.getArrayLength()], timestampMs);
+ mTracking = true;
+ }
+ mCompositeState = mFactory.setStateInComposite(mCompositeState, stateIndex, state);
+ mCounter.setState(mFactory.mCompositeToSerialState[mCompositeState], timestampMs);
+ }
+
+ /**
+ * Adds the delta to the metrics. The number of values must correspond to the dimension count
+ * supplied to the Factory constructor
+ */
+ public void increment(long[] values, long timestampMs) {
+ mCounter.incrementValues(values, timestampMs);
+ mTracking = true;
+ }
+
+ /**
+ * Returns accumulated stats for the specified composite state.
+ */
+ public void getStats(long[] outValues, int[] states) {
+ mCounter.getCounts(outValues, mFactory.getSerialState(states));
+ }
+
+ /**
+ * Resets the counters.
+ */
+ public void reset() {
+ mCounter.reset();
+ mTracking = false;
+ }
+
+ @Override
+ public String toString() {
+ return mCounter.toString();
+ }
+
+ /**
+ * Prints the accumulated stats, one line of every combination of states that has data.
+ */
+ public void dump(PrintWriter pw) {
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ dumpAllStates(pw, new int[mFactory.mStates.length], 0, tmpArray);
+ }
+
+ private void dumpAllStates(PrintWriter pw, int[] states, int stateIndex, long[] values) {
+ if (stateIndex < states.length) {
+ if (!mFactory.mStates[stateIndex].mTracked) {
+ dumpAllStates(pw, states, stateIndex + 1, values);
+ return;
+ }
+
+ for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
+ states[stateIndex] = i;
+ dumpAllStates(pw, states, stateIndex + 1, values);
+ }
+ return;
+ }
+
+ mCounter.getCounts(values, mFactory.getSerialState(states));
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < states.length; i++) {
+ if (mFactory.mStates[i].mTracked) {
+ if (sb.length() != 0) {
+ sb.append(" ");
+ }
+ sb.append(mFactory.mStates[i].mLabels[states[i]]);
+ }
+ }
+ sb.append(" ");
+ sb.append(Arrays.toString(values));
+ pw.println(sb);
+ }
+}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 1169552e165d..8f66d1f9365c 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -16,21 +16,190 @@
package com.android.internal.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.BatteryConsumer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.SparseArray;
import java.util.Arrays;
+import java.util.Objects;
/**
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
* details.
*/
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 = 1;
+
+ 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 =
+ 2 ^ Integer.bitCount(STATS_ARRAY_LENGTH_MASK) - 1;
+ private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
+ 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 =
+ (2 ^ Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
+
/**
- * Power component (e.g. CPU, WIFI etc) that this snapshot relates to.
+ * 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.
*/
- public @BatteryConsumer.PowerComponent int powerComponentId;
+ public static class Descriptor {
+ /**
+ * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
+ * to; or a custom power component ID (if the value
+ * is &gt;= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
+ */
+ public final int powerComponentId;
+ public final String name;
+
+ public final int statsArrayLength;
+ public final int uidStatsArrayLength;
+
+ /**
+ * Extra parameters specific to the power component, e.g. the availability of power
+ * monitors.
+ */
+ public final PersistableBundle extras;
+
+ public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
+ int statsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras) {
+ this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
+ statsArrayLength, uidStatsArrayLength, extras);
+ }
+
+ public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
+ int uidStatsArrayLength, PersistableBundle extras) {
+ if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "statsArrayLength is too high. Max = " + MAX_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.uidStatsArrayLength = uidStatsArrayLength;
+ this.extras = extras;
+ }
+
+ /**
+ * 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)
+ | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
+ & UID_STATS_ARRAY_LENGTH_MASK);
+ parcel.writeInt(firstWord);
+ parcel.writeInt(powerComponentId);
+ parcel.writeString(name);
+ 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) {
+ Log.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 uidStatsArrayLength =
+ (firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
+ int powerComponentId = parcel.readInt();
+ String name = parcel.readString();
+ PersistableBundle extras = parcel.readPersistableBundle();
+ return new Descriptor(powerComponentId, name, statsArrayLength, 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
+ && 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
+ }
+
+ @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
+ + ", 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.
@@ -47,12 +216,98 @@ public final class PowerStats {
*/
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);
+ 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) {
+ Log.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);
+ 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) {
+ Log.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
+ 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(Arrays.toString(stats));
+ }
+ for (int i = 0; i < uidStats.size(); i++) {
+ sb.append(uidPrefix)
+ .append(UserHandle.formatUid(uidStats.keyAt(i)))
+ .append(": ").append(Arrays.toString(uidStats.valueAt(i)));
+ }
+ return sb.toString();
+ }
+
/**
* Prints the contents of the stats snapshot.
*/
public void dump(IndentingPrintWriter pw) {
- pw.print("PowerStats: ");
- pw.println(BatteryConsumer.powerComponentIdToString(powerComponentId));
+ pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
pw.increaseIndent();
pw.print("duration", durationMs).println();
for (int i = 0; i < uidStats.size(); i++) {
@@ -64,4 +319,9 @@ public final class PowerStats {
}
pw.decreaseIndent();
}
+
+ @Override
+ public String toString() {
+ return "PowerStats: " + formatForBatteryHistory(" UID ");
+ }
}
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 76f5c107c970..69202111f74c 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -65,6 +65,16 @@ static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativeP
counter->updateValue(*vector, timestamp);
}
+static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+ jlong timestamp) {
+ battery::LongArrayMultiStateCounter *counter =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ std::vector<uint64_t> *vector =
+ reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+ counter->incrementValue(*vector, timestamp);
+}
+
static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -202,6 +212,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
// @CriticalNative
{"native_updateValues", "(JJJ)V", (void *)native_updateValues},
// @CriticalNative
+ {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
+ // @CriticalNative
{"native_addCounts", "(JJ)V", (void *)native_addCounts},
// @CriticalNative
{"native_reset", "(J)V", (void *)native_reset},
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 38c3aa04ea35..2cbeaa17332c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -35,8 +35,9 @@ import org.junit.runners.Suite;
LongArrayMultiStateCounterTest.class,
LongMultiStateCounterTest.class,
PowerProfileTest.class,
+ PowerStatsTest.class,
EnergyConsumerStatsTest.class
})
public class BatteryStatsTests {
-} \ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
new file mode 100644
index 000000000000..29da2319adc2
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PowerStatsTest {
+
+ private PowerStats.DescriptorRegistry mRegistry;
+ private PowerStats.Descriptor mDescriptor;
+
+ @Before
+ public void setup() {
+ mRegistry = new PowerStats.DescriptorRegistry();
+ PersistableBundle extras = new PersistableBundle();
+ extras.putBoolean("hasPowerMonitor", true);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, 2, extras);
+ mRegistry.register(mDescriptor);
+ }
+
+ @Test
+ public void parceling_compatibleParcel() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.durationMs = 1234;
+ stats.stats[0] = 10;
+ stats.stats[1] = 20;
+ stats.stats[2] = 30;
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
+
+ Parcel parcel = Parcel.obtain();
+ mDescriptor.writeSummaryToParcel(parcel);
+ stats.writeToParcel(parcel);
+ parcel.writeString("END");
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+
+ PowerStats.Descriptor newDescriptor =
+ PowerStats.Descriptor.readSummaryFromParcel(newParcel);
+ assertThat(newDescriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
+ assertThat(newDescriptor.name).isEqualTo("cpu");
+ assertThat(newDescriptor.statsArrayLength).isEqualTo(3);
+ assertThat(newDescriptor.uidStatsArrayLength).isEqualTo(2);
+ assertThat(newDescriptor.extras.getBoolean("hasPowerMonitor")).isTrue();
+
+ mRegistry.register(newDescriptor);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats.durationMs).isEqualTo(1234);
+ assertThat(newStats.stats).isEqualTo(new long[]{10, 20, 30});
+ assertThat(newStats.uidStats.size()).isEqualTo(2);
+ assertThat(newStats.uidStats.get(42)).isEqualTo(new long[]{40, 50});
+ assertThat(newStats.uidStats.get(99)).isEqualTo(new long[]{60, 70});
+
+ String end = newParcel.readString();
+ assertThat(end).isEqualTo("END");
+ }
+
+ @Test
+ public void parceling_unrecognizedPowerComponent() {
+ PowerStats stats = new PowerStats(
+ new PowerStats.Descriptor(777, "luck", 3, 2, new PersistableBundle()));
+ stats.durationMs = 1234;
+
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+ parcel.writeString("END");
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+
+ String end = newParcel.readString();
+ assertThat(end).isEqualTo("END");
+ }
+
+ @Test
+ public void parceling_wrongArrayLength() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.stats = new long[5]; // Is expected to be 3
+
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+ parcel.writeString("END");
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+
+ String end = newParcel.readString();
+ assertThat(end).isEqualTo("END");
+ }
+
+ private static Parcel marshallAndUnmarshall(Parcel parcel) {
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+
+ Parcel newParcel = Parcel.obtain();
+ newParcel.unmarshall(bytes, 0, bytes.length);
+ newParcel.setDataPosition(0);
+ return newParcel;
+ }
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b0f30d6875ae..610b8af48cd1 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -2631,6 +2631,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.dumpStatsSample(pw);
}
+ private void dumpAggregatedStats(PrintWriter pw) {
+ mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+ }
+
private void dumpMeasuredEnergyStats(PrintWriter pw) {
// Wait for the completion of pending works if there is any
awaitCompletion();
@@ -2875,6 +2879,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("--sample".equals(arg)) {
dumpStatsSample(pw);
return;
+ } else if ("--aggregated".equals(arg)) {
+ dumpAggregatedStats(pw);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
new file mode 100644
index 000000000000..6cc9d0a54607
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -0,0 +1,147 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.os.UserHandle;
+import android.text.format.DateFormat;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.PowerStats;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class represents aggregated power stats for a variety of power components (CPU, WiFi,
+ * etc) covering a specific period of power usage history.
+ */
+class AggregatedPowerStats {
+ private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
+
+ @CurrentTimeMillisLong
+ private long mStartTime;
+
+ @DurationMillisLong
+ private long mDurationMs;
+
+ AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
+ this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+ }
+
+ void setStartTime(@CurrentTimeMillisLong long startTime) {
+ mStartTime = startTime;
+ }
+
+ @CurrentTimeMillisLong
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ void setDuration(long durationMs) {
+ mDurationMs = durationMs;
+ }
+
+ @DurationMillisLong
+ public long getDuration() {
+ return mDurationMs;
+ }
+
+ PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId) {
+ return stats;
+ }
+ }
+ return null;
+ }
+
+ void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.setState(stateId, state, time);
+ }
+ }
+
+ void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ long time) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.setUidState(uid, stateId, state, time);
+ }
+ }
+
+ boolean isCompatible(PowerStats powerStats) {
+ int powerComponentId = powerStats.descriptor.powerComponentId;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId && !stats.isCompatible(powerStats)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void addPowerStats(PowerStats powerStats, long time) {
+ int powerComponentId = powerStats.descriptor.powerComponentId;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId) {
+ stats.addPowerStats(powerStats, time);
+ }
+ }
+ }
+
+ void reset() {
+ mStartTime = 0;
+ mDurationMs = 0;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.reset();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.print("Start time: ");
+ ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
+ ipw.print(" duration: ");
+ ipw.print(mDurationMs);
+ ipw.println();
+
+ ipw.println("Device");
+ ipw.increaseIndent();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.dumpDevice(ipw);
+ }
+ ipw.decreaseIndent();
+
+ Set<Integer> uids = new HashSet<>();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.collectUids(uids);
+ }
+
+ Integer[] allUids = uids.toArray(new Integer[uids.size()]);
+ Arrays.sort(allUids);
+ for (int uid : allUids) {
+ ipw.println(UserHandle.formatUid(uid));
+ ipw.increaseIndent();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.dumpUid(ipw, uid);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index f4b2f52eef9c..daf02ca81a10 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -687,12 +687,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
reason, 0);
- if (energyConsumerDeltas != null && !energyConsumerDeltas.isEmpty()
- && mStats.isUsageHistoryEnabled()) {
- mStats.recordEnergyConsumerDetailsLocked(elapsedRealtime, uptime,
- mEnergyConsumerSnapshot.getEnergyConsumerDetails(energyConsumerDeltas));
- }
-
if ((updateFlags & UPDATE_CPU) != 0) {
if (useLatestStates) {
onBattery = mStats.isOnBatteryLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 5b10afadc0fc..613f18982b86 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -125,6 +125,7 @@ import com.android.internal.os.KernelSingleUidTimeReader;
import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.os.LongMultiStateCounter;
import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats;
import com.android.internal.power.EnergyConsumerStats;
@@ -135,6 +136,7 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.NetworkCapabilitiesUtils;
+import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import libcore.util.EmptyArray;
@@ -280,8 +282,8 @@ public class BatteryStatsImpl extends BatteryStats {
= new KernelMemoryBandwidthStats();
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
- private final CpuUsageDetails mCpuUsageDetails = new CpuUsageDetails();
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private final PowerStatsAggregator mPowerStatsAggregator;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -613,15 +615,8 @@ public class BatteryStatsImpl extends BatteryStats {
LongArrayMultiStateCounter onBatteryScreenOffCounter =
u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
- if (isUsageHistoryEnabled()) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs,
- deltaContainer);
- recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- } else {
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
- }
+
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
if (u.mChildUids != null) {
@@ -635,25 +630,12 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
- if (isUsageHistoryEnabled()) {
- recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- }
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
}
}
- private void recordCpuUsage(int uid, LongArrayMultiStateCounter.LongArrayContainer cpuUsage,
- long elapsedRealtimeMs, long uptimeMs) {
- if (!cpuUsage.combineValues(mCpuUsageDetails.cpuUsageMs, mCpuPowerBracketMap)) {
- return;
- }
-
- mCpuUsageDetails.uid = uid;
- mHistory.recordCpuUsage(elapsedRealtimeMs, uptimeMs, mCpuUsageDetails);
- }
-
/**
* Removes kernel CPU stats for removed UIDs, in the order they were added to the
* mPendingRemovedUids queue.
@@ -674,6 +656,10 @@ public class BatteryStatsImpl extends BatteryStats {
*/
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
public void updateCpuTimesForAllUids() {
+ if (mCpuPowerStatsCollector != null) {
+ mCpuPowerStatsCollector.schedule();
+ }
+
synchronized (BatteryStatsImpl.this) {
if (!trackPerProcStateCpuTimes()) {
return;
@@ -705,16 +691,8 @@ public class BatteryStatsImpl extends BatteryStats {
u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
if (uid == parentUid || Process.isSdkSandboxUid(uid)) {
- if (isUsageHistoryEnabled()) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
- elapsedRealtimeMs, deltaContainer);
- recordCpuUsage(parentUid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- } else {
- mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
- elapsedRealtimeMs);
- }
+ mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+ elapsedRealtimeMs);
mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryScreenOffCounter,
elapsedRealtimeMs);
} else {
@@ -727,9 +705,6 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
- if (isUsageHistoryEnabled()) {
- recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- }
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
@@ -860,6 +835,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
new HistoryStepDetailsCalculatorImpl();
+ private final PowerStats.DescriptorRegistry mPowerStatsDescriptorRegistry =
+ new PowerStats.DescriptorRegistry();
private boolean mHaveBatteryLevel = false;
private boolean mBatteryPluggedIn;
@@ -1771,6 +1748,7 @@ public class BatteryStatsImpl extends BatteryStats {
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
mCpuPowerStatsCollector = null;
+ mPowerStatsAggregator = null;
}
private void init(Clock clock) {
@@ -4403,6 +4381,12 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteCurrentTimeChangedLocked(long currentTimeMs,
long elapsedRealtimeMs, long uptimeMs) {
mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs);
+ adjustStartClockTime(currentTimeMs);
+ }
+
+ private void adjustStartClockTime(long currentTimeMs) {
+ mStartClockTimeMs =
+ currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
}
@GuardedBy("this")
@@ -7674,18 +7658,6 @@ public class BatteryStatsImpl extends BatteryStats {
return names;
}
- /**
- * Adds energy consumer delta to battery history.
- */
- @GuardedBy("this")
- public void recordEnergyConsumerDetailsLocked(long elapsedRealtimeMs,
- long uptimeMs, EnergyConsumerDetails energyConsumerDetails) {
- if (isUsageHistoryEnabled()) {
- mHistory.recordEnergyConsumerDetails(elapsedRealtimeMs, uptimeMs,
- energyConsumerDetails);
- }
- }
-
@GuardedBy("this")
@Override public long getStartClockTime() {
final long currentTimeMs = mClock.currentTimeMillis();
@@ -7696,9 +7668,8 @@ public class BatteryStatsImpl extends BatteryStats {
// the previous time was completely bogus. So we are going to figure out a
// new time based on how much time has elapsed since we started counting.
mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
- currentTimeMs
- );
- return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
+ currentTimeMs);
+ adjustStartClockTime(currentTimeMs);
}
return mStartClockTimeMs;
}
@@ -10589,6 +10560,10 @@ public class BatteryStatsImpl extends BatteryStats {
final int batteryConsumerProcessState =
mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
+ if (mBsi.mSystemReady && Flags.streamlinedBatteryStats()) {
+ mBsi.mHistory.recordProcessStateChange(elapsedRealtimeMs, uptimeMs, mUid,
+ batteryConsumerProcessState);
+ }
getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
getMobileRadioActiveTimeCounter()
@@ -10972,6 +10947,19 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
+ mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+
+ PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
+ builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN)
+ .trackUidStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN,
+ PowerStatsAggregator.STATE_PROCESS_STATE);
+
+ mPowerStatsAggregator = builder.build();
mStartCount++;
initTimersAndCounters();
@@ -10991,6 +10979,14 @@ public class BatteryStatsImpl extends BatteryStats {
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
}
+ private void recordPowerStats(PowerStats stats) {
+ if (stats.durationMs > 0) {
+ synchronized (this) {
+ mHistory.recordPowerStats(mClock.elapsedRealtime(), mClock.uptimeMillis(), stats);
+ }
+ }
+ }
+
@VisibleForTesting
protected void initTimersAndCounters() {
mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase);
@@ -11100,14 +11096,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- int cpuPowerBracketCount = mPowerProfile.getCpuPowerBracketCount();
- mCpuUsageDetails.cpuBracketDescriptions = new String[cpuPowerBracketCount];
- mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount];
- for (int i = 0; i < cpuPowerBracketCount; i++) {
- mCpuUsageDetails.cpuBracketDescriptions[i] =
- mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, i);
- }
-
if (mEstimatedBatteryCapacityMah == -1) {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
@@ -11483,8 +11471,9 @@ public class BatteryStatsImpl extends BatteryStats {
* Creates an iterator for battery stats history.
*/
@Override
- public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
- return mHistory.copy().iterate();
+ public BatteryStatsHistoryIterator iterateBatteryStatsHistory(long startTimeMs,
+ long endTimeMs) {
+ return mHistory.copy().iterate(startTimeMs, endTimeMs);
}
@Override
@@ -15233,11 +15222,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- boolean isUsageHistoryEnabled() {
- return mConstants.RECORD_USAGE_HISTORY;
- }
-
- @GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
registerUsbStateReceiver(context);
@@ -15348,8 +15332,6 @@ public class BatteryStatsImpl extends BatteryStats {
public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
- public static final String KEY_RECORD_USAGE_HISTORY =
- "record_usage_history";
public static final String KEY_PER_UID_MODEM_POWER_MODEL =
"per_uid_modem_power_model";
public static final String KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION =
@@ -15400,7 +15382,6 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
- private static final boolean DEFAULT_RECORD_USAGE_HISTORY = false;
@PerUidModemPowerModel
private static final int DEFAULT_PER_UID_MODEM_MODEL =
PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX;
@@ -15422,7 +15403,6 @@ public class BatteryStatsImpl extends BatteryStats {
public int MAX_HISTORY_FILES;
public int MAX_HISTORY_BUFFER; /*Bytes*/
public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
- public boolean RECORD_USAGE_HISTORY = DEFAULT_RECORD_USAGE_HISTORY;
public int PER_UID_MODEM_MODEL = DEFAULT_PER_UID_MODEM_MODEL;
public boolean PHONE_ON_EXTERNAL_STATS_COLLECTION =
DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION;
@@ -15503,8 +15483,6 @@ public class BatteryStatsImpl extends BatteryStats {
DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
- RECORD_USAGE_HISTORY = mParser.getBoolean(
- KEY_RECORD_USAGE_HISTORY, DEFAULT_RECORD_USAGE_HISTORY);
final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL,
"");
PER_UID_MODEM_MODEL = getPerUidModemModel(perUidModemModel);
@@ -15582,8 +15560,6 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println(MAX_HISTORY_BUFFER/1024);
pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("=");
pw.println(BATTERY_CHARGED_DELAY_MS);
- pw.print(KEY_RECORD_USAGE_HISTORY); pw.print("=");
- pw.println(RECORD_USAGE_HISTORY);
pw.print(KEY_PER_UID_MODEM_POWER_MODEL); pw.print("=");
pw.println(getPerUidModemModelName(PER_UID_MODEM_MODEL));
pw.print(KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION); pw.print("=");
@@ -15732,6 +15708,14 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuPowerStatsCollector.collectAndDump(pw);
}
+ /**
+ * Aggregates power stats between the specified times and prints them.
+ */
+ public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
+ mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
+ stats-> stats.dump(pw));
+ }
+
private final Runnable mWriteAsyncRunnable = () -> {
synchronized (BatteryStatsImpl.this) {
writeSyncLocked();
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
new file mode 100644
index 000000000000..5b3fe064d79a
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.power.stats;
+
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+
+class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
+
+ CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
+ MultiStateStats.States[] uidStates) {
+ super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 14017467e60f..376ca897fbd1 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -16,7 +16,9 @@
package com.android.server.power.stats;
+import android.os.BatteryConsumer;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.util.SparseArray;
import com.android.internal.annotations.Keep;
@@ -42,7 +44,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
private final int mUidStatsSize;
// Reusable instance
- private final PowerStats mCpuPowerStats = new PowerStats();
+ private final PowerStats mCpuPowerStats;
private long mLastUpdateTimestampNanos;
public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
@@ -69,6 +71,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
}
mUidStatsSize = powerProfile.getCpuPowerBracketCount();
mTempUidStats = new long[mUidStatsSize];
+
+ mCpuPowerStats = new PowerStats(
+ new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize,
+ new PersistableBundle()));
}
/**
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
index 939a08ba0b6a..7f50ae02aa39 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
@@ -23,7 +23,6 @@ import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
-import android.os.BatteryStats.EnergyConsumerDetails;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -85,8 +84,6 @@ public class EnergyConsumerSnapshot {
*/
private final SparseArray<SparseLongArray> mAttributionSnapshots;
- private EnergyConsumerDetails mEnergyConsumerDetails;
-
/**
* Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
* exist and what their details are.
@@ -423,122 +420,4 @@ public class EnergyConsumerSnapshot {
// since the last snapshot. Round off to the nearest whole long.
return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
}
-
- /**
- * Converts the EnergyConsumerDeltaData object to EnergyConsumerDetails, which can
- * be saved in battery history.
- */
- EnergyConsumerDetails getEnergyConsumerDetails(
- EnergyConsumerDeltaData delta) {
- if (mEnergyConsumerDetails == null) {
- mEnergyConsumerDetails = createEnergyConsumerDetails();
- }
-
- final long[] chargeUC = mEnergyConsumerDetails.chargeUC;
- for (int i = 0; i < mEnergyConsumerDetails.consumers.length; i++) {
- EnergyConsumerDetails.EnergyConsumer energyConsumer =
- mEnergyConsumerDetails.consumers[i];
- switch (energyConsumer.type) {
- case EnergyConsumerType.BLUETOOTH:
- chargeUC[i] = delta.bluetoothChargeUC;
- break;
- case EnergyConsumerType.CPU_CLUSTER:
- if (delta.cpuClusterChargeUC != null) {
- chargeUC[i] = delta.cpuClusterChargeUC[energyConsumer.ordinal];
- } else {
- chargeUC[i] = UNAVAILABLE;
- }
- break;
- case EnergyConsumerType.DISPLAY:
- if (delta.displayChargeUC != null) {
- chargeUC[i] = delta.displayChargeUC[energyConsumer.ordinal];
- } else {
- chargeUC[i] = UNAVAILABLE;
- }
- break;
- case EnergyConsumerType.GNSS:
- chargeUC[i] = delta.gnssChargeUC;
- break;
- case EnergyConsumerType.MOBILE_RADIO:
- chargeUC[i] = delta.mobileRadioChargeUC;
- break;
- case EnergyConsumerType.WIFI:
- chargeUC[i] = delta.wifiChargeUC;
- break;
- case EnergyConsumerType.CAMERA:
- chargeUC[i] = delta.cameraChargeUC;
- break;
- case EnergyConsumerType.OTHER:
- if (delta.otherTotalChargeUC != null) {
- chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal];
- } else {
- chargeUC[i] = UNAVAILABLE;
- }
- break;
- default:
- chargeUC[i] = UNAVAILABLE;
- break;
- }
- }
- return mEnergyConsumerDetails;
- }
-
- private EnergyConsumerDetails createEnergyConsumerDetails() {
- EnergyConsumerDetails details = new EnergyConsumerDetails();
- details.consumers =
- new EnergyConsumerDetails.EnergyConsumer[mEnergyConsumers.size()];
- for (int i = 0; i < mEnergyConsumers.size(); i++) {
- EnergyConsumer energyConsumer = mEnergyConsumers.valueAt(i);
- EnergyConsumerDetails.EnergyConsumer consumer =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer.type = energyConsumer.type;
- consumer.ordinal = energyConsumer.ordinal;
- switch (consumer.type) {
- case EnergyConsumerType.BLUETOOTH:
- consumer.name = "BLUETOOTH";
- break;
- case EnergyConsumerType.CPU_CLUSTER:
- consumer.name = "CPU";
- break;
- case EnergyConsumerType.DISPLAY:
- consumer.name = "DISPLAY";
- break;
- case EnergyConsumerType.GNSS:
- consumer.name = "GNSS";
- break;
- case EnergyConsumerType.MOBILE_RADIO:
- consumer.name = "MOBILE_RADIO";
- break;
- case EnergyConsumerType.WIFI:
- consumer.name = "WIFI";
- break;
- case EnergyConsumerType.OTHER:
- consumer.name = sanitizeCustomBucketName(energyConsumer.name);
- break;
- default:
- consumer.name = "UNKNOWN";
- break;
- }
- if (consumer.type != EnergyConsumerType.OTHER) {
- boolean hasOrdinal = consumer.ordinal != 0;
- if (!hasOrdinal) {
- // See if any other EnergyConsumer of the same type has an ordinal
- for (int j = 0; j < mEnergyConsumers.size(); j++) {
- EnergyConsumer aConsumer = mEnergyConsumers.valueAt(j);
- if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
- hasOrdinal = true;
- break;
- }
- }
- }
- if (hasOrdinal) {
- consumer.name = consumer.name + "/" + energyConsumer.ordinal;
- }
- }
- details.consumers[i] = consumer;
- }
-
- details.chargeUC = new long[details.consumers.length];
- return details;
- }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
new file mode 100644
index 000000000000..686268fea22b
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -0,0 +1,232 @@
+/*
+ * 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.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.util.Collection;
+
+/**
+ * Aggregated power stats for a specific power component (e.g. CPU, WiFi, etc). This class
+ * treats stats as arrays of nonspecific longs. Subclasses contain specific logic to interpret those
+ * longs and use them for calculations such as power attribution. They may use meta-data supplied
+ * as part of the {@link PowerStats.Descriptor}.
+ */
+class PowerComponentAggregatedPowerStats {
+ public final int powerComponentId;
+ private final MultiStateStats.States[] mDeviceStateConfig;
+ private final MultiStateStats.States[] mUidStateConfig;
+ private final int[] mDeviceStates;
+ private final long[] mDeviceStateTimestamps;
+
+ private MultiStateStats.Factory mStatsFactory;
+ private MultiStateStats.Factory mUidStatsFactory;
+ private PowerStats.Descriptor mPowerStatsDescriptor;
+ private MultiStateStats mDeviceStats;
+ private final SparseArray<UidStats> mUidStats = new SparseArray<>();
+
+ private static class UidStats {
+ public int[] states;
+ public long[] stateTimestampMs;
+ public MultiStateStats stats;
+ }
+
+ PowerComponentAggregatedPowerStats(int powerComponentId,
+ MultiStateStats.States[] deviceStates,
+ MultiStateStats.States[] uidStates) {
+ this.powerComponentId = powerComponentId;
+ mDeviceStateConfig = deviceStates;
+ mUidStateConfig = uidStates;
+ mDeviceStates = new int[mDeviceStateConfig.length];
+ mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
+ }
+
+ void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ mDeviceStates[stateId] = state;
+ mDeviceStateTimestamps[stateId] = time;
+
+ if (mDeviceStateConfig[stateId].isTracked()) {
+ if (mDeviceStats != null || createDeviceStats()) {
+ mDeviceStats.setState(stateId, state, time);
+ }
+ }
+
+ if (mUidStateConfig[stateId].isTracked()) {
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null || createUidStats(uidStats)) {
+ uidStats.stats.setState(stateId, state, time);
+ }
+ }
+ }
+ }
+
+ void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ long time) {
+ if (!mUidStateConfig[stateId].isTracked()) {
+ return;
+ }
+
+ UidStats uidStats = getUidStats(uid);
+ uidStats.states[stateId] = state;
+ uidStats.stateTimestampMs[stateId] = time;
+
+ if (uidStats.stats != null || createUidStats(uidStats)) {
+ uidStats.stats.setState(stateId, state, time);
+ }
+ }
+
+ boolean isCompatible(PowerStats powerStats) {
+ return mPowerStatsDescriptor == null || mPowerStatsDescriptor.equals(powerStats.descriptor);
+ }
+
+ void addPowerStats(PowerStats powerStats, long timestampMs) {
+ mPowerStatsDescriptor = powerStats.descriptor;
+
+ if (mDeviceStats == null) {
+ if (mStatsFactory == null) {
+ mStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
+ mUidStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
+ }
+
+ createDeviceStats();
+ }
+
+ mDeviceStats.increment(powerStats.stats, timestampMs);
+
+ for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) {
+ int uid = powerStats.uidStats.keyAt(i);
+ PowerComponentAggregatedPowerStats.UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats);
+ }
+ uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
+ }
+ }
+
+ void reset() {
+ mPowerStatsDescriptor = null;
+ mStatsFactory = null;
+ mUidStatsFactory = null;
+ mDeviceStats = null;
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ mUidStats.valueAt(i).stats = null;
+ }
+ }
+
+ private UidStats getUidStats(int uid) {
+ // TODO(b/292247660): map isolated and sandbox UIDs
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new UidStats();
+ uidStats.states = new int[mUidStateConfig.length];
+ uidStats.stateTimestampMs = new long[mUidStateConfig.length];
+ mUidStats.put(uid, uidStats);
+ }
+ return uidStats;
+ }
+
+ void collectUids(Collection<Integer> uids) {
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ if (mUidStats.valueAt(i).stats != null) {
+ uids.add(mUidStats.keyAt(i));
+ }
+ }
+ }
+
+ boolean getDeviceStats(long[] outValues, int[] deviceStates) {
+ if (deviceStates.length != mDeviceStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + deviceStates.length
+ + " expected: " + mDeviceStateConfig.length);
+ }
+ if (mDeviceStats != null) {
+ mDeviceStats.getStats(outValues, deviceStates);
+ return true;
+ }
+ return false;
+ }
+
+ boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
+ if (uidStates.length != mUidStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + uidStates.length
+ + " expected: " + mUidStateConfig.length);
+ }
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats != null && uidStats.stats != null) {
+ uidStats.stats.getStats(outValues, uidStates);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean createDeviceStats() {
+ if (mStatsFactory == null) {
+ return false;
+ }
+
+ mDeviceStats = mStatsFactory.create();
+ for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+ mDeviceStats.setState(stateId, mDeviceStates[stateId],
+ mDeviceStateTimestamps[stateId]);
+ }
+ return true;
+ }
+
+ private boolean createUidStats(UidStats uidStats) {
+ if (mUidStatsFactory == null) {
+ return false;
+ }
+
+ uidStats.stats = mUidStatsFactory.create();
+ for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, mDeviceStates[stateId],
+ mDeviceStateTimestamps[stateId]);
+ }
+ for (int stateId = mDeviceStateConfig.length; stateId < mUidStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, uidStats.states[stateId],
+ uidStats.stateTimestampMs[stateId]);
+ }
+ return true;
+ }
+
+ void dumpDevice(IndentingPrintWriter ipw) {
+ if (mDeviceStats != null) {
+ ipw.println(mPowerStatsDescriptor.name);
+ ipw.increaseIndent();
+ mDeviceStats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+
+ void dumpUid(IndentingPrintWriter ipw, int uid) {
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats != null && uidStats.stats != null) {
+ ipw.println(mPowerStatsDescriptor.name);
+ ipw.increaseIndent();
+ uidStats.stats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
new file mode 100644
index 000000000000..6a1c1da93163
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -0,0 +1,226 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+class PowerStatsAggregator {
+ public static final int STATE_POWER = 0;
+ public static final int STATE_SCREEN = 1;
+ public static final int STATE_PROCESS_STATE = 2;
+
+ @IntDef({
+ STATE_POWER,
+ STATE_SCREEN,
+ STATE_PROCESS_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrackedState {
+ }
+
+ static final int POWER_STATE_BATTERY = 0;
+ static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
+ static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+ static final int SCREEN_STATE_ON = 0;
+ static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
+ static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+ static final String[] STATE_LABELS_PROCESS_STATE;
+
+ static {
+ String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+ procStateLabels[i] = BatteryConsumer.processStateToString(i);
+ }
+ STATE_LABELS_PROCESS_STATE = procStateLabels;
+ }
+
+ private final BatteryStatsHistory mHistory;
+ private final AggregatedPowerStats mStats;
+
+ private PowerStatsAggregator(BatteryStatsHistory history,
+ AggregatedPowerStats aggregatedPowerStats) {
+ mHistory = history;
+ mStats = aggregatedPowerStats;
+ }
+
+ /**
+ * Iterates of the battery history and aggregates power stats between the specified times.
+ * The start and end are specified in the battery-stats monotonic time, which is the
+ * adjusted elapsed time found in HistoryItem.time.
+ * <p>
+ * The aggregated stats are sent to the consumer. One aggregation pass may produce
+ * multiple sets of aggregated stats if there was an incompatible change that occurred in the
+ * middle of the recorded battery history.
+ * <p>
+ * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
+ * the stats in the <code>accept</code> method and never cache it.
+ */
+ void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+ Consumer<AggregatedPowerStats> consumer) {
+ mStats.reset();
+
+ int currentBatteryState = POWER_STATE_BATTERY;
+ int currentScreenState = SCREEN_STATE_OTHER;
+ long baseTime = -1;
+ long lastTime = 0;
+ try (BatteryStatsHistoryIterator iterator =
+ mHistory.copy().iterate(startTimeMs, endTimeMs)) {
+ while (iterator.hasNext()) {
+ BatteryStats.HistoryItem item = iterator.next();
+
+ if (baseTime < 0) {
+ mStats.setStartTime(item.currentTime);
+ baseTime = item.time;
+ }
+
+ lastTime = item.time;
+
+ int batteryState =
+ (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
+ ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+ if (batteryState != currentBatteryState) {
+ mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+ currentBatteryState = batteryState;
+ }
+
+ int screenState =
+ (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
+ ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+ if (screenState != currentScreenState) {
+ mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+ currentScreenState = screenState;
+ }
+
+ if (item.processStateChange != null) {
+ mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+ item.processStateChange.processState, item.time);
+ }
+
+ if (item.powerStats != null) {
+ if (!mStats.isCompatible(item.powerStats)) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ mStats.reset();
+ mStats.setStartTime(item.currentTime);
+ baseTime = lastTime = item.time;
+ }
+ mStats.addPowerStats(item.powerStats, item.time);
+ }
+ }
+ }
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ }
+
+ static class Builder {
+ static class PowerComponentAggregateStatsBuilder {
+ private final int mPowerComponentId;
+ private @TrackedState int[] mTrackedDeviceStates;
+ private @TrackedState int[] mTrackedUidStates;
+
+ PowerComponentAggregateStatsBuilder(int powerComponentId) {
+ this.mPowerComponentId = powerComponentId;
+ }
+
+ public PowerComponentAggregateStatsBuilder trackDeviceStates(
+ @TrackedState int... states) {
+ mTrackedDeviceStates = states;
+ return this;
+ }
+
+ public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
+ mTrackedUidStates = states;
+ return this;
+ }
+
+ private PowerComponentAggregatedPowerStats build() {
+ MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
+ new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
+ PowerStatsAggregator.STATE_LABELS_POWER),
+ new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
+ PowerStatsAggregator.STATE_LABELS_SCREEN),
+ };
+
+ MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
+ new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
+ PowerStatsAggregator.STATE_LABELS_POWER),
+ new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
+ PowerStatsAggregator.STATE_LABELS_SCREEN),
+ new MultiStateStats.States(
+ isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+ PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
+ };
+
+ switch (mPowerComponentId) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return new CpuAggregatedPowerStats(deviceStates, uidStates);
+ default:
+ return new PowerComponentAggregatedPowerStats(mPowerComponentId,
+ deviceStates, uidStates);
+ }
+ }
+
+ private boolean isTracked(int[] trackedStates, int state) {
+ if (trackedStates == null) {
+ return false;
+ }
+
+ for (int trackedState : trackedStates) {
+ if (trackedState == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final BatteryStatsHistory mHistory;
+ private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
+ new ArrayList<>();
+
+ Builder(BatteryStatsHistory history) {
+ mHistory = history;
+ }
+
+ PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
+ PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
+ powerComponentId);
+ mPowerComponents.add(builder);
+ return builder;
+ }
+
+ PowerStatsAggregator build() {
+ return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
+ mPowerComponents.stream()
+ .map(PowerComponentAggregateStatsBuilder::build)
+ .toArray(PowerComponentAggregatedPowerStats[]::new)));
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 5ea1929e185f..8ab45070a017 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -23,6 +23,7 @@ android_test {
"androidx.test.uiautomator_uiautomator",
"mockito-target-minus-junit4",
"servicestests-utils",
+ "platform-test-annotations",
"flag-junit",
],
diff --git a/services/tests/powerstatstests/AndroidManifest.xml b/services/tests/powerstatstests/AndroidManifest.xml
index d3a88d2bc38c..d6898a1f6589 100644
--- a/services/tests/powerstatstests/AndroidManifest.xml
+++ b/services/tests/powerstatstests/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<queries>
<package android:name="com.android.coretests.apps.bstatstestapp" />
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 4fde73bd8408..77124d0120f7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -32,6 +32,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Future;
@@ -49,6 +51,7 @@ public class BatteryStatsHistoryIteratorTest {
@Before
public void setup() {
final File historyDir = createTemporaryDirectory(getClass().getSimpleName());
+ mMockClock.currentTime = 3000;
mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
mBatteryStats.setDummyExternalStatsSync(mExternalStatsSync);
mBatteryStats.setRecordAllHistoryLocked(true);
@@ -70,20 +73,10 @@ public class BatteryStatsHistoryIteratorTest {
}
@Test
- public void testIterator() {
- mMockClock.realtime = 1000;
- mMockClock.uptime = 1000;
+ public void unconstrainedIteration() {
+ prepareHistory();
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
- 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
- 1_000_000, 1_000_000);
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
- 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
- 2_000_000, 2_000_000);
- mBatteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
- mBatteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
-
- final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
@@ -116,23 +109,75 @@ public class BatteryStatsHistoryIteratorTest {
assertThat(iterator.next()).isNull();
}
- // Test history that spans multiple buffers and uses more than 32k different strings.
@Test
- public void tagsLongHistory() {
+ public void constrainedIteration() {
+ prepareHistory();
+
+ // Initial time is 3000
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
+ 3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
+ 1003_000L, 2003_000L, 2004_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
+ 3_000L, 3_000L, 1003_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
+ 1003_000L, 2003_000L);
+ }
+
+ private void prepareHistory() {
+ mMockClock.realtime = 1000;
+ mMockClock.uptime = 1000;
+ mMockClock.currentTime = 3000;
+
mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
1_000_000, 1_000_000);
+ mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
+ 2_000_000, 2_000_000);
+ mBatteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
+ mBatteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
+ }
+
+ private void assertIncludedEvents(BatteryStatsHistoryIterator iterator,
+ Long... expectedTimestamps) {
+ ArrayList<Long> actualTimestamps = new ArrayList<>();
+ while (iterator.hasNext()) {
+ BatteryStats.HistoryItem item = iterator.next();
+ actualTimestamps.add(item.currentTime);
+ }
+ assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
+ }
+
+ // Test history that spans multiple buffers and uses more than 32k different strings.
+ @Test
+ public void tagsLongHistory() {
+ mMockClock.currentTime = 1_000_000;
+ mMockClock.realtime = 1_000_000;
+ mMockClock.uptime = 1_000_000;
+
+ mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, mMockClock.realtime,
+ mMockClock.uptime, mMockClock.currentTime);
// More than 32k strings
final int eventCount = 0x7FFF + 100;
for (int i = 0; i < eventCount; i++) {
// Names repeat in order to verify de-duping of identical history tags.
String name = "a" + (i % 10);
- mBatteryStats.noteAlarmStartLocked(name, null, APP_UID, 3_000_000, 2_000_000);
- mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID, 3_500_000, 2_500_000);
+ mMockClock.currentTime += 1_000_000;
+ mMockClock.realtime += 1_000_000;
+ mMockClock.uptime += 1_000_000;
+ mBatteryStats.noteAlarmStartLocked(name, null, APP_UID,
+ mMockClock.realtime, mMockClock.uptime);
+ mMockClock.currentTime += 500_000;
+ mMockClock.realtime += 500_000;
+ mMockClock.uptime += 500_000;
+ mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID,
+ mMockClock.realtime, mMockClock.uptime);
}
- final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
@@ -146,12 +191,6 @@ public class BatteryStatsHistoryIteratorTest {
assertThat(item.eventTag).isNull();
assertThat(item.time).isEqualTo(1_000_000);
- assertThat(item = iterator.next()).isNotNull();
- assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
- assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
- assertThat(item.eventTag).isNull();
- assertThat(item.time).isEqualTo(2_000_000);
-
for (int i = 0; i < eventCount; i++) {
String name = "a" + (i % 10);
do {
@@ -205,7 +244,7 @@ public class BatteryStatsHistoryIteratorTest {
mExternalStatsSync.updateCpuStats(300, 7_100_000, 4_100_000);
- final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index f2cbef69c8e5..f22296a6261c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -24,13 +24,16 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
-import android.os.BatteryStats.CpuUsageDetails;
-import android.os.BatteryStats.EnergyConsumerDetails;
import android.os.BatteryStats.HistoryItem;
import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.UserHandle;
import android.telephony.NetworkRegistrationInfo;
+import android.util.AtomicFile;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -38,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.PowerStats;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +75,7 @@ public class BatteryStatsHistoryTest {
private BatteryStatsHistory.TraceDelegate mTracer;
@Mock
private BatteryStatsHistory.HistoryStepDetailsCalculator mStepDetailsCalculator;
+ private List<String> mReadFiles = new ArrayList<>();
@Before
public void setUp() {
@@ -85,8 +90,17 @@ public class BatteryStatsHistoryTest {
}
}
mHistoryDir.delete();
+
+ mClock.realtime = 123;
+
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- mStepDetailsCalculator, mClock, mTracer);
+ mStepDetailsCalculator, mClock, mTracer) {
+ @Override
+ public boolean readFileToParcel(Parcel out, AtomicFile file) {
+ mReadFiles.add(file.getBaseFile().getName());
+ return super.readFileToParcel(out, file);
+ }
+ };
when(mStepDetailsCalculator.getHistoryStepDetails())
.thenReturn(new BatteryStats.HistoryStepDetails());
@@ -179,70 +193,165 @@ public class BatteryStatsHistoryTest {
@Test
public void testConstruct() {
createActiveFile(mHistory);
- verifyFileNumbers(mHistory, Arrays.asList(0));
- verifyActiveFile(mHistory, "0.bin");
+ verifyFileNames(mHistory, Arrays.asList("123.bh"));
+ verifyActiveFile(mHistory, "123.bh");
}
@Test
public void testStartNextFile() {
- List<Integer> fileList = new ArrayList<>();
- fileList.add(0);
+ mClock.realtime = 123;
+
+ List<String> fileList = new ArrayList<>();
+ fileList.add("123.bh");
createActiveFile(mHistory);
// create file 1 to 31.
for (int i = 1; i < 32; i++) {
- fileList.add(i);
+ mClock.realtime = 1000 * i;
+ fileList.add(mClock.realtime + ".bh");
+
mHistory.startNextFile();
createActiveFile(mHistory);
- verifyFileNumbers(mHistory, fileList);
- verifyActiveFile(mHistory, i + ".bin");
+ verifyFileNames(mHistory, fileList);
+ verifyActiveFile(mHistory, mClock.realtime + ".bh");
}
// create file 32
+ mClock.realtime = 1000 * 32;
mHistory.startNextFile();
createActiveFile(mHistory);
- fileList.add(32);
+ fileList.add("32000.bh");
fileList.remove(0);
// verify file 0 is deleted.
- verifyFileDeleted("0.bin");
- verifyFileNumbers(mHistory, fileList);
- verifyActiveFile(mHistory, "32.bin");
+ verifyFileDeleted("123.bh");
+ verifyFileNames(mHistory, fileList);
+ verifyActiveFile(mHistory, "32000.bh");
// create file 33
+ mClock.realtime = 1000 * 33;
mHistory.startNextFile();
createActiveFile(mHistory);
// verify file 1 is deleted
- fileList.add(33);
+ fileList.add("33000.bh");
fileList.remove(0);
- verifyFileDeleted("1.bin");
- verifyFileNumbers(mHistory, fileList);
- verifyActiveFile(mHistory, "33.bin");
-
- assertEquals(0, mHistory.getHistoryUsedSize());
+ verifyFileDeleted("1000.bh");
+ verifyFileNames(mHistory, fileList);
+ verifyActiveFile(mHistory, "33000.bh");
// create a new BatteryStatsHistory object, it will pick up existing history files.
BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
null, mClock, mTracer);
// verify constructor can pick up all files from file system.
- verifyFileNumbers(history2, fileList);
- verifyActiveFile(history2, "33.bin");
+ verifyFileNames(history2, fileList);
+ verifyActiveFile(history2, "33000.bh");
+
+ mClock.realtime = 1234567;
history2.reset();
createActiveFile(history2);
+
// verify all existing files are deleted.
- for (int i = 2; i < 33; ++i) {
- verifyFileDeleted(i + ".bin");
+ for (String file : fileList) {
+ verifyFileDeleted(file);
}
// verify file 0 is created
- verifyFileNumbers(history2, Arrays.asList(0));
- verifyActiveFile(history2, "0.bin");
+ verifyFileNames(history2, Arrays.asList("1234567.bh"));
+ verifyActiveFile(history2, "1234567.bh");
// create file 1.
+ mClock.realtime = 2345678;
+
history2.startNextFile();
createActiveFile(history2);
- verifyFileNumbers(history2, Arrays.asList(0, 1));
- verifyActiveFile(history2, "1.bin");
+ verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
+ verifyActiveFile(history2, "2345678.bh");
+ }
+
+ @Test
+ public void unconstrainedIteration() {
+ prepareMultiFileHistory();
+
+ mReadFiles.clear();
+
+ // Prepare history for iteration
+ mHistory.iterate(0, 0);
+
+ Parcel parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("123.bh");
+
+ // Skip to the end to force reading the next parcel
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("1000.bh");
+
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("2000.bh");
+
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNull();
+ assertThat(mReadFiles).isEmpty();
+ }
+
+ @Test
+ public void constrainedIteration() {
+ prepareMultiFileHistory();
+
+ mReadFiles.clear();
+
+ // Prepare history for iteration
+ mHistory.iterate(1000, 3000);
+
+ Parcel parcel = mHistory.getNextParcel(1000, 3000);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("1000.bh");
+
+ // Skip to the end to force reading the next parcel
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(1000, 3000);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("2000.bh");
+
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(1000, 3000);
+ assertThat(parcel).isNull();
+ assertThat(mReadFiles).isEmpty();
+ }
+
+ private void prepareMultiFileHistory() {
+ mHistory.forceRecordAllHistory();
+
+ mClock.realtime = 1000;
+ mClock.uptime = 1000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
+
+ mHistory.startNextFile(); // 1000.bh
+
+ mClock.realtime = 2000;
+ mClock.uptime = 2000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
+
+ mHistory.startNextFile(); // 2000.bh
+
+ mClock.realtime = 3000;
+ mClock.uptime = 3000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ HistoryItem.EVENT_ALARM, "alarm", 42);
+
+ // Flush accumulated history to disk
+ mHistory.startNextFile();
}
private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -251,12 +360,11 @@ public class BatteryStatsHistoryTest {
assertTrue(expectedFile.exists());
}
- private void verifyFileNumbers(BatteryStatsHistory history, List<Integer> fileList) {
- assertEquals(fileList.size(), history.getFilesNumbers().size());
+ private void verifyFileNames(BatteryStatsHistory history, List<String> fileList) {
+ assertEquals(fileList.size(), history.getFilesNames().size());
for (int i = 0; i < fileList.size(); i++) {
- assertEquals(fileList.get(i), history.getFilesNumbers().get(i));
- final File expectedFile =
- new File(mHistoryDir, fileList.get(i) + ".bin");
+ assertEquals(fileList.get(i), history.getFilesNames().get(i));
+ final File expectedFile = new File(mHistoryDir, fileList.get(i));
assertTrue(expectedFile.exists());
}
}
@@ -267,6 +375,9 @@ public class BatteryStatsHistoryTest {
private void createActiveFile(BatteryStatsHistory history) {
final File file = history.getActiveFile().getBaseFile();
+ if (file.exists()) {
+ return;
+ }
try {
file.createNewFile();
} catch (IOException e) {
@@ -275,49 +386,18 @@ public class BatteryStatsHistoryTest {
}
@Test
- public void testRecordMeasuredEnergyDetails() {
- mHistory.forceRecordAllHistory();
- mHistory.startRecordingHistory(0, 0, /* reset */ true);
- mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
- 1234);
-
- EnergyConsumerDetails details = new EnergyConsumerDetails();
- EnergyConsumerDetails.EnergyConsumer consumer1 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer1.type = 42;
- consumer1.ordinal = 0;
- consumer1.name = "A";
-
- EnergyConsumerDetails.EnergyConsumer consumer2 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer2.type = 777;
- consumer2.ordinal = 0;
- consumer2.name = "B/0";
-
- EnergyConsumerDetails.EnergyConsumer consumer3 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer3.type = 777;
- consumer3.ordinal = 1;
- consumer3.name = "B/1";
-
- EnergyConsumerDetails.EnergyConsumer consumer4 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer4.type = 314;
- consumer4.ordinal = 1;
- consumer4.name = "C";
-
- details.consumers =
- new EnergyConsumerDetails.EnergyConsumer[]{consumer1, consumer2, consumer3,
- consumer4};
- details.chargeUC = new long[details.consumers.length];
- for (int i = 0; i < details.chargeUC.length; i++) {
- details.chargeUC[i] = 100L * i;
- }
- details.chargeUC[3] = BatteryStats.POWER_DATA_UNAVAILABLE;
-
- mHistory.recordEnergyConsumerDetails(200, 200, details);
-
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ public void recordPowerStats() {
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, 2,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.durationMs = 100;
+ powerStats.stats[0] = 200;
+ powerStats.uidStats.put(300, new long[]{400, 500});
+ powerStats.uidStats.put(600, new long[]{700, 800});
+
+ mHistory.recordPowerStats(200, 200, powerStats);
+
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
@@ -325,62 +405,10 @@ public class BatteryStatsHistoryTest {
String dump = toString(item, /* checkin */ false);
assertThat(dump).contains("+200ms");
- assertThat(dump).contains("ext=energy:A=0 B/0=100 B/1=200");
- assertThat(dump).doesNotContain("C=");
-
- String checkin = toString(item, /* checkin */ true);
- assertThat(checkin).contains("XE");
- assertThat(checkin).contains("A=0,B/0=100,B/1=200");
- assertThat(checkin).doesNotContain("C=");
- }
-
- @Test
- public void cpuUsageDetails() {
- mHistory.forceRecordAllHistory();
- mHistory.startRecordingHistory(0, 0, /* reset */ true);
- mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
- 1234);
-
- CpuUsageDetails details = new CpuUsageDetails();
- details.cpuBracketDescriptions = new String[] {"low", "Med", "HIGH"};
- details.uid = 10123;
- details.cpuUsageMs = new long[] { 100, 200, 300};
- mHistory.recordCpuUsage(200, 200, details);
-
- details.uid = 10321;
- details.cpuUsageMs = new long[] { 400, 500, 600};
- mHistory.recordCpuUsage(300, 300, details);
-
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
- assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
-
- assertThat(item = iterator.next()).isNotNull();
-
- String dump = toString(item, /* checkin */ false);
- assertThat(dump).contains("+200ms");
- assertThat(dump).contains("ext=cpu:u0a123: 100, 200, 300");
- assertThat(dump).contains("ext=cpu-bracket:0:low");
- assertThat(dump).contains("ext=cpu-bracket:1:Med");
- assertThat(dump).contains("ext=cpu-bracket:2:HIGH");
-
- String checkin = toString(item, /* checkin */ true);
- assertThat(checkin).contains("XB,3,0,low");
- assertThat(checkin).contains("XB,3,1,Med");
- assertThat(checkin).contains("XB,3,2,HIGH");
- assertThat(checkin).contains("XC,10123,100,200,300");
-
- assertThat(item = iterator.next()).isNotNull();
-
- dump = toString(item, /* checkin */ false);
- assertThat(dump).contains("+300ms");
- assertThat(dump).contains("ext=cpu:u0a321: 400, 500, 600");
- // Power bracket descriptions are written only once
- assertThat(dump).doesNotContain("ext=cpu-bracket");
-
- checkin = toString(item, /* checkin */ true);
- assertThat(checkin).doesNotContain("XB");
- assertThat(checkin).contains("XC,10321,400,500,600");
+ assertThat(dump).contains("duration=100");
+ assertThat(dump).contains("foo=[200]");
+ assertThat(dump).contains("300: [400, 500]");
+ assertThat(dump).contains("600: [700, 800]");
}
@Test
@@ -399,7 +427,7 @@ public class BatteryStatsHistoryTest {
mHistory.recordNrStateChangeEvent(500, 500,
NetworkRegistrationInfo.NR_STATE_NONE);
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
@@ -440,7 +468,7 @@ public class BatteryStatsHistoryTest {
mHistory.recordNrStateChangeEvent(500, 500,
NetworkRegistrationInfo.NR_STATE_NONE);
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
@@ -498,7 +526,7 @@ public class BatteryStatsHistoryTest {
mClock.uptime = 1_000_000;
// More than 32k strings
final int tagCount = 0x7FFF + 20;
- for (int tag = 0; tag < tagCount;) {
+ for (int tag = 0; tag < tagCount; ) {
mClock.realtime += 10;
mClock.uptime += 10;
mHistory.recordEvent(mClock.realtime, mClock.uptime, HistoryItem.EVENT_ALARM_START,
@@ -522,8 +550,8 @@ public class BatteryStatsHistoryTest {
int wakelockTagsUnpooled = 0;
int wakeReasonTagsPooled = 0;
int wakeReasonTagsUnpooled = 0;
- for (BatteryStatsHistoryIterator iterator = mHistory.iterate(); iterator.hasNext(); ) {
- HistoryItem item = iterator.next();
+ for (BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0); iterator.hasNext(); ) {
+ HistoryItem item = iterator.next();
if (item.cmd != HistoryItem.CMD_UPDATE) {
continue;
}
@@ -569,10 +597,40 @@ public class BatteryStatsHistoryTest {
assertThat(wakeReasonTagsUnpooled).isGreaterThan(0);
}
+ @Test
+ public void recordProcStateChange() {
+ mHistory.recordProcessStateChange(200, 200, 42, BatteryConsumer.PROCESS_STATE_BACKGROUND);
+ mHistory.recordProcessStateChange(300, 300, 42, BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ // Large UID, > 0xFFFFFF
+ mHistory.recordProcessStateChange(400, 400,
+ UserHandle.getUid(777, Process.LAST_ISOLATED_UID),
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
+ BatteryStats.HistoryItem item;
+ assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
+
+ assertThat(item = iterator.next()).isNotNull();
+
+ String dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+200ms");
+ assertThat(dump).contains("procstate: 42: bg");
+
+ assertThat(item = iterator.next()).isNotNull();
+ dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+300ms");
+ assertThat(dump).contains("procstate: 42: fg");
+
+ assertThat(item = iterator.next()).isNotNull();
+ dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+400ms");
+ assertThat(dump).contains("procstate: u777i999: fgs");
+ }
+
private String toString(BatteryStats.HistoryItem item, boolean checkin) {
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
- mHistoryPrinter.printNextItem(pw, item, 0, checkin, /* verbose */ false);
+ mHistoryPrinter.printNextItem(pw, item, 0, checkin, /* verbose */ true);
pw.flush();
return writer.toString();
}
@@ -596,6 +654,7 @@ public class BatteryStatsHistoryTest {
0xffffffffffffffffL};
// Parcel subarrays of different lengths and assert the size of the resulting parcel
+ testVarintParceler(Arrays.copyOfRange(values, 0, 0), 0);
testVarintParceler(Arrays.copyOfRange(values, 0, 1), 4); // v. 8
testVarintParceler(Arrays.copyOfRange(values, 0, 2), 4); // v. 16
testVarintParceler(Arrays.copyOfRange(values, 0, 3), 4); // v. 24
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 88b9522d4cb1..7ef1a3fd0d83 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -263,7 +263,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 220;
bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
- final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
@@ -319,7 +319,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 220;
bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
- final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
@@ -933,7 +933,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 5000;
bi.noteAlarmFinishLocked("foo", null, UID);
- BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
@@ -972,7 +972,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 5000;
bi.noteAlarmFinishLocked("foo", ws, UID);
- BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
index 28f4799656b7..6cd08653bc33 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
@@ -26,7 +26,6 @@ import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
-import android.os.BatteryStats;
import android.util.SparseArray;
import android.util.SparseLongArray;
@@ -238,17 +237,6 @@ public final class EnergyConsumerSnapshotTest {
}
@Test
- public void getMeasuredEnergyDetails() {
- final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(ALL_ID_CONSUMER_MAP);
- snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
- EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(RESULTS_1, VOLTAGE_1);
- BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
- assertThat(details.consumers).hasLength(4);
- assertThat(details.chargeUC).isEqualTo(new long[]{2667, 3200000, 0, 0});
- assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0");
- }
-
- @Test
public void testUpdateAndGetDelta_updatesCameraCharge() {
EnergyConsumer cameraConsumer =
createEnergyConsumer(7, 0, EnergyConsumerType.CAMERA, "CAMERA");
@@ -266,12 +254,8 @@ public final class EnergyConsumerSnapshotTest {
createEnergyConsumerResult(cameraConsumer.id, 90_000, null, null),
};
EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(result1, VOLTAGE_1);
-
- // Verify that the delta between the two results is reported.
- BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
- assertThat(details.consumers).hasLength(1);
long expectedDeltaUC = calculateChargeConsumedUC(60_000, VOLTAGE_1, 90_000, VOLTAGE_1);
- assertThat(details.chargeUC[0]).isEqualTo(expectedDeltaUC);
+ assertThat(delta.cameraChargeUC).isEqualTo(expectedDeltaUC);
}
private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
new file mode 100644
index 000000000000..4ecee9fe7d23
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.BatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MultiStateStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MultiStateStatsTest {
+
+ public static final int DIMENSION_COUNT = 2;
+
+ @Test
+ public void compositeStateIndex_allEnabled() {
+ MultiStateStats.Factory factory = makeFactory(true, true, true);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(BatteryConsumer.PROCESS_STATE_COUNT * 4)
+ .haveDifferentSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void compositeStateIndex_procStateTrackingDisabled() {
+ MultiStateStats.Factory factory = makeFactory(true, false, true);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(4)
+ .haveDifferentSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .haveSameSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, false, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void compositeStateIndex_screenTrackingDisabled() {
+ MultiStateStats.Factory factory = makeFactory(true, true, false);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(BatteryConsumer.PROCESS_STATE_COUNT * 2)
+ .haveDifferentSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .haveSameSerialStates(
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void compositeStateIndex_allDisabled() {
+ MultiStateStats.Factory factory = makeFactory(false, false, false);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(1)
+ .haveSameSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void tooManyStates() {
+ // 4 bits needed to represent
+ String[] labels = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};
+ // 4 * 10 = 40 bits needed to represent the composite state
+ MultiStateStats.States[] states = new MultiStateStats.States[10];
+ for (int i = 0; i < states.length; i++) {
+ states[i] = new MultiStateStats.States(true, labels);
+ }
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
+ assertThat(e.getMessage()).contains("40");
+ }
+
+ @Test
+ public void multiStateStats_aggregation() {
+ MultiStateStats.Factory factory = makeFactory(true, true, false);
+ MultiStateStats multiStateStats = factory.create();
+ multiStateStats.setState(0 /* batteryState */, 1 /* on */, 1000);
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000);
+ multiStateStats.setState(2 /* screenState */, 0 /* off */, 1000);
+
+ multiStateStats.increment(new long[]{100, 200}, 1000);
+
+ multiStateStats.setState(0 /* batteryState */, 0 /* off */, 2000);
+ multiStateStats.setState(2 /* screenState */, 1 /* on */, 2000); // untracked
+
+ multiStateStats.increment(new long[]{300, 500}, 3000);
+
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+ multiStateStats.increment(new long[]{200, 200}, 5000);
+
+ long[] stats = new long[DIMENSION_COUNT];
+ multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0});
+ // (400 - 100) * 0.5 + (600 - 400) * 0.5
+ assertThat(stats).isEqualTo(new long[]{250, 350});
+
+ multiStateStats.getStats(stats, new int[]{1, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0});
+ // (400 - 100) * 0.5 + (600 - 400) * 0
+ assertThat(stats).isEqualTo(new long[]{150, 250});
+
+ // Note that screen state does not affect the result, as it is untracked
+ multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_BACKGROUND, 1});
+ // (400 - 100) * 0 + (600 - 400) * 0.5
+ assertThat(stats).isEqualTo(new long[]{100, 100});
+
+ multiStateStats.getStats(stats, new int[]{1, BatteryConsumer.PROCESS_STATE_BACKGROUND, 0});
+ // Never been in this composite state
+ assertThat(stats).isEqualTo(new long[]{0, 0});
+ }
+
+ @Test
+ public void dump() {
+ MultiStateStats.Factory factory = makeFactory(true, true, false);
+ MultiStateStats multiStateStats = factory.create();
+ multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000);
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000);
+ multiStateStats.setState(2 /* screenState */, 0 /* off */, 1000);
+ multiStateStats.setState(0 /* batteryState */, 1 /* on */, 2000);
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000);
+ multiStateStats.increment(new long[]{100, 200}, 5000);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw, true);
+ multiStateStats.dump(pw);
+ assertThat(sw.toString()).isEqualTo(
+ "plugged-in fg [25, 50]\n"
+ + "on-battery fg [25, 50]\n"
+ + "on-battery bg [50, 100]\n"
+ );
+ }
+
+ private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
+ boolean trackProcState, boolean trackScreenState) {
+ return new MultiStateStats.Factory(DIMENSION_COUNT,
+ new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
+ new MultiStateStats.States(trackProcState,
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_CACHED)),
+ new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+ }
+
+ private FactorySubject assertThatCpuPerformanceStatsFactory(
+ MultiStateStats.Factory factory) {
+ FactorySubject subject = new FactorySubject();
+ subject.mFactory = factory;
+ return subject;
+ }
+
+ private static class FactorySubject {
+ private MultiStateStats.Factory mFactory;
+
+ FactorySubject hasSerialStateCount(int stateCount) {
+ assertThat(mFactory.getSerialStateCount()).isEqualTo(stateCount);
+ return this;
+ }
+
+ public FactorySubject haveDifferentSerialStates(State... states) {
+ int[] serialStates = getSerialStates(states);
+ assertWithMessage("Expected all to be different: " + Arrays.toString(serialStates))
+ .that(Arrays.stream(serialStates).distinct().toArray())
+ .hasLength(states.length);
+ return this;
+ }
+
+ public FactorySubject haveSameSerialStates(State... states) {
+ int[] serialStates = getSerialStates(states);
+ assertWithMessage("Expected all to be the same: " + Arrays.toString(serialStates))
+ .that(Arrays.stream(serialStates).distinct().toArray())
+ .hasLength(1);
+ return this;
+ }
+
+ private int[] getSerialStates(State[] states) {
+ int[] serialStates = new int[states.length];
+ for (int i = 0; i < states.length; i++) {
+ serialStates[i] = mFactory.getSerialState(
+ new int[]{
+ states[i].batteryState ? 0 : 1,
+ states[i].procstate,
+ states[i].screenState ? 0 : 1
+ });
+ }
+ return serialStates;
+ }
+ }
+
+ private State state(boolean batteryState, boolean screenState, int procstate) {
+ State state = new State();
+ state.batteryState = batteryState;
+ state.screenState = screenState;
+ state.procstate = procstate;
+ return state;
+ }
+
+ private static class State {
+ public boolean batteryState;
+ public boolean screenState;
+ public int procstate;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
new file mode 100644
index 000000000000..47de44324ae1
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PowerStatsAggregatorTest {
+ private static final int TEST_POWER_COMPONENT = 77;
+ private static final int TEST_UID = 42;
+
+ private final MockClock mClock = new MockClock();
+ private long mStartTime;
+ private BatteryStatsHistory mHistory;
+ private PowerStatsAggregator mAggregator;
+ private int mAggregatedStatsCount;
+
+ @Before
+ public void setup() throws ParseException {
+ mHistory = new BatteryStatsHistory(32, 1024,
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
+ mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
+ .parse("2008-09-23 08:00").getTime();
+ mClock.currentTime = mStartTime;
+
+ PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
+ builder.trackPowerComponent(TEST_POWER_COMPONENT)
+ .trackDeviceStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN)
+ .trackUidStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN,
+ PowerStatsAggregator.STATE_PROCESS_STATE);
+ mAggregator = builder.build();
+ }
+
+ @Test
+ public void stateUpdates() {
+ mHistory.forceRecordAllHistory();
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
+ mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+
+ advance(1000);
+
+ PowerStats.Descriptor descriptor =
+ new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{10000};
+ powerStats.uidStats.put(TEST_UID, new long[]{1234});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 90, /* plugged */ false);
+ mHistory.recordStateStopEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+
+ advance(1000);
+
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
+
+ advance(3000);
+
+ powerStats.stats = new long[]{20000};
+ powerStats.uidStats.put(TEST_UID, new long[]{4444});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ assertThat(mAggregatedStatsCount++).isEqualTo(0);
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getDuration()).isEqualTo(5000);
+
+ long[] values = new long[1];
+
+ PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats(
+ TEST_POWER_COMPONENT);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{10000});
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{20000});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1234});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1111});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{3333});
+ });
+ }
+
+ @Test
+ public void incompatiblePowerStats() {
+ mHistory.forceRecordAllHistory();
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+
+ advance(1000);
+
+ PowerStats.Descriptor descriptor =
+ new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{10000};
+ powerStats.uidStats.put(TEST_UID, new long[]{1234});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 90, /* plugged */ false);
+
+ advance(1000);
+
+ descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ PersistableBundle.forPair("something", "changed"));
+ powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{20000};
+ powerStats.uidStats.put(TEST_UID, new long[]{4444});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ advance(1000);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
+
+ mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ long[] values = new long[1];
+
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT);
+
+ if (mAggregatedStatsCount == 0) {
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getDuration()).isEqualTo(2000);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{10000});
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1234});
+ } else if (mAggregatedStatsCount == 1) {
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+ assertThat(stats.getDuration()).isEqualTo(1000);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{20000});
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{4444});
+ } else {
+ fail();
+ }
+ mAggregatedStatsCount++;
+ });
+ }
+
+ private void advance(long durationMs) {
+ mClock.realtime += durationMs;
+ mClock.uptime += durationMs;
+ mClock.currentTime += durationMs;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index 08c821344670..330f698277f8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -49,7 +50,7 @@ public class PowerStatsCollectorTest {
mMockClock) {
@Override
protected PowerStats collectStats() {
- return new PowerStats();
+ return new PowerStats(new PowerStats.Descriptor(0, 0, 0, new PersistableBundle()));
}
};
mCollector.addConsumer(stats -> mCollectedStats = stats);