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

Commit 6b8e7a0b authored by Adam Lesinski's avatar Adam Lesinski Committed by The Android Automerger
Browse files

BatteryStats: Better big-little CPU accounting

Generalize cpu clusters so we can measure frequency
and power usage across heterogeneous cpu clusters.

This also brings back reading of cpu-times for power calculation.

Bug:22773176
Change-Id: I9c794ae9756c782c0e971c7f5fcebbe70374b269
parent af06e9cd
Loading
Loading
Loading
Loading
+6 −7
Original line number Diff line number Diff line
@@ -463,13 +463,15 @@ public abstract class BatteryStats implements Parcelable {
        public abstract long getCpuPowerMaUs(int which);

        /**
         * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
         * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed for a
         * given CPU cluster.
         * @param cluster the index of the CPU cluster.
         * @param step the index of the CPU speed. This is not the actual speed of the CPU.
         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
         * @see BatteryStats#getCpuSpeedSteps()
         * @see PowerProfile.getNumCpuClusters()
         * @see PowerProfile.getNumSpeedStepsInCpuCluster(int)
         */
        @Deprecated
        public abstract long getTimeAtCpuSpeed(int step, int which);
        public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);

        public static abstract class Sensor {
            /*
@@ -2276,9 +2278,6 @@ public abstract class BatteryStats implements Parcelable {

    public abstract Map<String, ? extends Timer> getKernelWakelockStats();

    /** Returns the number of different speeds that the CPU can run at */
    public abstract int getCpuSpeedSteps();

    public abstract void writeToParcelWithoutUids(Parcel out, int flags);

    private final static void formatTimeRaw(StringBuilder out, long seconds) {
+1 −1
Original line number Diff line number Diff line
@@ -338,7 +338,7 @@ public final class BatteryStatsHelper {
        }

        if (mCpuPowerCalculator == null) {
            mCpuPowerCalculator = new CpuPowerCalculator();
            mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
        }
        mCpuPowerCalculator.reset();

+159 −75
Original line number Diff line number Diff line
@@ -105,7 +105,7 @@ public final class BatteryStatsImpl extends BatteryStats {
    private static final int MAGIC = 0xBA757475; // 'BATSTATS'

    // Current on-disk Parcel version
    private static final int VERSION = 130 + (USE_OLD_HISTORY ? 1000 : 0);
    private static final int VERSION = 131 + (USE_OLD_HISTORY ? 1000 : 0);

    // Maximum number of items we will record in the history.
    private static final int MAX_HISTORY_ITEMS = 2000;
@@ -118,8 +118,6 @@ public final class BatteryStatsImpl extends BatteryStats {
    // in to one common name.
    private static final int MAX_WAKELOCKS_PER_UID = 100;

    private static int sNumSpeedSteps;

    private final JournaledFile mFile;
    public final AtomicFile mCheckinFile;
    public final AtomicFile mDailyFile;
@@ -133,7 +131,7 @@ public final class BatteryStatsImpl extends BatteryStats {
    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();

    private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
    private final KernelCpuSpeedReader mKernelCpuSpeedReader = new KernelCpuSpeedReader();
    private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;

    public interface BatteryCallback {
        public void batteryNeedsCpuUpdate();
@@ -4411,7 +4409,7 @@ public final class BatteryStatsImpl extends BatteryStats {
        LongSamplingCounter mUserCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
        LongSamplingCounter mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
        LongSamplingCounter mCpuPower = new LongSamplingCounter(mOnBatteryTimeBase);
        LongSamplingCounter[] mSpeedBins;
        LongSamplingCounter[][] mCpuClusterSpeed;

        /**
         * The statistics we have collected for this uid's wake locks.
@@ -4470,7 +4468,6 @@ public final class BatteryStatsImpl extends BatteryStats {
            mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                    mWifiMulticastTimers, mOnBatteryTimeBase);
            mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
            mSpeedBins = new LongSamplingCounter[getCpuSpeedSteps()];
        }

        @Override
@@ -5008,10 +5005,18 @@ public final class BatteryStatsImpl extends BatteryStats {
        }

        @Override
        public long getTimeAtCpuSpeed(int step, int which) {
            if (step >= 0 && step < mSpeedBins.length) {
                if (mSpeedBins[step] != null) {
                    return mSpeedBins[step].getCountLocked(which);
        public long getTimeAtCpuSpeed(int cluster, int step, int which) {
            if (mCpuClusterSpeed != null) {
                if (cluster >= 0 && cluster < mCpuClusterSpeed.length) {
                    final LongSamplingCounter[] cpuSpeeds = mCpuClusterSpeed[cluster];
                    if (cpuSpeeds != null) {
                        if (step >= 0 && step < cpuSpeeds.length) {
                            final LongSamplingCounter c = cpuSpeeds[step];
                            if (c != null) {
                                return c.getCountLocked(which);
                            }
                        }
                    }
                }
            }
            return 0;
@@ -5128,10 +5133,16 @@ public final class BatteryStatsImpl extends BatteryStats {
            mUserCpuTime.reset(false);
            mSystemCpuTime.reset(false);
            mCpuPower.reset(false);
            for (int i = 0; i < mSpeedBins.length; i++) {
                LongSamplingCounter c = mSpeedBins[i];
                if (c != null) {
                    c.reset(false);

            if (mCpuClusterSpeed != null) {
                for (LongSamplingCounter[] speeds : mCpuClusterSpeed) {
                    if (speeds != null) {
                        for (LongSamplingCounter speed : speeds) {
                            if (speed != null) {
                                speed.reset(false);
                            }
                        }
                    }
                }
            }

@@ -5280,13 +5291,19 @@ public final class BatteryStatsImpl extends BatteryStats {
                mUserCpuTime.detach();
                mSystemCpuTime.detach();
                mCpuPower.detach();
                for (int i = 0; i < mSpeedBins.length; i++) {
                    LongSamplingCounter c = mSpeedBins[i];

                if (mCpuClusterSpeed != null) {
                    for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
                        if (cpuSpeeds != null) {
                            for (LongSamplingCounter c : cpuSpeeds) {
                                if (c != null) {
                                    c.detach();
                                }
                            }
                        }
                    }
                }
            }

            return !active;
        }
@@ -5461,9 +5478,14 @@ public final class BatteryStatsImpl extends BatteryStats {
            mSystemCpuTime.writeToParcel(out);
            mCpuPower.writeToParcel(out);

            out.writeInt(mSpeedBins.length);
            for (int i = 0; i < mSpeedBins.length; i++) {
                LongSamplingCounter c = mSpeedBins[i];
            if (mCpuClusterSpeed != null) {
                out.writeInt(1);
                out.writeInt(mCpuClusterSpeed.length);
                for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
                    if (cpuSpeeds != null) {
                        out.writeInt(1);
                        out.writeInt(cpuSpeeds.length);
                        for (LongSamplingCounter c : cpuSpeeds) {
                            if (c != null) {
                                out.writeInt(1);
                                c.writeToParcel(out);
@@ -5471,6 +5493,13 @@ public final class BatteryStatsImpl extends BatteryStats {
                                out.writeInt(0);
                            }
                        }
                    } else {
                        out.writeInt(0);
                    }
                }
            } else {
                out.writeInt(0);
            }
        }

        void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
@@ -5653,13 +5682,32 @@ public final class BatteryStatsImpl extends BatteryStats {
            mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
            mCpuPower = new LongSamplingCounter(mOnBatteryTimeBase, in);

            int bins = in.readInt();
            int steps = getCpuSpeedSteps();
            mSpeedBins = new LongSamplingCounter[bins >= steps ? bins : steps];
            for (int i = 0; i < bins; i++) {
            if (in.readInt() != 0) {
                    mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
                int numCpuClusters = in.readInt();
                if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numCpuClusters) {
                    throw new ParcelFormatException("Incompatible number of cpu clusters");
                }

                mCpuClusterSpeed = new LongSamplingCounter[numCpuClusters][];
                for (int cluster = 0; cluster < numCpuClusters; cluster++) {
                    if (in.readInt() != 0) {
                        int numSpeeds = in.readInt();
                        if (mPowerProfile != null &&
                                mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
                            throw new ParcelFormatException("Incompatible number of cpu speeds");
                        }

                        final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
                        mCpuClusterSpeed[cluster] = cpuSpeeds;
                        for (int speed = 0; speed < numSpeeds; speed++) {
                            if (in.readInt() != 0) {
                                cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase, in);
                            }
                        }
                    }
                }
            } else {
                mCpuClusterSpeed = null;
            }
        }

@@ -6874,6 +6922,19 @@ public final class BatteryStatsImpl extends BatteryStats {
    public void setPowerProfile(PowerProfile profile) {
        synchronized (this) {
            mPowerProfile = profile;

            // We need to initialize the KernelCpuSpeedReaders to read from
            // the first cpu of each core. Once we have the PowerProfile, we have access to this
            // information.
            final int numClusters = mPowerProfile.getNumCpuClusters();
            mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
            int firstCpuOfCluster = 0;
            for (int i = 0; i < numClusters; i++) {
                final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
                mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
                        numSpeedSteps);
                firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
            }
        }
    }

@@ -6881,10 +6942,6 @@ public final class BatteryStatsImpl extends BatteryStats {
        mCallback = cb;
    }

    public void setNumSpeedSteps(int steps) {
        if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
    }

    public void setRadioScanningTimeout(long timeout) {
        if (mPhoneSignalScanningTimer != null) {
            mPhoneSignalScanningTimer.setTimeout(timeout);
@@ -7997,9 +8054,11 @@ public final class BatteryStatsImpl extends BatteryStats {
        // If no app is holding a wakelock, then the distribution is normal.
        final int wakelockWeight = 50;

        // Read the time spent at various cpu frequencies.
        final int cpuSpeedSteps = getCpuSpeedSteps();
        final long[] cpuSpeeds = mKernelCpuSpeedReader.readDelta();
        // Read the time spent for each cluster at various cpu frequencies.
        final long[][] clusterSpeeds = new long[mKernelCpuSpeedReaders.length][];
        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
            clusterSpeeds[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
        }

        int numWakelocks = 0;

@@ -8072,11 +8131,23 @@ public final class BatteryStatsImpl extends BatteryStats {

                        // Add the cpu speeds to this UID. These are used as a ratio
                        // for computing the power this UID used.
                        for (int i = 0; i < cpuSpeedSteps; i++) {
                            if (u.mSpeedBins[i] == null) {
                                u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
                        if (u.mCpuClusterSpeed == null) {
                            u.mCpuClusterSpeed = new LongSamplingCounter[clusterSpeeds.length][];
                        }

                        for (int cluster = 0; cluster < clusterSpeeds.length; cluster++) {
                            if (u.mCpuClusterSpeed[cluster] == null) {
                                u.mCpuClusterSpeed[cluster] =
                                        new LongSamplingCounter[clusterSpeeds[cluster].length];
                            }

                            final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeed[cluster];
                            for (int speed = 0; speed < clusterSpeeds[cluster].length; speed++) {
                                if (cpuSpeeds[speed] == null) {
                                    cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
                                }
                                cpuSpeeds[speed].addCountLocked(clusterSpeeds[cluster][speed]);
                            }
                            u.mSpeedBins[i].addCountLocked(cpuSpeeds[i]);
                        }
                    }
                });
@@ -8776,11 +8847,6 @@ public final class BatteryStatsImpl extends BatteryStats {
        }
    }

    @Override
    public int getCpuSpeedSteps() {
        return sNumSpeedSteps;
    }

    /**
     * Retrieve the statistics object for a particular uid, creating if needed.
     */
@@ -9216,11 +9282,6 @@ public final class BatteryStatsImpl extends BatteryStats {
            }
        }

        sNumSpeedSteps = in.readInt();
        if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) {
            throw new ParcelFormatException("Bad speed steps in data: " + sNumSpeedSteps);
        }

        final int NU = in.readInt();
        if (NU > 10000) {
            throw new ParcelFormatException("File corrupt: too many uids " + NU);
@@ -9304,18 +9365,34 @@ public final class BatteryStatsImpl extends BatteryStats {
            u.mSystemCpuTime.readSummaryFromParcelLocked(in);
            u.mCpuPower.readSummaryFromParcelLocked(in);

            if (in.readInt() != 0) {
                final int numClusters = in.readInt();
                if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numClusters) {
                    throw new ParcelFormatException("Incompatible cpu cluster arrangement");
                }

                u.mCpuClusterSpeed = new LongSamplingCounter[numClusters][];
                for (int cluster = 0; cluster < numClusters; cluster++) {
                    int NSB = in.readInt();
            if (NSB > 100) {
                    if (mPowerProfile != null &&
                            mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != NSB) {
                        throw new ParcelFormatException("File corrupt: too many speed bins " + NSB);
                    }

            u.mSpeedBins = new LongSamplingCounter[NSB];
            for (int i=0; i<NSB; i++) {
                    if (in.readInt() != 0) {
                    u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
                    u.mSpeedBins[i].readSummaryFromParcelLocked(in);
                        u.mCpuClusterSpeed[cluster] = new LongSamplingCounter[NSB];
                        for (int speed = 0; speed < NSB; speed++) {
                            if (in.readInt() != 0) {
                                u.mCpuClusterSpeed[cluster][speed] = new LongSamplingCounter(
                                        mOnBatteryTimeBase);
                                u.mCpuClusterSpeed[cluster][speed].readSummaryFromParcelLocked(in);
                            }
                        }
                    }
                }
            } else {
                u.mCpuClusterSpeed = null;
            }

            int NW = in.readInt();
            if (NW > 100) {
@@ -9531,7 +9608,6 @@ public final class BatteryStatsImpl extends BatteryStats {
            }
        }

        out.writeInt(sNumSpeedSteps);
        final int NU = mUidStats.size();
        out.writeInt(NU);
        for (int iu = 0; iu < NU; iu++) {
@@ -9640,16 +9716,28 @@ public final class BatteryStatsImpl extends BatteryStats {
            u.mSystemCpuTime.writeSummaryFromParcelLocked(out);
            u.mCpuPower.writeSummaryFromParcelLocked(out);

            out.writeInt(u.mSpeedBins.length);
            for (int i = 0; i < u.mSpeedBins.length; i++) {
                LongSamplingCounter speedBin = u.mSpeedBins[i];
                if (speedBin != null) {
            if (u.mCpuClusterSpeed != null) {
                out.writeInt(1);
                    speedBin.writeSummaryFromParcelLocked(out);
                out.writeInt(u.mCpuClusterSpeed.length);
                for (LongSamplingCounter[] cpuSpeeds : u.mCpuClusterSpeed) {
                    if (cpuSpeeds != null) {
                        out.writeInt(1);
                        out.writeInt(cpuSpeeds.length);
                        for (LongSamplingCounter c : cpuSpeeds) {
                            if (c != null) {
                                out.writeInt(1);
                                c.writeSummaryFromParcelLocked(out);
                            } else {
                                out.writeInt(0);
                            }
                        }
                    } else {
                        out.writeInt(0);
                    }
                }
            } else {
                out.writeInt(0);
            }

            final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
            int NW = wakeStats.size();
@@ -9897,8 +9985,6 @@ public final class BatteryStatsImpl extends BatteryStats {
        mFlashlightTurnedOnTimers.clear();
        mCameraTurnedOnTimers.clear();

        sNumSpeedSteps = in.readInt();

        int numUids = in.readInt();
        mUidStats.clear();
        for (int i = 0; i < numUids; i++) {
@@ -10037,8 +10123,6 @@ public final class BatteryStatsImpl extends BatteryStats {
            out.writeInt(0);
        }

        out.writeInt(sNumSpeedSteps);

        if (inclUids) {
            int size = mUidStats.size();
            out.writeInt(size);
+36 −1
Original line number Diff line number Diff line
@@ -22,12 +22,47 @@ import android.util.Log;
public class CpuPowerCalculator extends PowerCalculator {
    private static final String TAG = "CpuPowerCalculator";
    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
    private final PowerProfile mProfile;

    public CpuPowerCalculator(PowerProfile profile) {
        mProfile = profile;
    }

    @Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {

        app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
        app.cpuPowerMah = (double) u.getCpuPowerMaUs(statsType) / (60.0 * 60.0 * 1000.0 * 1000.0);

        // Aggregate total time spent on each cluster.
        long totalTime = 0;
        final int numClusters = mProfile.getNumCpuClusters();
        for (int cluster = 0; cluster < numClusters; cluster++) {
            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
            for (int speed = 0; speed < speedsForCluster; speed++) {
                totalTime += u.getTimeAtCpuSpeed(cluster, speed, statsType);
            }
        }
        totalTime = Math.max(totalTime, 1);

        double cpuPowerMaMs = 0;
        for (int cluster = 0; cluster < numClusters; cluster++) {
            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
            for (int speed = 0; speed < speedsForCluster; speed++) {
                final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) /
                        totalTime;
                final double cpuSpeedStepPower = ratio * app.cpuTimeMs *
                        mProfile.getAveragePowerForCpu(cluster, speed);
                if (DEBUG && ratio != 0) {
                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                            + speed + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
                            + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
                }
                cpuPowerMaMs += cpuSpeedStepPower;
            }
        }
        app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);

        if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
                    + BatteryStatsHelper.makemAh(app.cpuPowerMah));
+24 −10
Original line number Diff line number Diff line
@@ -24,8 +24,8 @@ import java.io.IOException;
import java.util.Arrays;

/**
 * Reads CPU time spent at various frequencies and provides a delta from the last call to
 * {@link #readDelta}. Each line in the proc file has the format:
 * Reads CPU time of a specific core spent at various frequencies and provides a delta from the
 * last call to {@link #readDelta}. Each line in the proc file has the format:
 *
 * freq time
 *
@@ -33,12 +33,20 @@ import java.util.Arrays;
 */
public class KernelCpuSpeedReader {
    private static final String TAG = "KernelCpuSpeedReader";
    private static final String sProcFile =
            "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state";
    private static final int MAX_SPEEDS = 60;

    private long[] mLastSpeedTimes = new long[MAX_SPEEDS];
    private long[] mDeltaSpeedTimes = new long[MAX_SPEEDS];
    private final String mProcFile;
    private final long[] mLastSpeedTimes;
    private final long[] mDeltaSpeedTimes;

    /**
     * @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
     */
    public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
        mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
                cpuNumber);
        mLastSpeedTimes = new long[numSpeedSteps];
        mDeltaSpeedTimes = new long[numSpeedSteps];
    }

    /**
     * The returned array is modified in subsequent calls to {@link #readDelta}.
@@ -46,7 +54,7 @@ public class KernelCpuSpeedReader {
     * {@link #readDelta}.
     */
    public long[] readDelta() {
        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
        try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
            String line;
            int speedIndex = 0;
@@ -56,12 +64,18 @@ public class KernelCpuSpeedReader {

                // The proc file reports time in 1/100 sec, so convert to milliseconds.
                long time = Long.parseLong(splitter.next()) * 10;
                if (time < mLastSpeedTimes[speedIndex]) {
                    // The stats reset when the cpu hotplugged. That means that the time
                    // we read is offset from 0, so the time is the delta.
                    mDeltaSpeedTimes[speedIndex] = time;
                } else {
                    mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
                }
                mLastSpeedTimes[speedIndex] = time;
                speedIndex++;
            }
        } catch (IOException e) {
            Slog.e(TAG, "Failed to read cpu-freq", e);
            Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
            Arrays.fill(mDeltaSpeedTimes, 0);
        }
        return mDeltaSpeedTimes;
Loading