Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a1069e55 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Add date range to battery history iterator

Bug: 285646152
Test: atest FrameworksCoreTests:BatteryStatsTests FrameworksServicesTests:BatteryStatsTests
Change-Id: I3c7813bf1d00b660bd202436420902a12afe6d3d
parent 5709fa4e
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -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;
@@ -2416,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.
@@ -7462,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 {
@@ -8385,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;
+1 −1
Original line number Diff line number Diff line
@@ -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
+146 −87
Original line number Diff line number Diff line
@@ -41,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;
@@ -82,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.
@@ -147,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
@@ -198,12 +198,42 @@ public class BatteryStatsHistory {
    private long mTrackRunningHistoryElapsedRealtimeMs = 0;
    private long mTrackRunningHistoryUptimeMs = 0;
    private long mHistoryBaseTimeMs;
    private ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
    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.
     */
@@ -313,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;
@@ -434,27 +479,14 @@ 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.
@@ -465,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();
@@ -481,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();
        }
@@ -502,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);
        }
    }

@@ -532,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();
    }
@@ -545,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;
@@ -558,7 +615,7 @@ public class BatteryStatsHistory {
                mWritableHistory.setCleanupEnabledLocked(false);
            }
        }
        return new BatteryStatsHistoryIterator(this);
        return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
    }

    /**
@@ -585,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) {
@@ -601,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();
@@ -764,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();
@@ -867,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
@@ -881,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) {
@@ -932,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;
        }

@@ -1432,33 +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);
            }
            mWrittenPowerStatsDescriptors.clear();

            // 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;
+50 −23
Original line number Diff line number Diff line
@@ -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,26 +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 final PowerStats.DescriptorRegistry mDescriptorRegistry =
            new PowerStats.DescriptorRegistry();
    private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
    private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
    private boolean mNextItemReady;

    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;
    }

    /**
@@ -61,10 +68,18 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
     */
    @Override
    public BatteryStats.HistoryItem next() {
        Parcel p = mBatteryStatsHistory.getNextParcel();
        if (!mNextItemReady) {
            advance();
        }
        mNextItemReady = false;
        return mHistoryItem;
    }

    private void advance() {
        while (true) {
            Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs);
            if (p == null) {
            close();
            return null;
                break;
            }

            final long lastRealtimeMs = mHistoryItem.time;
@@ -73,13 +88,25 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
                readHistoryDelta(p, mHistoryItem);
            } catch (Throwable t) {
                Slog.wtf(TAG, "Corrupted battery history", t);
            return null;
                break;
            }
            if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
                && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
                    && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
                    && lastWalltimeMs != 0) {
                mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
            }
        return mHistoryItem;
            if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
                break;
            }
            if (mHistoryItem.currentTime >= mStartTimeMs) {
                mNextItemReady = true;
                return;
            }
        }

        mHistoryItem = null;
        mNextItemReady = true;
        close();
    }

    private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
+11 −5
Original line number Diff line number Diff line
@@ -4379,6 +4379,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")
@@ -7660,9 +7666,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;
    }
@@ -11452,8 +11457,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
Loading