From e43530ab571e901f94361078c7c1f970a0bd27f2 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Fri, 21 Aug 2009 13:11:37 -0700 Subject: [PATCH] Track CPU speed stepping to get more accurate CPU cost per app. More CPU speed stepping happening with newer devices, so we need to qualify CPU time with the CPU speed, since power consumption varies greatly by speed. Apps that peg the CPU should get a higher penaltly. Also, fix for 2062930: NPE at VolumePreference.onKey() --- core/java/android/os/BatteryStats.java | 12 +++ .../android/preference/VolumePreference.java | 3 + .../android/internal/os/BatteryStatsImpl.java | 78 ++++++++++++++++-- .../com/android/internal/os/PowerProfile.java | 23 +++--- core/res/res/xml/power_profile.xml | 15 +++- .../java/com/android/server/ProcessStats.java | 79 ++++++++++++++++++- .../server/am/ActivityManagerService.java | 3 + .../server/am/BatteryStatsService.java | 2 + 8 files changed, 196 insertions(+), 19 deletions(-) diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e203fd5902582..a49a27a06368b 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -314,6 +314,15 @@ public abstract class BatteryStats implements Parcelable { * @return foreground cpu time in microseconds */ public abstract long getForegroundTime(int which); + + /** + * Returns the approximate cpu time spent in microseconds, at a certain CPU speed. + * @param speedStep the index of the CPU speed. This is not the actual speed of the + * CPU. + * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED + * @see BatteryStats#getCpuSpeedSteps() + */ + public abstract long getTimeAtCpuSpeedStep(int speedStep, int which); } /** @@ -573,6 +582,9 @@ public abstract class BatteryStats implements Parcelable { public abstract Map getKernelWakelockStats(); + /** Returns the number of different speeds that the CPU can run at */ + public abstract int getCpuSpeedSteps(); + private final static void formatTimeRaw(StringBuilder out, long seconds) { long days = seconds / (60 * 60 * 24); if (days != 0) { diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index b337d2803cfd1..a2645949b8a55 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -121,6 +121,9 @@ public class VolumePreference extends SeekBarPreference implements if (mSeekBarVolumizer != null) { Dialog dialog = getDialog(); if (dialog != null && dialog.isShowing()) { + View view = dialog.getWindow().getDecorView() + .findViewById(com.android.internal.R.id.seekbar); + if (view != null) view.setOnKeyListener(null); // Stopped while dialog was showing, revert changes mSeekBarVolumizer.revertVolume(); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 2da72df59e7fd..35c66ba3bbb7e 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -56,7 +56,9 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 39; + private static final int VERSION = 40; + + private static int sNumSpeedSteps; private final File mFile; private final File mBackupFile; @@ -213,7 +215,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * State for keeping track of counting information. */ - public static final class Counter extends BatteryStats.Counter implements Unpluggable { + public static class Counter extends BatteryStats.Counter implements Unpluggable { int mCount; int mLoadedCount; int mLastCount; @@ -302,7 +304,22 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedCount = mPluggedCount = mCount; } } - + + public static class SamplingCounter extends Counter { + + SamplingCounter(ArrayList unpluggables, Parcel in) { + super(unpluggables, in); + } + + SamplingCounter(ArrayList unpluggables) { + super(unpluggables); + } + + public void addCountLocked(long count) { + mCount += count; + } + } + /** * State for keeping track of timing information. */ @@ -1940,10 +1957,16 @@ public final class BatteryStatsImpl extends BatteryStats { */ long mUnpluggedForegroundTime; + SamplingCounter[] mSpeedBins; + Proc() { mUnpluggables.add(this); + mSpeedBins = new SamplingCounter[getCpuSpeedSteps()]; + for (int i = 0; i < mSpeedBins.length; i++) { + mSpeedBins[i] = new SamplingCounter(mUnpluggables); + } } - + public void unplug(long batteryUptime, long batteryRealtime) { mUnpluggedUserTime = mUserTime; mUnpluggedSystemTime = mSystemTime; @@ -1974,6 +1997,11 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mUnpluggedSystemTime); out.writeLong(mUnpluggedForegroundTime); out.writeInt(mUnpluggedStarts); + + out.writeInt(mSpeedBins.length); + for (int i = 0; i < mSpeedBins.length; i++) { + mSpeedBins[i].writeToParcel(out); + } } void readFromParcelLocked(Parcel in) { @@ -1993,6 +2021,12 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedSystemTime = in.readLong(); mUnpluggedForegroundTime = in.readLong(); mUnpluggedStarts = in.readInt(); + + int bins = in.readInt(); + mSpeedBins = new SamplingCounter[bins]; + for (int i = 0; i < bins; i++) { + mSpeedBins[i] = new SamplingCounter(mUnpluggables, in); + } } public BatteryStatsImpl getBatteryStats() { @@ -2075,6 +2109,22 @@ public final class BatteryStatsImpl extends BatteryStats { } return val; } + + /* Called by ActivityManagerService when CPU times are updated. */ + public void addSpeedStepTimes(long[] values) { + for (int i = 0; i < mSpeedBins.length && i < values.length; i++) { + mSpeedBins[i].addCountLocked(values[i]); + } + } + + @Override + public long getTimeAtCpuSpeedStep(int speedStep, int which) { + if (speedStep < mSpeedBins.length) { + return mSpeedBins[speedStep].getCountLocked(which); + } else { + return 0; + } + } } /** @@ -2625,6 +2675,10 @@ public final class BatteryStatsImpl extends BatteryStats { readFromParcel(p); } + public void setNumSpeedSteps(int steps) { + if (sNumSpeedSteps == 0) sNumSpeedSteps = steps; + } + @Override public int getStartCount() { return mStartCount; @@ -2853,6 +2907,11 @@ public final class BatteryStatsImpl extends BatteryStats { return mDischargeCurrentLevel; } + @Override + public int getCpuSpeedSteps() { + return sNumSpeedSteps; + } + /** * Retrieve the statistics object for a particular uid, creating if needed. */ @@ -3063,7 +3122,9 @@ public final class BatteryStatsImpl extends BatteryStats { getKernelWakelockTimerLocked(kwltName).readSummaryFromParcelLocked(in); } } - + + sNumSpeedSteps = in.readInt(); + final int NU = in.readInt(); for (int iu = 0; iu < NU; iu++) { int uid = in.readInt(); @@ -3206,6 +3267,7 @@ public final class BatteryStatsImpl extends BatteryStats { } } + out.writeInt(sNumSpeedSteps); final int NU = mUidStats.size(); out.writeInt(NU); for (int iu = 0; iu < NU; iu++) { @@ -3404,6 +3466,8 @@ public final class BatteryStatsImpl extends BatteryStats { mFullTimers.clear(); mWindowTimers.clear(); + sNumSpeedSteps = in.readInt(); + int numUids = in.readInt(); mUidStats.clear(); for (int i = 0; i < numUids; i++) { @@ -3484,7 +3548,9 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(0); } } - + + out.writeInt(sNumSpeedSteps); + int size = mUidStats.size(); out.writeInt(size); for (int i = 0; i < size; i++) { diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 94f703add4692..4b4b71782552a 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -47,14 +47,9 @@ public class PowerProfile { public static final String POWER_CPU_IDLE = "cpu.idle"; /** - * Power consumption when CPU is running at normal speed. + * Power consumption when CPU is in power collapse mode. */ - public static final String POWER_CPU_NORMAL = "cpu.normal"; - - /** - * Power consumption when CPU is running at full speed. - */ - public static final String POWER_CPU_FULL = "cpu.full"; + public static final String POWER_CPU_ACTIVE = "cpu.active"; /** * Power consumption when WiFi driver is scanning for networks. @@ -124,6 +119,8 @@ public class PowerProfile { */ public static final String POWER_VIDEO = "dsp.video"; + public static final String POWER_CPU_SPEEDS = "cpu.speeds"; + static final HashMap sPowerMap = new HashMap(); private static final String TAG_DEVICE = "device"; @@ -214,10 +211,10 @@ public class PowerProfile { } /** - * Returns the average current in mA consumed by the subsystem for the given level. + * Returns the average current in mA consumed by the subsystem for the given level. * @param type the subsystem type * @param level the level of power at which the subsystem is running. For instance, the - * signal strength of the cell network between 0 and 4 (if there are 4 bars max.). + * signal strength of the cell network between 0 and 4 (if there are 4 bars max.) * If there is no data for multiple levels, the level is ignored. * @return the average current in milliAmps. */ @@ -240,4 +237,12 @@ public class PowerProfile { return 0; } } + + public int getNumSpeedSteps() { + Object value = sPowerMap.get(POWER_CPU_SPEEDS); + if (value != null && value instanceof Double[]) { + return ((Double[])value).length; + } + return 1; // Only one speed + } } diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index 859902e73d9ed..710b71ea7e75c 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -26,15 +26,24 @@ 0.1 0.1 0.1 - 0.1 - 0.2 - 1 0.1 0.1 1 1 + 1 0.1 + + + 400000 + + + 0.1 + + + 0.2 + diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index af80e20cc0e66..eaab3ebfb86b3 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -30,6 +30,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.StringTokenizer; public class ProcessStats { private static final String TAG = "ProcessStats"; @@ -138,7 +139,22 @@ public class ProcessStats { private boolean mFirst = true; private byte[] mBuffer = new byte[256]; - + + /** + * The time in microseconds that the CPU has been running at each speed. + */ + private long[] mCpuSpeedTimes; + + /** + * The relative time in microseconds that the CPU has been running at each speed. + */ + private long[] mRelCpuSpeedTimes; + + /** + * The different speeds that the CPU can be running at. + */ + private long[] mCpuSpeeds; + public static class Stats { public final int pid; final String statFile; @@ -460,6 +476,67 @@ public class ProcessStats { return 0; } + /** + * Returns the times spent at each CPU speed, since the last call to this method. If this + * is the first time, it will return 1 for each value. + * @return relative times spent at different speed steps. + */ + public long[] getLastCpuSpeedTimes() { + if (mCpuSpeedTimes == null) { + mCpuSpeedTimes = getCpuSpeedTimes(null); + mRelCpuSpeedTimes = new long[mCpuSpeedTimes.length]; + for (int i = 0; i < mCpuSpeedTimes.length; i++) { + mRelCpuSpeedTimes[i] = 1; // Initialize + } + } else { + getCpuSpeedTimes(mRelCpuSpeedTimes); + for (int i = 0; i < mCpuSpeedTimes.length; i++) { + long temp = mRelCpuSpeedTimes[i]; + mRelCpuSpeedTimes[i] -= mCpuSpeedTimes[i]; + mCpuSpeedTimes[i] = temp; + } + } + return mRelCpuSpeedTimes; + } + + private long[] getCpuSpeedTimes(long[] out) { + long[] tempTimes = out; + long[] tempSpeeds = mCpuSpeeds; + final int MAX_SPEEDS = 20; + if (out == null) { + tempTimes = new long[MAX_SPEEDS]; // Hopefully no more than that + tempSpeeds = new long[MAX_SPEEDS]; + } + int speed = 0; + String file = readFile("/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state", '\0'); + StringTokenizer st = new StringTokenizer(file, "\n "); + while (st.hasMoreElements()) { + String token = st.nextToken(); + try { + long val = Long.parseLong(token); + tempSpeeds[speed] = val; + token = st.nextToken(); + val = Long.parseLong(token); + tempTimes[speed] = val; + speed++; + if (speed == MAX_SPEEDS) break; // No more + if (localLOGV && out == null) { + Log.v(TAG, "First time : Speed/Time = " + tempSpeeds[speed - 1] + + "\t" + tempTimes[speed - 1]); + } + } catch (NumberFormatException nfe) { + Log.i(TAG, "Unable to parse time_in_state"); + } + } + if (out == null) { + out = new long[speed]; + mCpuSpeeds = new long[speed]; + System.arraycopy(tempSpeeds, 0, mCpuSpeeds, 0, speed); + System.arraycopy(tempTimes, 0, out, 0, speed); + } + return out; + } + final public int getLastUserTime() { return mRelUserTime; } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 4db5239e67ada..d4f7207ecbd41 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -1572,6 +1572,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + long[] cpuSpeedTimes = mProcessStats.getLastCpuSpeedTimes(); final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics(); synchronized(bstats) { synchronized(mPidsSelfLocked) { @@ -1585,11 +1586,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (pr != null) { BatteryStatsImpl.Uid.Proc ps = pr.batteryStats; ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addSpeedStepTimes(cpuSpeedTimes); } else { BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(st.name, st.pid); if (ps != null) { ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addSpeedStepTimes(cpuSpeedTimes); } } } diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index ed0d534587f68..61537f56155f5 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -28,6 +28,7 @@ import android.util.Log; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.PowerProfile; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -49,6 +50,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void publish(Context context) { mContext = context; ServiceManager.addService("batteryinfo", asBinder()); + mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps()); } public void shutdown() {