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

Commit 98d0f477 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Add monotonic clock

Bug: 297274264
Test: atest FrameworksCoreTests:BatteryStatsTests PowerStatsTests

Change-Id: I800134264cf68779a11a13e362434a4d356d416d
parent 4567f3a2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1854,7 +1854,7 @@ public abstract class BatteryStats {
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
        public HistoryItem next;

        // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
        // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().
        @UnsupportedAppUsage
        public long time;

+61 −62
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -78,7 +77,7 @@ public class BatteryStatsHistory {
    private static final String TAG = "BatteryStatsHistory";

    // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
    private static final int VERSION = 209;
    private static final int VERSION = 210;

    private static final String HISTORY_DIR = "battery-history";
    private static final String FILE_SUFFIX = ".bh";
@@ -194,10 +193,11 @@ public class BatteryStatsHistory {
    private int mNextHistoryTagIdx = 0;
    private int mNumHistoryTagChars = 0;
    private int mHistoryBufferLastPos = -1;
    private long mLastHistoryElapsedRealtimeMs = 0;
    private long mTrackRunningHistoryElapsedRealtimeMs = 0;
    private long mTrackRunningHistoryUptimeMs = 0;
    private long mHistoryBaseTimeMs;
    private final MonotonicClock mMonotonicClock;
    // Monotonic time when we started writing to the history buffer
    private long mHistoryBufferStartTime;
    private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
    private byte mLastHistoryStepLevel = 0;
    private boolean mMutable = true;
@@ -307,23 +307,26 @@ public class BatteryStatsHistory {
     * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
     */
    public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
            MonotonicClock monotonicClock) {
        this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
                stepDetailsCalculator, clock, new TraceDelegate());
                stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
        initHistoryBuffer();
    }

    @VisibleForTesting
    public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
            int maxHistoryFiles, int maxHistoryBufferSize,
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) {
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
            MonotonicClock monotonicClock, TraceDelegate tracer) {
        this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
                clock, tracer, null);
                clock, monotonicClock, tracer, null);
    }

    private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
            int maxHistoryFiles, int maxHistoryBufferSize,
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer,
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
            MonotonicClock monotonicClock, TraceDelegate tracer,
            BatteryStatsHistory writableHistory) {
        mHistoryBuffer = historyBuffer;
        mSystemDir = systemDir;
@@ -332,6 +335,7 @@ public class BatteryStatsHistory {
        mStepDetailsCalculator = stepDetailsCalculator;
        mTracer = tracer;
        mClock = clock;
        mMonotonicClock = monotonicClock;
        mWritableHistory = writableHistory;
        if (mWritableHistory != null) {
            mMutable = false;
@@ -381,16 +385,18 @@ public class BatteryStatsHistory {
    }

    private BatteryHistoryFile makeBatteryHistoryFile() {
        return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
        return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
    }

    public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
            MonotonicClock monotonicClock) {
        mMaxHistoryFiles = maxHistoryFiles;
        mMaxHistoryBufferSize = maxHistoryBufferSize;
        mStepDetailsCalculator = stepDetailsCalculator;
        mTracer = new TraceDelegate();
        mClock = clock;
        mMonotonicClock = monotonicClock;

        mHistoryBuffer = Parcel.obtain();
        mSystemDir = null;
@@ -417,16 +423,16 @@ public class BatteryStatsHistory {
        mHistoryBuffer = Parcel.obtain();
        mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);

        mMonotonicClock = null;
        readFromParcel(parcel, true /* useBlobs */);
    }

    private void initHistoryBuffer() {
        mHistoryBaseTimeMs = 0;
        mLastHistoryElapsedRealtimeMs = 0;
        mTrackRunningHistoryElapsedRealtimeMs = 0;
        mTrackRunningHistoryUptimeMs = 0;
        mWrittenPowerStatsDescriptors.clear();

        mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
        mHistoryBuffer.setDataSize(0);
        mHistoryBuffer.setDataPosition(0);
        mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -466,7 +472,7 @@ public class BatteryStatsHistory {
            historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());

            return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
                    this);
                    null, this);
        }
    }

@@ -491,7 +497,7 @@ public class BatteryStatsHistory {
     * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
     * create next history file.
     */
    public void startNextFile() {
    public void startNextFile(long elapsedRealtimeMs) {
        if (mMaxHistoryFiles == 0) {
            Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
            return;
@@ -517,6 +523,7 @@ public class BatteryStatsHistory {
            Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
        }

        mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
        mHistoryBuffer.setDataSize(0);
        mHistoryBuffer.setDataPosition(0);
        mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -699,9 +706,12 @@ public class BatteryStatsHistory {
        if (mHistoryParcels != null) {
            while (mParcelIndex < mHistoryParcels.size()) {
                final Parcel p = mHistoryParcels.get(mParcelIndex++);
                if (!skipHead(p)) {
                if (!verifyVersion(p)) {
                    continue;
                }
                // skip monotonic time field.
                p.readLong();

                final int bufSize = p.readInt();
                final int curPos = p.dataPosition();
                mCurrentParcelEnd = curPos + bufSize;
@@ -745,24 +755,36 @@ public class BatteryStatsHistory {
        }
        out.unmarshall(raw, 0, raw.length);
        out.setDataPosition(0);
        return skipHead(out);
        if (!verifyVersion(out)) {
            return false;
        }
        // skip monotonic time field.
        out.readLong();
        return true;
    }

    /**
     * Skip the header part of history parcel.
     * Verify header part of history parcel.
     *
     * @param p history parcel to skip head.
     * @return true if version match, false if not.
     */
    private boolean skipHead(Parcel p) {
    private boolean verifyVersion(Parcel p) {
        p.setDataPosition(0);
        final int version = p.readInt();
        if (version != VERSION) {
            return false;
        return version == VERSION;
    }
        // skip historyBaseTime field.
        p.readLong();
        return true;

    /**
     * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
     * buffer.
     */
    public long getHistoryBufferStartTime(Parcel p) {
        int pos = p.dataPosition();
        p.setDataPosition(0);
        p.readInt();        // Skip the version field
        long monotonicTime = p.readLong();
        p.setDataPosition(pos);
        return monotonicTime;
    }

    /**
@@ -1438,7 +1460,8 @@ public class BatteryStatsHistory {
            throw new ConcurrentModificationException("Battery history is not writable");
        }

        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
        final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
                                - mHistoryLastWritten.time;
        final int diffStates = mHistoryLastWritten.states ^ cur.states;
        final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
        final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
@@ -1476,7 +1499,9 @@ public class BatteryStatsHistory {
            mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
            mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
            mHistoryBufferLastPos = -1;
            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;

            elapsedRealtimeMs -= timeDiffMs;

            // If the last written history had a wakelock tag, we need to retain it.
            // Note that the condition above made sure that we aren't in a case where
            // both it and the current history item have a wakelock tag.
@@ -1513,7 +1538,7 @@ public class BatteryStatsHistory {
            HistoryItem copy = new HistoryItem();
            copy.setTo(cur);

            startNextFile();
            startNextFile(elapsedRealtimeMs);

            // startRecordingHistory will reset mHistoryCur.
            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
@@ -1548,7 +1573,7 @@ public class BatteryStatsHistory {
        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
        final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
        mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
        if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
            Slog.wtf(TAG, "Significantly earlier event written to battery history:"
                    + " time=" + mHistoryLastWritten.time
@@ -1556,7 +1581,6 @@ public class BatteryStatsHistory {
        }
        mHistoryLastWritten.tagsFirstOccurrence = hasTags;
        writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
        cur.wakelockTag = null;
        cur.wakeReasonTag = null;
        cur.eventCode = HistoryItem.EVENT_NONE;
@@ -1926,6 +1950,10 @@ public class BatteryStatsHistory {
            return;
        }

        // Save the monotonic time first, so that even if the history write below fails,
        // we still wouldn't end up with overlapping history timelines.
        mMonotonicClock.write();

        Parcel p = Parcel.obtain();
        try {
            final long start = SystemClock.uptimeMillis();
@@ -1951,8 +1979,7 @@ public class BatteryStatsHistory {
            return;
        }

        final long historyBaseTime = in.readLong();

        mHistoryBufferStartTime = in.readLong();
        mHistoryBuffer.setDataSize(0);
        mHistoryBuffer.setDataPosition(0);

@@ -1972,39 +1999,11 @@ public class BatteryStatsHistory {
            mHistoryBuffer.appendFrom(in, curPos, bufSize);
            in.setDataPosition(curPos + bufSize);
        }

        mHistoryBaseTimeMs = historyBaseTime;
        if (DEBUG) {
            StringBuilder sb = new StringBuilder(128);
            sb.append("****************** NEW mHistoryBaseTimeMs: ");
            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
            Slog.i(TAG, sb.toString());
        }

        if (mHistoryBaseTimeMs > 0) {
            long elapsedRealtimeMs = mClock.elapsedRealtime();
            mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
            mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
            if (DEBUG) {
                StringBuilder sb = new StringBuilder(128);
                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
                Slog.i(TAG, sb.toString());
            }
        }
    }

    private void writeHistoryBuffer(Parcel out) {
        if (DEBUG) {
            StringBuilder sb = new StringBuilder(128);
            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
            sb.append(" mLastHistoryElapsedRealtimeMs: ");
            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
            Slog.i(TAG, sb.toString());
        }
        out.writeInt(BatteryStatsHistory.VERSION);
        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
        out.writeLong(mHistoryBufferStartTime);
        out.writeInt(mHistoryBuffer.dataSize());
        if (DEBUG) {
            Slog.i(TAG, "***************** WRITING HISTORY: "
+15 −10
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.internal.os;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -34,8 +33,8 @@ 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 long mStartTimeMs;
    private final long mEndTimeMs;
    private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
            new BatteryStats.HistoryStepDetails();
    private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
@@ -43,10 +42,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
            new PowerStats.DescriptorRegistry();
    private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
    private boolean mNextItemReady;
    private boolean mTimeInitialized;

    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
            @CurrentTimeMillisLong long startTimeMs,
            @CurrentTimeMillisLong long endTimeMs) {
    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
            long endTimeMs) {
        mBatteryStatsHistory = history;
        mStartTimeMs = startTimeMs;
        mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
@@ -82,7 +81,12 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
                break;
            }

            final long lastRealtimeMs = mHistoryItem.time;
            if (!mTimeInitialized) {
                mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
                mTimeInitialized = true;
            }

            final long lastMonotonicTimeMs = mHistoryItem.time;
            final long lastWalltimeMs = mHistoryItem.currentTime;
            try {
                readHistoryDelta(p, mHistoryItem);
@@ -93,12 +97,13 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
            if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
                    && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
                    && lastWalltimeMs != 0) {
                mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
                mHistoryItem.currentTime =
                        lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
            }
            if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
            if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
                break;
            }
            if (mHistoryItem.currentTime >= mStartTimeMs) {
            if (mHistoryItem.time >= mStartTimeMs) {
                mNextItemReady = true;
                return;
            }
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.os;

import android.annotation.NonNull;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;

import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

/**
 * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
 * on reboot, but keeps going.
 */
public class MonotonicClock {
    private static final String TAG = "MonotonicClock";

    private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
    private static final String XML_ATTR_TIMESHIFT = "timeshift";

    private final AtomicFile mFile;
    private final Clock mClock;
    private long mTimeshift;

    public MonotonicClock(File file) {
        mFile = new AtomicFile(file);
        mClock = Clock.SYSTEM_CLOCK;
        read();
    }

    public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
        mClock = clock;
        mFile = null;
        mTimeshift = monotonicTime - mClock.elapsedRealtime();
    }

    /**
     * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
     * after a device reboot the time keeps increasing.
     */
    public long monotonicTime() {
        return monotonicTime(mClock.elapsedRealtime());
    }

    /**
     * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
     * of being read from the Clock.
     */
    public long monotonicTime(long elapsedRealtimeMs) {
        return mTimeshift + elapsedRealtimeMs;
    }

    private void read() {
        if (!mFile.exists()) {
            return;
        }

        try {
            readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
        } catch (IOException e) {
            Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
        }
    }

    /**
     * Saves the timeshift into a file.  Call this method just before system shutdown, after
     * writing the last battery history event.
     */
    public void write() {
        if (mFile == null) {
            return;
        }

        mFile.write(out -> {
            try {
                writeXml(out, Xml.newBinarySerializer());
                out.close();
            } catch (IOException e) {
                Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
            }
        });
    }

    /**
     * Parses an XML file containing the persistent state of the monotonic clock.
     */
    @VisibleForTesting
    public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
        long savedTimeshift = 0;
        try {
            parser.setInput(inputStream, StandardCharsets.UTF_8.name());
            int eventType = parser.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                if (eventType == XmlPullParser.START_TAG
                        && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
                    savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
                }
                eventType = parser.next();
            }
        } catch (XmlPullParserException e) {
            throw new IOException(e);
        }
        mTimeshift = savedTimeshift - mClock.elapsedRealtime();
    }

    /**
     * Creates an XML file containing the persistent state of the monotonic clock.
     */
    @VisibleForTesting
    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
        serializer.setOutput(out, StandardCharsets.UTF_8.name());
        serializer.startDocument(null, true);
        serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
        serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
        serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
        serializer.endDocument();
    }
}
+2 −6
Original line number Diff line number Diff line
@@ -5,14 +5,10 @@ per-file *Binder* = file:BINDER_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS

# BatteryStats
per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
per-file *Kernel* = file:/BATTERY_STATS_OWNERS
per-file *Clock* = file:/BATTERY_STATS_OWNERS
per-file *MultiState* = file:/BATTERY_STATS_OWNERS
per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
Loading