diff options
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 >= {@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); |