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

Commit 3d422c37 authored by Mike Ma's avatar Mike Ma
Browse files

Add cluster&active cost to cpu power

Add logic to read per UID cluster and active CPU time from the kernel in
BatteryStatsImpl, store them in BatteryStats.Uid, then use these data to
calculate CPU power more accurately in CpuPowerCalculator.

Change-Id: I06a84d2bba8b97445466b310f15092614ff3477f
Bug: 67752294
Test: PowerProfileTest
Test: KernelUidCpuActiveTimeReaderTest
Test: KernelUidCpuClusterTimeReaderTest
Test: BatteryStatsCpuTimesTest
parent 4b60648b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -683,6 +683,14 @@ public abstract class BatteryStats implements Parcelable {

        public abstract long[] getCpuFreqTimes(int which);
        public abstract long[] getScreenOffCpuFreqTimes(int which);
        /**
         * Returns cpu active time of an uid.
         */
        public abstract long getCpuActiveTime();
        /**
         * Returns cpu times of an uid on each cluster
         */
        public abstract long[] getCpuClusterTimes();

        /**
         * Returns cpu times of an uid at a particular process state.
+4 −4
Original line number Diff line number Diff line
@@ -665,14 +665,14 @@ public class BatteryStatsHelper {

    /**
     * Calculate the baseline power usage for the device when it is in suspend and idle.
     * The device is drawing POWER_CPU_IDLE power at its lowest power state.
     * The device is drawing POWER_CPU_IDLE + POWER_CPU_AWAKE power when a wakelock is held.
     * The device is drawing POWER_CPU_SUSPEND power at its lowest power state.
     * The device is drawing POWER_CPU_SUSPEND + POWER_CPU_IDLE power when a wakelock is held.
     */
    private void addIdleUsage() {
        final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
                mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
                mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND);
        final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
                mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
                mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
        final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
        if (DEBUG && totalPowerMah != 0) {
            Log.d(TAG, "Suspend: time=" + (mTypeBatteryRealtimeUs / 1000)
+104 −0
Original line number Diff line number Diff line
@@ -198,6 +198,12 @@ public class BatteryStatsImpl extends BatteryStats {
    protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
            new KernelUidCpuFreqTimeReader();
    @VisibleForTesting
    protected KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
            new KernelUidCpuActiveTimeReader();
    @VisibleForTesting
    protected KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
            new KernelUidCpuClusterTimeReader();
    @VisibleForTesting
    protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
    private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
@@ -3880,6 +3886,8 @@ public class BatteryStatsImpl extends BatteryStats {
        }
        mKernelUidCpuTimeReader.removeUid(isolatedUid);
        mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
        mKernelUidCpuActiveTimeReader.removeUid(isolatedUid);
        mKernelUidCpuClusterTimeReader.removeUid(isolatedUid);
    }
    public int mapUid(int uid) {
@@ -6479,9 +6487,11 @@ public class BatteryStatsImpl extends BatteryStats {
        LongSamplingCounter mUserCpuTime;
        LongSamplingCounter mSystemCpuTime;
        LongSamplingCounter[][] mCpuClusterSpeedTimesUs;
        LongSamplingCounter mCpuActiveTimeMs;
        LongSamplingCounterArray mCpuFreqTimeMs;
        LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
        LongSamplingCounterArray mCpuClusterTimesMs;
        LongSamplingCounterArray[] mProcStateTimeMs;
        LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
@@ -6551,6 +6561,8 @@ public class BatteryStatsImpl extends BatteryStats {
            mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
            mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
            mCpuActiveTimeMs = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
            mCpuClusterTimesMs = new LongSamplingCounterArray(mBsi.mOnBatteryTimeBase);
            mWakelockStats = mBsi.new OverflowArrayMap<Wakelock>(uid) {
                @Override public Wakelock instantiateObject() {
@@ -6597,6 +6609,17 @@ public class BatteryStatsImpl extends BatteryStats {
            return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which);
        }
        @Override
        public long getCpuActiveTime() {
            return mCpuActiveTimeMs.getCountLocked(STATS_SINCE_CHARGED);
        }
        @Override
        public long[] getCpuClusterTimes() {
            return nullIfAllZeros(mCpuClusterTimesMs, STATS_SINCE_CHARGED);
        }
        @Override
        public long[] getCpuFreqTimes(int which, int procState) {
            if (which < 0 || which >= NUM_PROCESS_STATE) {
@@ -7660,6 +7683,9 @@ public class BatteryStatsImpl extends BatteryStats {
                mScreenOffCpuFreqTimeMs.reset(false);
            }
            mCpuActiveTimeMs.reset(false);
            mCpuClusterTimesMs.reset(false);
            if (mProcStateTimeMs != null) {
                for (LongSamplingCounterArray counters : mProcStateTimeMs) {
                    if (counters != null) {
@@ -7864,6 +7890,8 @@ public class BatteryStatsImpl extends BatteryStats {
                if (mScreenOffCpuFreqTimeMs != null) {
                    mScreenOffCpuFreqTimeMs.detach();
                }
                mCpuActiveTimeMs.detach();
                mCpuClusterTimesMs.detach();
                if (mProcStateTimeMs != null) {
                    for (LongSamplingCounterArray counters : mProcStateTimeMs) {
@@ -8139,6 +8167,10 @@ public class BatteryStatsImpl extends BatteryStats {
            LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
            LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
            mCpuActiveTimeMs.writeToParcel(out);
            mCpuClusterTimesMs.writeToParcel(out);
            if (mProcStateTimeMs != null) {
                out.writeInt(mProcStateTimeMs.length);
                for (LongSamplingCounterArray counters : mProcStateTimeMs) {
@@ -8456,6 +8488,9 @@ public class BatteryStatsImpl extends BatteryStats {
            mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
                    in, mBsi.mOnBatteryScreenOffTimeBase);
            mCpuActiveTimeMs = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
            mCpuClusterTimesMs = new LongSamplingCounterArray(mBsi.mOnBatteryTimeBase, in);
            int length = in.readInt();
            if (length == NUM_PROCESS_STATE) {
                mProcStateTimeMs = new LongSamplingCounterArray[length];
@@ -11437,6 +11472,8 @@ public class BatteryStatsImpl extends BatteryStats {
        if (!mOnBatteryInternal) {
            mKernelUidCpuTimeReader.readDelta(null);
            mKernelUidCpuFreqTimeReader.readDelta(null);
            mKernelUidCpuActiveTimeReader.readDelta(null);
            mKernelUidCpuClusterTimeReader.readDelta(null);
            for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
                mKernelCpuSpeedReaders[cluster].readDelta();
            }
@@ -11453,6 +11490,8 @@ public class BatteryStatsImpl extends BatteryStats {
            updateClusterSpeedTimes(updatedUids);
        }
        readKernelUidCpuFreqTimesLocked(partialTimersToConsider);
        readKernelUidCpuActiveTimesLocked();
        readKernelUidCpuClusterTimesLocked();
    }
    /**
@@ -11764,6 +11803,64 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    /**
     * Take a snapshot of the cpu active times spent by each uid and update the corresponding
     * counters.
     */
    @VisibleForTesting
    public void readKernelUidCpuActiveTimesLocked() {
        final long startTimeMs = mClocks.uptimeMillis();
        mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesUs) -> {
            uid = mapUid(uid);
            if (Process.isIsolated(uid)) {
                mKernelUidCpuActiveTimeReader.removeUid(uid);
                Slog.w(TAG, "Got active times for an isolated uid with no mapping: " + uid);
                return;
            }
            if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
                mKernelUidCpuActiveTimeReader.removeUid(uid);
                return;
            }
            final Uid u = getUidStatsLocked(uid);
            u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesUs);
        });
        final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
        if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
            Slog.d(TAG, "Reading cpu active times took " + elapsedTimeMs + "ms");
        }
    }
    /**
     * Take a snapshot of the cpu cluster times spent by each uid and update the corresponding
     * counters.
     */
    @VisibleForTesting
    public void readKernelUidCpuClusterTimesLocked() {
        final long startTimeMs = mClocks.uptimeMillis();
        mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesUs) -> {
            uid = mapUid(uid);
            if (Process.isIsolated(uid)) {
                mKernelUidCpuClusterTimeReader.removeUid(uid);
                Slog.w(TAG, "Got cluster times for an isolated uid with no mapping: " + uid);
                return;
            }
            if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
                mKernelUidCpuClusterTimeReader.removeUid(uid);
                return;
            }
            final Uid u = getUidStatsLocked(uid);
            u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesUs);
        });
        final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
        if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
            Slog.d(TAG, "Reading cpu cluster times took " + elapsedTimeMs + "ms");
        }
    }
    boolean setChargingLocked(boolean charging) {
        if (mCharging != charging) {
            mCharging = charging;
@@ -13249,6 +13346,10 @@ public class BatteryStatsImpl extends BatteryStats {
                    in, mOnBatteryTimeBase);
            u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
                    in, mOnBatteryScreenOffTimeBase);
            u.mCpuActiveTimeMs.readSummaryFromParcelLocked(in);
            u.mCpuClusterTimesMs.readSummaryFromParcelLocked(in);
            int length = in.readInt();
            if (length == Uid.NUM_PROCESS_STATE) {
                u.mProcStateTimeMs = new LongSamplingCounterArray[length];
@@ -13725,6 +13826,9 @@ public class BatteryStatsImpl extends BatteryStats {
            LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
            LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
            u.mCpuActiveTimeMs.writeSummaryFromParcelLocked(out);
            u.mCpuClusterTimesMs.writeSummaryToParcelLocked(out);
            if (u.mProcStateTimeMs != null) {
                out.writeInt(u.mProcStateTimeMs.length);
                for (LongSamplingCounterArray counters : u.mProcStateTimeMs) {
+21 −3
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@ public class CpuPowerCalculator extends PowerCalculator {
    @Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
            long rawUptimeUs, int statsType) {

        app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
        final int numClusters = mProfile.getNumCpuClusters();

@@ -42,7 +41,7 @@ public class CpuPowerCalculator extends PowerCalculator {
            for (int speed = 0; speed < speedsForCluster; speed++) {
                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
                final double cpuSpeedStepPower = timeUs *
                        mProfile.getAveragePowerForCpu(cluster, speed);
                        mProfile.getAveragePowerForCpuCore(cluster, speed);
                if (DEBUG) {
                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                            + speed + " timeUs=" + timeUs + " power="
@@ -51,6 +50,25 @@ public class CpuPowerCalculator extends PowerCalculator {
                cpuPowerMaUs += cpuSpeedStepPower;
            }
        }
        cpuPowerMaUs += u.getCpuActiveTime() * mProfile.getAveragePower(
                PowerProfile.POWER_CPU_ACTIVE);
        long[] cpuClusterTimes = u.getCpuClusterTimes();
        if (cpuClusterTimes != null) {
            if (cpuClusterTimes.length == numClusters) {
                for (int i = 0; i < numClusters; i++) {
                    double power = cpuClusterTimes[i] * mProfile.getAveragePowerForCpuCluster(i);
                    cpuPowerMaUs += power;
                    if (DEBUG) {
                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
                                + cpuClusterTimes[i] + " power="
                                + BatteryStatsHelper.makemAh(power / MICROSEC_IN_HR));
                    }
                }
            } else {
                Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
                        + numClusters + " actual # " + cpuClusterTimes.length);
            }
        }
        app.cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;

        if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.Nullable;
import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;

import com.android.internal.annotations.VisibleForTesting;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * Reads /proc/uid_concurrent_active_time which has the format:
 * active: X (X is # cores)
 * [uid0]: [time-0] [time-1] [time-2] ... (# entries = # cores)
 * [uid1]: [time-0] [time-1] [time-2] ... ...
 * ...
 * Time-N means the CPU time a UID spent running concurrently with N other processes.
 * The file contains a monotonically increasing count of time for a single boot. This class
 * maintains the previous results of a call to {@link #readDelta} in order to provide a
 * proper delta.
 */
public class KernelUidCpuActiveTimeReader {
    private static final boolean DEBUG = false;
    private static final String TAG = "KernelUidCpuActiveTimeReader";
    private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_active_time";

    private int mCoreCount;
    private long mLastTimeReadMs;
    private long mNowTimeMs;
    private SparseArray<long[]> mLastUidCpuActiveTimeMs = new SparseArray<>();

    public interface Callback {
        void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
    }

    public void readDelta(@Nullable Callback cb) {
        final int oldMask = StrictMode.allowThreadDiskReadsMask();
        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
            mNowTimeMs = SystemClock.elapsedRealtime();
            readDeltaInternal(reader, cb);
            mLastTimeReadMs = mNowTimeMs;
        } catch (IOException e) {
            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
        } finally {
            StrictMode.setThreadPolicyMask(oldMask);
        }
    }

    public void removeUid(int uid) {
        mLastUidCpuActiveTimeMs.delete(uid);
    }

    public void removeUidsInRange(int startUid, int endUid) {
        if (endUid < startUid) {
            Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid);
            return;
        }
        mLastUidCpuActiveTimeMs.put(startUid, null);
        mLastUidCpuActiveTimeMs.put(endUid, null);
        final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid);
        final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
        mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
    }

    @VisibleForTesting
    public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException {
        String line = reader.readLine();
        if (line == null || !line.startsWith("active:")) {
            Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
            return;
        }
        if (mCoreCount == 0) {
            mCoreCount = Integer.parseInt(line.substring(line.indexOf(' ')+1));
        }
        while ((line = reader.readLine()) != null) {
            final int index = line.indexOf(' ');
            final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
            readTimesForUid(uid, line.substring(index + 1), cb);
        }
    }

    private void readTimesForUid(int uid, String line, @Nullable Callback cb) {
        long[] lastActiveTime = mLastUidCpuActiveTimeMs.get(uid);
        if (lastActiveTime == null) {
            lastActiveTime = new long[mCoreCount];
            mLastUidCpuActiveTimeMs.put(uid, lastActiveTime);
        }
        final String[] timesStr = line.split(" ");
        if (timesStr.length != mCoreCount) {
            Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, CPU cores: %d",
                    timesStr.length, mCoreCount));
            return;
        }
        long sumDeltas = 0;
        final long[] curActiveTime = new long[mCoreCount];
        boolean notify = false;
        for (int i = 0; i < mCoreCount; i++) {
            // Times read will be in units of 10ms
            curActiveTime[i] = Long.parseLong(timesStr[i], 10) * 10;
            long delta = curActiveTime[i] - lastActiveTime[i];
            if (delta < 0 || curActiveTime[i] < 0) {
                if (DEBUG) {
                    final StringBuilder sb = new StringBuilder();
                    sb.append(String.format("Malformed cpu active time for UID=%d\n", uid));
                    sb.append(String.format("data=(%d,%d)\n", lastActiveTime[i], curActiveTime[i]));
                    sb.append("times=(");
                    TimeUtils.formatDuration(mLastTimeReadMs, sb);
                    sb.append(",");
                    TimeUtils.formatDuration(mNowTimeMs, sb);
                    sb.append(")");
                    Slog.e(TAG, sb.toString());
                }
                return;
            }
            notify |= delta > 0;
            sumDeltas += delta / (i + 1);
        }
        if (notify) {
            System.arraycopy(curActiveTime, 0, lastActiveTime, 0, mCoreCount);
            if (cb != null) {
                cb.onUidCpuActiveTime(uid, sumDeltas);
            }
        }
    }
}
Loading