diff options
| author | 2014-01-16 01:39:06 +0000 | |
|---|---|---|
| committer | 2014-01-16 01:39:06 +0000 | |
| commit | c8345b32c9636d4d2984a2e9f23dd0391c664ba4 (patch) | |
| tree | ea6a78f3820b7bd2c567d47334a9644991d5cfbb | |
| parent | 0430ee5acb1afe4d1e950eb586d302e35d0c49b1 (diff) | |
| parent | a7c837f043c1ca0bdecd42645ba7da8c5717566d (diff) | |
Merge "Add battery power use reporting to batterystats service."
5 files changed, 969 insertions, 21 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 58b94ca8cb1c..10c386a0a665 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -24,11 +24,14 @@ import java.util.Formatter; import java.util.List; import java.util.Map; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.telephony.SignalStrength; import android.util.Printer; import android.util.SparseArray; import android.util.TimeUtils; +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; /** * A class providing access to battery usage statistics, including information on @@ -166,6 +169,8 @@ public abstract class BatteryStats implements Parcelable { private static final String SIGNAL_STRENGTH_COUNT_DATA = "sgc"; private static final String DATA_CONNECTION_TIME_DATA = "dct"; private static final String DATA_CONNECTION_COUNT_DATA = "dcc"; + private static final String POWER_USE_SUMMARY_DATA = "pws"; + private static final String POWER_USE_ITEM_DATA = "pwi"; private final StringBuilder mFormatBuilder = new StringBuilder(32); private final Formatter mFormatter = new Formatter(mFormatBuilder); @@ -865,6 +870,8 @@ public abstract class BatteryStats implements Parcelable { */ public abstract long getBluetoothOnTime(long batteryRealtime, int which); + public abstract int getBluetoothPingCount(); + public static final int NETWORK_MOBILE_RX_DATA = 0; public static final int NETWORK_MOBILE_TX_DATA = 1; public static final int NETWORK_WIFI_RX_DATA = 2; @@ -997,6 +1004,8 @@ public abstract class BatteryStats implements Parcelable { /** 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) { long days = seconds / (60 * 60 * 24); if (days != 0) { @@ -1163,7 +1172,7 @@ public abstract class BatteryStats implements Parcelable { * * NOTE: all times are expressed in 'ms'. */ - public final void dumpCheckinLocked(PrintWriter pw, int which, int reqUid) { + public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid) { final long rawUptime = SystemClock.uptimeMillis() * 1000; final long rawRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryUptime = getBatteryUptime(rawUptime); @@ -1300,6 +1309,61 @@ public abstract class BatteryStats implements Parcelable { } } + BatteryStatsHelper helper = new BatteryStatsHelper(context); + helper.create(this); + helper.refreshStats(which, UserHandle.USER_ALL); + List<BatterySipper> sippers = helper.getUsageList(); + if (sippers != null && sippers.size() > 0) { + dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA, + BatteryStatsHelper.makemAh(helper.getPowerProfile().getBatteryCapacity()), + BatteryStatsHelper.makemAh(helper.getTotalPower()), + BatteryStatsHelper.makemAh(helper.getMinDrainedPower()), + BatteryStatsHelper.makemAh(helper.getMaxDrainedPower())); + for (int i=0; i<sippers.size(); i++) { + BatterySipper bs = sippers.get(i); + int uid = 0; + String label; + switch (bs.drainType) { + case IDLE: + label="idle"; + break; + case CELL: + label="cell"; + break; + case PHONE: + label="phone"; + break; + case WIFI: + label="wifi"; + break; + case BLUETOOTH: + label="blue"; + break; + case SCREEN: + label="scrn"; + break; + case APP: + uid = bs.uidObj.getUid(); + label = "uid"; + break; + case USER: + uid = UserHandle.getUid(bs.userId, 0); + label = "user"; + break; + case UNACCOUNTED: + label = "unacc"; + break; + case OVERCOUNTED: + label = "over"; + break; + default: + label = "???"; + } + dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label, + BatteryStatsHelper.makemAh(bs.value)); + } + } + for (int iu = 0; iu < NU; iu++) { final int uid = uidStats.keyAt(iu); if (reqUid >= 0 && uid != reqUid) { @@ -1471,8 +1535,13 @@ public abstract class BatteryStats implements Parcelable { } } + private void printmAh(PrintWriter printer, double power) { + printer.print(BatteryStatsHelper.makemAh(power)); + } + @SuppressWarnings("unused") - public final void dumpLocked(PrintWriter pw, String prefix, final int which, int reqUid) { + public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which, + int reqUid) { final long rawUptime = SystemClock.uptimeMillis() * 1000; final long rawRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryUptime = getBatteryUptime(rawUptime); @@ -1758,6 +1827,65 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } + BatteryStatsHelper helper = new BatteryStatsHelper(context); + helper.create(this); + helper.refreshStats(which, UserHandle.USER_ALL); + List<BatterySipper> sippers = helper.getUsageList(); + if (sippers != null && sippers.size() > 0) { + pw.print(prefix); pw.println(" Estimated power use (mAh):"); + pw.print(prefix); pw.print(" Capacity: "); + printmAh(pw, helper.getPowerProfile().getBatteryCapacity()); + pw.print(", Computed drain: "); printmAh(pw, helper.getTotalPower()); + pw.print(", Min drain: "); printmAh(pw, helper.getMinDrainedPower()); + pw.print(", Max drain: "); printmAh(pw, helper.getMaxDrainedPower()); + pw.println(); + for (int i=0; i<sippers.size(); i++) { + BatterySipper bs = sippers.get(i); + switch (bs.drainType) { + case IDLE: + pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value); + pw.println(); + break; + case CELL: + pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.value); + pw.println(); + break; + case PHONE: + pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.value); + pw.println(); + break; + case WIFI: + pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.value); + pw.println(); + break; + case BLUETOOTH: + pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.value); + pw.println(); + break; + case SCREEN: + pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.value); + pw.println(); + break; + case APP: + pw.print(prefix); pw.print(" Uid "); pw.print(bs.uidObj.getUid()); + pw.print(": "); printmAh(pw, bs.value); pw.println(); + break; + case USER: + pw.print(prefix); pw.print(" User "); pw.print(bs.userId); + pw.print(": "); printmAh(pw, bs.value); pw.println(); + break; + case UNACCOUNTED: + pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.value); + pw.println(); + break; + case OVERCOUNTED: + pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.value); + pw.println(); + break; + } + } + } + if (timers.size() > 0) { Collections.sort(timers, timerComparator); pw.print(prefix); pw.println(" All partial wake locks:"); @@ -2287,7 +2415,7 @@ public abstract class BatteryStats implements Parcelable { * @param pw a Printer to receive the dump output. */ @SuppressWarnings("unused") - public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { + public void dumpLocked(Context context, PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { prepareForDumpLocked(); long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); @@ -2344,15 +2472,15 @@ public abstract class BatteryStats implements Parcelable { pw.println("Statistics since last charge:"); pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); - dumpLocked(pw, "", STATS_SINCE_CHARGED, reqUid); + dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid); pw.println(""); } pw.println("Statistics since last unplugged:"); - dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, reqUid); + dumpLocked(context, pw, "", STATS_SINCE_UNPLUGGED, reqUid); } @SuppressWarnings("unused") - public void dumpCheckinLocked( + public void dumpCheckinLocked(Context context, PrintWriter pw, List<ApplicationInfo> apps, boolean isUnpluggedOnly, boolean includeHistory) { prepareForDumpLocked(); @@ -2400,11 +2528,11 @@ public abstract class BatteryStats implements Parcelable { } } if (isUnpluggedOnly) { - dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1); + dumpCheckinLocked(context, pw, STATS_SINCE_UNPLUGGED, -1); } else { - dumpCheckinLocked(pw, STATS_SINCE_CHARGED, -1); - dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1); + dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1); + dumpCheckinLocked(context, pw, STATS_SINCE_UNPLUGGED, -1); } } } diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java new file mode 100644 index 000000000000..4eff5acfc079 --- /dev/null +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009 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.os.BatteryStats.Uid; + +/** + * Contains power usage of an application, system service, or hardware type. + */ +public class BatterySipper implements Comparable<BatterySipper> { + public int userId; + public Uid uidObj; + public double value; + public double[] values; + public DrainType drainType; + public long usageTime; + public long cpuTime; + public long gpsTime; + public long wifiRunningTime; + public long cpuFgTime; + public long wakeLockTime; + public long mobileRxPackets; + public long mobileTxPackets; + public long wifiRxPackets; + public long wifiTxPackets; + public long mobileRxBytes; + public long mobileTxBytes; + public long wifiRxBytes; + public long wifiTxBytes; + public double percent; + public double noCoveragePercent; + public String[] mPackages; + public String packageWithHighestDrain; + + public enum DrainType { + IDLE, + CELL, + PHONE, + WIFI, + BLUETOOTH, + SCREEN, + APP, + USER, + UNACCOUNTED, + OVERCOUNTED + } + + public BatterySipper(DrainType drainType, Uid uid, double[] values) { + this.values = values; + if (values != null) value = values[0]; + this.drainType = drainType; + uidObj = uid; + } + + public double[] getValues() { + return values; + } + + @Override + public int compareTo(BatterySipper other) { + // Return the flipped value because we want the items in descending order + return Double.compare(other.value, value); + } + + /** + * Gets a list of packages associated with the current user + */ + public String[] getPackages() { + return mPackages; + } + + public int getUid() { + // Bail out if the current sipper is not an App sipper. + if (uidObj == null) { + return 0; + } + return uidObj.getUid(); + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java new file mode 100644 index 000000000000..4c851e79e619 --- /dev/null +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2009 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 static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA; +import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA; +import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA; +import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.net.ConnectivityManager; +import android.os.BatteryStats; +import android.os.BatteryStats.Uid; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.telephony.SignalStrength; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BatterySipper.DrainType; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * A helper class for retrieving the power usage information for all applications and services. + * + * The caller must initialize this class as soon as activity object is ready to use (for example, in + * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). + */ +public class BatteryStatsHelper { + + private static final boolean DEBUG = true; + + private static final String TAG = BatteryStatsHelper.class.getSimpleName(); + + private static BatteryStats sStatsXfer; + + final private Context mContext; + + private IBatteryStats mBatteryInfo; + private BatteryStats mStats; + private PowerProfile mPowerProfile; + + private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>(); + private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>(); + private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>(); + private final SparseArray<List<BatterySipper>> mUserSippers + = new SparseArray<List<BatterySipper>>(); + private final SparseArray<Double> mUserPower = new SparseArray<Double>(); + + private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; + private int mAsUser = 0; + + long mBatteryRealtime; + long mBatteryUptime; + long mTypeBatteryRealtime; + long mTypeBatteryUptime; + + private long mStatsPeriod = 0; + private double mMaxPower = 1; + private double mTotalPower; + private double mWifiPower; + private double mBluetoothPower; + private double mMinDrainedPower; + private double mMaxDrainedPower; + + // How much the apps together have left WIFI running. + private long mAppWifiRunning; + + public BatteryStatsHelper(Context context) { + mContext = context; + } + + /** Clears the current stats and forces recreating for future use. */ + public void clearStats() { + mStats = null; + } + + public BatteryStats getStats() { + if (mStats == null) { + load(); + } + return mStats; + } + + public PowerProfile getPowerProfile() { + return mPowerProfile; + } + + public void create(BatteryStats stats) { + mPowerProfile = new PowerProfile(mContext); + mStats = stats; + } + + public void create(Bundle icicle) { + if (icicle != null) { + mStats = sStatsXfer; + } + mBatteryInfo = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + mPowerProfile = new PowerProfile(mContext); + } + + public void storeState() { + sStatsXfer = mStats; + } + + public static String makemAh(double power) { + if (power < .0001) return String.format("%.8f", power); + else if (power < .0001) return String.format("%.7f", power); + else if (power < .001) return String.format("%.6f", power); + else if (power < .01) return String.format("%.5f", power); + else if (power < .1) return String.format("%.4f", power); + else if (power < 1) return String.format("%.3f", power); + else if (power < 10) return String.format("%.2f", power); + else if (power < 100) return String.format("%.1f", power); + else return String.format("%.0f", power); + } + + /** + * Refreshes the power usage list. + */ + public void refreshStats(int statsType, int asUser) { + refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000, + SystemClock.uptimeMillis() * 1000); + } + + public void refreshStats(int statsType, int asUser, long rawRealtimeNano, long rawUptimeNano) { + // Initialize mStats if necessary. + getStats(); + + mMaxPower = 0; + mTotalPower = 0; + mWifiPower = 0; + mBluetoothPower = 0; + mAppWifiRunning = 0; + + mUsageList.clear(); + mWifiSippers.clear(); + mBluetoothSippers.clear(); + mUserSippers.clear(); + mUserPower.clear(); + + if (mStats == null) { + return; + } + + mStatsType = statsType; + mAsUser = asUser; + mBatteryUptime = mStats.getBatteryUptime(rawUptimeNano); + mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeNano); + mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeNano, mStatsType); + mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeNano, mStatsType); + + if (DEBUG) { + Log.d(TAG, "Raw time: realtime=" + (rawRealtimeNano/1000) + " uptime=" + + (rawUptimeNano/1000)); + Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime=" + + (mBatteryUptime/1000)); + Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime=" + + (mTypeBatteryUptime/1000)); + } + mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() + * mPowerProfile.getBatteryCapacity()) / 100; + mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() + * mPowerProfile.getBatteryCapacity()) / 100; + + processAppUsage(); + processMiscUsage(); + + if (DEBUG) { + Log.d(TAG, "Accuracy: total computed=" + makemAh(mTotalPower) + ", min discharge=" + + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower)); + } + if (true || mStats.getLowDischargeAmountSinceCharge() > 10) { + if (mMinDrainedPower > mTotalPower) { + double amount = mMinDrainedPower - mTotalPower; + if (mMaxPower < amount) { + mMaxPower = amount; + } + addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount); + } else if (mMaxDrainedPower < mTotalPower) { + double amount = mTotalPower - mMaxDrainedPower; + if (mMaxPower < amount) { + mMaxPower = amount; + } + addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount); + } + } + + Collections.sort(mUsageList); + } + + private void processAppUsage() { + SensorManager sensorManager = (SensorManager) mContext.getSystemService( + Context.SENSOR_SERVICE); + final int which = mStatsType; + final int speedSteps = mPowerProfile.getNumSpeedSteps(); + final double[] powerCpuNormal = new double[speedSteps]; + final long[] cpuSpeedStepTimes = new long[speedSteps]; + for (int p = 0; p < speedSteps; p++) { + powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); + } + final double mobilePowerPerPacket = getMobilePowerPerPacket(); + final double wifiPowerPerPacket = getWifiPowerPerPacket(); + long appWakelockTime = 0; + BatterySipper osApp = null; + mStatsPeriod = mTypeBatteryRealtime; + SparseArray<? extends Uid> uidStats = mStats.getUidStats(); + final int NU = uidStats.size(); + for (int iu = 0; iu < NU; iu++) { + Uid u = uidStats.valueAt(iu); + double p; // in mAs + double power = 0; // in mAs + double highestDrain = 0; + String packageWithHighestDrain = null; + //mUsageList.add(new AppUsage(u.getUid(), new double[] {power})); + Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); + long cpuTime = 0; + long cpuFgTime = 0; + long wakelockTime = 0; + long gpsTime = 0; + if (processStats.size() > 0) { + // Process CPU time + for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent + : processStats.entrySet()) { + Uid.Proc ps = ent.getValue(); + final long userTime = ps.getUserTime(which); + final long systemTime = ps.getSystemTime(which); + final long foregroundTime = ps.getForegroundTime(which); + cpuFgTime += foregroundTime * 10; // convert to millis + final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis + int totalTimeAtSpeeds = 0; + // Get the total first + for (int step = 0; step < speedSteps; step++) { + cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); + totalTimeAtSpeeds += cpuSpeedStepTimes[step]; + } + if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1; + // Then compute the ratio of time spent at each speed + double processPower = 0; + for (int step = 0; step < speedSteps; step++) { + double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; + if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #" + + step + " ratio=" + makemAh(ratio) + " power=" + + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000))); + processPower += ratio * tmpCpuTime * powerCpuNormal[step]; + } + cpuTime += tmpCpuTime; + if (DEBUG && processPower != 0) { + Log.d(TAG, String.format("process %s, cpu power=%s", + ent.getKey(), makemAh(processPower / (60*60*1000)))); + } + power += processPower; + if (packageWithHighestDrain == null + || packageWithHighestDrain.startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } else if (highestDrain < processPower + && !ent.getKey().startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } + } + } + if (cpuFgTime > cpuTime) { + if (DEBUG && cpuFgTime > cpuTime + 10000) { + Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); + } + cpuTime = cpuFgTime; // Statistics may not have been gathered yet. + } + power /= (60*60*1000); + + // Process wake lock usage + Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats(); + for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry + : wakelockStats.entrySet()) { + Uid.Wakelock wakelock = wakelockEntry.getValue(); + // Only care about partial wake locks since full wake locks + // are canceled when the user turns the screen off. + BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL); + if (timer != null) { + wakelockTime += timer.getTotalTimeLocked(mBatteryRealtime, which); + } + } + wakelockTime /= 1000; // convert to millis + appWakelockTime += wakelockTime; + + // Add cost of holding a wake lock + p = (wakelockTime + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake " + + wakelockTime + " power=" + makemAh(p)); + power += p; + + // Add cost of mobile traffic + final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType); + p = (mobileRx + mobileTx) * mobilePowerPerPacket; + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets " + + (mobileRx+mobileTx) + " power=" + makemAh(p)); + power += p; + + // Add cost of wifi traffic + final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType); + final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType); + final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType); + final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType); + p = (wifiRx + wifiTx) * wifiPowerPerPacket; + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets " + + (mobileRx+mobileTx) + " power=" + makemAh(p)); + power += p; + + // Add cost of keeping WIFI running. + long wifiRunningTimeMs = u.getWifiRunningTime(mBatteryRealtime, which) / 1000; + mAppWifiRunning += wifiRunningTimeMs; + p = (wifiRunningTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running " + + wifiRunningTimeMs + " power=" + makemAh(p)); + power += p; + + // Add cost of WIFI scans + long wifiScanTimeMs = u.getWifiScanTime(mBatteryRealtime, which) / 1000; + p = (wifiScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000); + if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs + + " power=" + makemAh(p)); + power += p; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mBatteryRealtime, which) / 1000; + p = ((batchScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin)) + ) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin + + " time=" + batchScanTimeMs + " power=" + makemAh(p)); + power += p; + } + + // Process Sensor usage + Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); + for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry + : sensorStats.entrySet()) { + Uid.Sensor sensor = sensorEntry.getValue(); + int sensorHandle = sensor.getHandle(); + BatteryStats.Timer timer = sensor.getSensorTime(); + long sensorTime = timer.getTotalTimeLocked(mBatteryRealtime, which) / 1000; + double multiplier = 0; + switch (sensorHandle) { + case Uid.Sensor.GPS: + multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); + gpsTime = sensorTime; + break; + default: + List<Sensor> sensorList = sensorManager.getSensorList( + android.hardware.Sensor.TYPE_ALL); + for (android.hardware.Sensor s : sensorList) { + if (s.getHandle() == sensorHandle) { + multiplier = s.getPower(); + break; + } + } + } + p = (multiplier * sensorTime) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle + + " time=" + sensorTime + " power=" + makemAh(p)); + power += p; + } + + if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s", + u.getUid(), makemAh(power))); + + // Add the app to the list if it is consuming power + boolean isOtherUser = false; + final int userId = UserHandle.getUserId(u.getUid()); + if (power != 0 || u.getUid() == 0) { + BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, + new double[] {power}); + app.cpuTime = cpuTime; + app.gpsTime = gpsTime; + app.wifiRunningTime = wifiRunningTimeMs; + app.cpuFgTime = cpuFgTime; + app.wakeLockTime = wakelockTime; + app.mobileRxPackets = mobileRx; + app.mobileTxPackets = mobileTx; + app.wifiRxPackets = wifiRx; + app.wifiTxPackets = wifiTx; + app.mobileRxBytes = mobileRxB; + app.mobileTxBytes = mobileTxB; + app.wifiRxBytes = wifiRxB; + app.wifiTxBytes = wifiTxB; + app.packageWithHighestDrain = packageWithHighestDrain; + if (u.getUid() == Process.WIFI_UID) { + mWifiSippers.add(app); + } else if (u.getUid() == Process.BLUETOOTH_UID) { + mBluetoothSippers.add(app); + } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser + && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { + isOtherUser = true; + List<BatterySipper> list = mUserSippers.get(userId); + if (list == null) { + list = new ArrayList<BatterySipper>(); + mUserSippers.put(userId, list); + } + list.add(app); + } else { + mUsageList.add(app); + } + if (u.getUid() == 0) { + osApp = app; + } + } + if (power != 0) { + if (u.getUid() == Process.WIFI_UID) { + mWifiPower += power; + } else if (u.getUid() == Process.BLUETOOTH_UID) { + mBluetoothPower += power; + } else if (isOtherUser) { + Double userPower = mUserPower.get(userId); + if (userPower == null) { + userPower = power; + } else { + userPower += power; + } + mUserPower.put(userId, userPower); + } else { + if (power > mMaxPower) mMaxPower = power; + mTotalPower += power; + } + } + } + + // The device has probably been awake for longer than the screen on + // time and application wake lock time would account for. Assign + // this remainder to the OS, if possible. + if (osApp != null) { + long wakeTimeMillis = mBatteryUptime / 1000; + wakeTimeMillis -= appWakelockTime + + (mStats.getScreenOnTime(mBatteryRealtime, which) / 1000); + if (wakeTimeMillis > 0) { + double power = (wakeTimeMillis + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) + / (60*60*1000); + if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + + makemAh(power)); + osApp.wakeLockTime += wakeTimeMillis; + osApp.value += power; + osApp.values[0] += power; + if (osApp.value > mMaxPower) mMaxPower = osApp.value; + mTotalPower += power; + } + } + } + + private void addPhoneUsage() { + long phoneOnTimeMs = mStats.getPhoneOnTime(mBatteryRealtime, mStatsType) / 1000; + double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + * phoneOnTimeMs / (60*60*1000); + addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); + } + + private void addScreenUsage() { + double power = 0; + long screenOnTimeMs = mStats.getScreenOnTime(mBatteryRealtime, mStatsType) / 1000; + power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); + final double screenFullPower = + mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); + for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { + double screenBinPower = screenFullPower * (i + 0.5f) + / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + long brightnessTime = mStats.getScreenBrightnessTime(i, mBatteryRealtime, mStatsType) + / 1000; + double p = screenBinPower*brightnessTime; + if (DEBUG && p != 0) { + Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime + + " power=" + makemAh(p/(60*60*1000))); + } + power += p; + } + power /= (60*60*1000); // To hours + addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); + } + + private void addRadioUsage() { + double power = 0; + final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + long signalTimeMs = 0; + long noCoverageTimeMs = 0; + for (int i = 0; i < BINS; i++) { + long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mBatteryRealtime, mStatsType) + / 1000; + double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i)) + / (60*60*1000); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + + makemAh(p)); + } + power += p; + signalTimeMs += strengthTimeMs; + if (i == 0) { + noCoverageTimeMs = strengthTimeMs; + } + } + long scanningTimeMs = mStats.getPhoneSignalScanningTime(mBatteryRealtime, mStatsType) + / 1000; + double p = (scanningTimeMs * mPowerProfile.getAveragePower( + PowerProfile.POWER_RADIO_SCANNING)) + / (60*60*1000); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p)); + } + power += p; + BatterySipper bs = + addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power); + if (signalTimeMs != 0) { + bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; + } + } + + private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) { + for (int i=0; i<from.size(); i++) { + BatterySipper wbs = from.get(i); + if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime); + bs.cpuTime += wbs.cpuTime; + bs.gpsTime += wbs.gpsTime; + bs.wifiRunningTime += wbs.wifiRunningTime; + bs.cpuFgTime += wbs.cpuFgTime; + bs.wakeLockTime += wbs.wakeLockTime; + bs.mobileRxPackets += wbs.mobileRxPackets; + bs.mobileTxPackets += wbs.mobileTxPackets; + bs.wifiRxPackets += wbs.wifiRxPackets; + bs.wifiTxPackets += wbs.wifiTxPackets; + bs.mobileRxBytes += wbs.mobileRxBytes; + bs.mobileTxBytes += wbs.mobileTxBytes; + bs.wifiRxBytes += wbs.wifiRxBytes; + bs.wifiTxBytes += wbs.wifiTxBytes; + } + } + + private void addWiFiUsage() { + long onTimeMs = mStats.getWifiOnTime(mBatteryRealtime, mStatsType) / 1000; + long runningTimeMs = mStats.getGlobalWifiRunningTime(mBatteryRealtime, mStatsType) / 1000; + if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs + + " app runningTime=" + mAppWifiRunning); + runningTimeMs -= mAppWifiRunning; + if (runningTimeMs < 0) runningTimeMs = 0; + double wifiPower = (onTimeMs * 0 /* TODO */ + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON) + + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) + / (60*60*1000); + if (DEBUG && wifiPower != 0) { + Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower)); + } + BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs, + wifiPower + mWifiPower); + aggregateSippers(bs, mWifiSippers, "WIFI"); + } + + private void addIdleUsage() { + long idleTimeMs = (mTypeBatteryRealtime + - mStats.getScreenOnTime(mBatteryRealtime, mStatsType)) / 1000; + double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)) + / (60*60*1000); + if (DEBUG && idlePower != 0) { + Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower)); + } + addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower); + } + + private void addBluetoothUsage() { + long btOnTimeMs = mStats.getBluetoothOnTime(mBatteryRealtime, mStatsType) / 1000; + double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON) + / (60*60*1000); + if (DEBUG && btPower != 0) { + Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower)); + } + int btPingCount = mStats.getBluetoothPingCount(); + double pingPower = (btPingCount + * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) + / (60*60*1000); + if (DEBUG && pingPower != 0) { + Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower)); + } + btPower += pingPower; + BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs, + btPower + mBluetoothPower); + aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); + } + + private void addUserUsage() { + for (int i=0; i<mUserSippers.size(); i++) { + final int userId = mUserSippers.keyAt(i); + final List<BatterySipper> sippers = mUserSippers.valueAt(i); + Double userPower = mUserPower.get(userId); + double power = (userPower != null) ? userPower : 0.0; + BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power); + bs.userId = userId; + aggregateSippers(bs, sippers, "User"); + } + } + + /** + * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio. + */ + private double getMobilePowerPerPacket() { + final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system + final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + / 3600; + + final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileData = mobileRx + mobileTx; + + final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000; + final double mobilePps = radioDataUptimeMs != 0 + ? mobileData / (double)radioDataUptimeMs + : (((double)MOBILE_BPS) / 8 / 2048); + + return (MOBILE_POWER / mobilePps) / (60*60); + } + + /** + * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio. + */ + private double getWifiPowerPerPacket() { + final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system + final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) + / 3600; + return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60); + } + + private void processMiscUsage() { + addUserUsage(); + addPhoneUsage(); + addScreenUsage(); + addWiFiUsage(); + addBluetoothUsage(); + addIdleUsage(); // Not including cellular idle power + // Don't compute radio usage if it's a wifi-only device + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + addRadioUsage(); + } + } + + private BatterySipper addEntry(DrainType drainType, long time, double power) { + mTotalPower += power; + return addEntryNoTotal(drainType, time, power); + } + + private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) { + if (power > mMaxPower) mMaxPower = power; + mTotalPower += power; + BatterySipper bs = new BatterySipper(drainType, null, new double[] {power}); + bs.usageTime = time; + mUsageList.add(bs); + return bs; + } + + public List<BatterySipper> getUsageList() { + return mUsageList; + } + + public long getStatsPeriod() { return mStatsPeriod; } + + public int getStatsType() { return mStatsType; }; + + public double getMaxPower() { return mMaxPower; } + + public double getTotalPower() { return mTotalPower; } + + public double getMinDrainedPower() { + return mMinDrainedPower; + } + + public double getMaxDrainedPower() { + return mMaxDrainedPower; + } + + private void load() { + if (mBatteryInfo == null) { + return; + } + try { + byte[] data = mBatteryInfo.getStatistics(); + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR + .createFromParcel(parcel); + stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); + mStats = stats; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException:", e); + } + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 794c6859515e..7d178393d6ed 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -20,6 +20,7 @@ import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENAB import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; +import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkStats; import android.os.BatteryManager; @@ -4940,6 +4941,11 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void pullPendingStateUpdatesLocked() { + updateKernelWakelocksLocked(); + updateNetworkActivityLocked(); + } + void setOnBatteryLocked(boolean onBattery, int oldStatus, int level) { boolean doWrite = false; Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE); @@ -4962,8 +4968,7 @@ public final class BatteryStatsImpl extends BatteryStats { resetAllStatsLocked(); mDischargeStartLevel = level; } - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); mHistoryCur.batteryLevel = (byte)level; mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: " @@ -4985,8 +4990,7 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeAmountScreenOff = 0; doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); } else { - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); mHistoryCur.batteryLevel = (byte)level; mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " @@ -6282,8 +6286,7 @@ public final class BatteryStatsImpl extends BatteryStats { @SuppressWarnings("unused") void writeToParcelLocked(Parcel out, boolean inclUids, int flags) { // Need to update with current kernel wake lock counts. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); final long uSecUptime = SystemClock.uptimeMillis() * 1000; final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; @@ -6388,11 +6391,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void prepareForDumpLocked() { // Need to retrieve current kernel wake lock stats before printing. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); } - public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { + public void dumpLocked(Context context, PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { if (DEBUG) { Printer pr = new PrintWriterPrinter(pw); pr.println("*** Screen timer:"); @@ -6422,6 +6424,6 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Bluetooth timer:"); mBluetoothOnTimer.logState(pr, " "); } - super.dumpLocked(pw, isUnpluggedOnly, reqUid); + super.dumpLocked(context, pw, isUnpluggedOnly, reqUid); } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 2d596788a829..9805efe7d099 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -568,11 +568,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub { if (isCheckin) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); synchronized (mStats) { - mStats.dumpCheckinLocked(pw, apps, isUnpluggedOnly, includeHistory); + mStats.dumpCheckinLocked(mContext, pw, apps, isUnpluggedOnly, includeHistory); } } else { synchronized (mStats) { - mStats.dumpLocked(pw, isUnpluggedOnly, reqUid); + mStats.dumpLocked(mContext, pw, isUnpluggedOnly, reqUid); } } } |