diff options
17 files changed, 1402 insertions, 657 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 508fdee..3051926 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -269,6 +269,15 @@ public abstract class BatteryStats implements Parcelable { public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which); /** + * Returns the total time in microseconds associated with this Timer since the + * 'mark' was last set. + * + * @param elapsedRealtimeUs current elapsed realtime of system in microseconds + * @return a time in microseconds + */ + public abstract long getTimeSinceMarkLocked(long elapsedRealtimeUs); + + /** * Temporary for debugging. */ public abstract void logState(Printer pw, String prefix); @@ -332,7 +341,17 @@ public abstract class BatteryStats implements Parcelable { * @return a Map from Strings to Uid.Pkg objects. */ public abstract ArrayMap<String, ? extends Pkg> getPackageStats(); - + + /** + * Returns the time in milliseconds that this app kept the WiFi controller in the + * specified state <code>type</code>. + * @param type one of {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, or + * {@link #CONTROLLER_TX_TIME}. + * @param which one of {@link #STATS_CURRENT}, {@link #STATS_SINCE_CHARGED}, or + * {@link #STATS_SINCE_UNPLUGGED}. + */ + public abstract long getWifiControllerActivity(int type, int which); + /** * {@hide} */ @@ -1914,7 +1933,6 @@ public abstract class BatteryStats implements Parcelable { public static final int NETWORK_MOBILE_TX_DATA = 1; public static final int NETWORK_WIFI_RX_DATA = 2; public static final int NETWORK_WIFI_TX_DATA = 3; - public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1; public abstract long getNetworkActivityBytes(int type, int which); @@ -1923,10 +1941,25 @@ public abstract class BatteryStats implements Parcelable { public static final int CONTROLLER_IDLE_TIME = 0; public static final int CONTROLLER_RX_TIME = 1; public static final int CONTROLLER_TX_TIME = 2; - public static final int CONTROLLER_ENERGY = 3; - public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_ENERGY + 1; + public static final int CONTROLLER_POWER_DRAIN = 3; + public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1; + /** + * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and + * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the + * respective state. + * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in + * milli-ampere-milliseconds (mAms). + */ public abstract long getBluetoothControllerActivity(int type, int which); + + /** + * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and + * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the + * respective state. + * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in + * milli-ampere-milliseconds (mAms). + */ public abstract long getWifiControllerActivity(int type, int which); /** @@ -2618,7 +2651,7 @@ public abstract class BatteryStats implements Parcelable { label = "???"; } dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label, - BatteryStatsHelper.makemAh(bs.value)); + BatteryStatsHelper.makemAh(bs.totalPowerMah)); } } @@ -3264,6 +3297,13 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); + sb.append(" WiFi Energy use: ").append(BatteryStatsHelper.makemAh( + getWifiControllerActivity(CONTROLLER_POWER_DRAIN, which) / (double)(1000*60*60))); + sb.append(" mAh"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime)); sb.append(")"); @@ -3376,48 +3416,48 @@ public abstract class BatteryStats implements Parcelable { final BatterySipper bs = sippers.get(i); switch (bs.drainType) { case IDLE: - pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case CELL: - pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case PHONE: - pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case WIFI: - pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case BLUETOOTH: - pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case SCREEN: - pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case FLASHLIGHT: - pw.print(prefix); pw.print(" Flashlight: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Flashlight: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case APP: pw.print(prefix); pw.print(" Uid "); UserHandle.formatUid(pw, bs.uidObj.getUid()); - pw.print(": "); printmAh(pw, bs.value); pw.println(); + pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case USER: pw.print(prefix); pw.print(" User "); pw.print(bs.userId); - pw.print(": "); printmAh(pw, bs.value); pw.println(); + pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case UNACCOUNTED: - pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; case OVERCOUNTED: - pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.value); + pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.totalPowerMah); pw.println(); break; } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index bea4ece..1746bed 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -109,6 +109,7 @@ interface IBatteryStats { void noteWifiBatchedScanStoppedFromSource(in WorkSource ws); void noteWifiMulticastEnabledFromSource(in WorkSource ws); void noteWifiMulticastDisabledFromSource(in WorkSource ws); + void noteWifiRadioPowerState(int powerState, long timestampNs); void noteNetworkInterfaceType(String iface, int type); void noteNetworkStatsEnabled(); void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion); diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java index 4cd959f..056b0aa 100644 --- a/core/java/com/android/internal/os/BatterySipper.java +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -23,17 +23,25 @@ import android.os.BatteryStats.Uid; public class BatterySipper implements Comparable<BatterySipper> { public int userId; public Uid uidObj; - public double value; - public double[] values; + public double totalPowerMah; public DrainType drainType; - // Measured in milliseconds. - public long usageTime; - public long cpuTime; - public long gpsTime; - public long wifiRunningTime; - public long cpuFgTime; - public long wakeLockTime; + /** + * Generic usage time in milliseconds. + */ + public long usageTimeMs; + + /** + * Generic power usage in mAh. + */ + public double usagePowerMah; + + // Subsystem usage times. + public long cpuTimeMs; + public long gpsTimeMs; + public long wifiRunningTimeMs; + public long cpuFgTimeMs; + public long wakeLockTimeMs; public long mobileRxPackets; public long mobileTxPackets; @@ -52,12 +60,13 @@ public class BatterySipper implements Comparable<BatterySipper> { public String packageWithHighestDrain; // Measured in mAh (milli-ampere per hour). - public double wifiPower; - public double cpuPower; - public double wakeLockPower; - public double mobileRadioPower; - public double gpsPower; - public double sensorPower; + // These are included when summed. + public double wifiPowerMah; + public double cpuPowerMah; + public double wakeLockPowerMah; + public double mobileRadioPowerMah; + public double gpsPowerMah; + public double sensorPowerMah; public enum DrainType { IDLE, @@ -73,17 +82,12 @@ public class BatterySipper implements Comparable<BatterySipper> { OVERCOUNTED } - public BatterySipper(DrainType drainType, Uid uid, double[] values) { - this.values = values; - if (values != null) value = values[0]; + public BatterySipper(DrainType drainType, Uid uid, double value) { + this.totalPowerMah = value; this.drainType = drainType; uidObj = uid; } - public double[] getValues() { - return values; - } - public void computeMobilemspp() { long packets = mobileRxPackets+mobileTxPackets; mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0; @@ -101,7 +105,7 @@ public class BatterySipper implements Comparable<BatterySipper> { } } // Return the flipped value because we want the items in descending order - return Double.compare(other.value, value); + return Double.compare(other.totalPowerMah, totalPowerMah); } /** @@ -123,11 +127,14 @@ public class BatterySipper implements Comparable<BatterySipper> { * Add stats from other to this BatterySipper. */ public void add(BatterySipper other) { - cpuTime += other.cpuTime; - gpsTime += other.gpsTime; - wifiRunningTime += other.wifiRunningTime; - cpuFgTime += other.cpuFgTime; - wakeLockTime += other.wakeLockTime; + totalPowerMah += other.totalPowerMah; + usageTimeMs += other.usageTimeMs; + usagePowerMah += other.usagePowerMah; + cpuTimeMs += other.cpuTimeMs; + gpsTimeMs += other.gpsTimeMs; + wifiRunningTimeMs += other.wifiRunningTimeMs; + cpuFgTimeMs += other.cpuFgTimeMs; + wakeLockTimeMs += other.wakeLockTimeMs; mobileRxPackets += other.mobileRxPackets; mobileTxPackets += other.mobileTxPackets; mobileActive += other.mobileActive; @@ -138,11 +145,20 @@ public class BatterySipper implements Comparable<BatterySipper> { mobileTxBytes += other.mobileTxBytes; wifiRxBytes += other.wifiRxBytes; wifiTxBytes += other.wifiTxBytes; - wifiPower += other.wifiPower; - gpsPower += other.gpsPower; - cpuPower += other.cpuPower; - sensorPower += other.sensorPower; - mobileRadioPower += other.mobileRadioPower; - wakeLockPower += other.wakeLockPower; + wifiPowerMah += other.wifiPowerMah; + gpsPowerMah += other.gpsPowerMah; + cpuPowerMah += other.cpuPowerMah; + sensorPowerMah += other.sensorPowerMah; + mobileRadioPowerMah += other.mobileRadioPowerMah; + wakeLockPowerMah += other.wakeLockPowerMah; + } + + /** + * Sum all the powers and store the value into `value`. + * @return the sum of all the power in this BatterySipper. + */ + public double sumPower() { + return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah + + mobileRadioPowerMah + wakeLockPowerMah; } } diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index d3611bf..024b7c5 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -16,17 +16,12 @@ 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.content.Intent; import android.content.IntentFilter; -import android.hardware.Sensor; import android.hardware.SensorManager; import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.BatteryStats.Uid; import android.os.Bundle; @@ -38,7 +33,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; -import android.telephony.SignalStrength; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; @@ -54,7 +48,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Map; /** * A helper class for retrieving the power usage information for all applications and services. @@ -63,8 +56,7 @@ import java.util.Map; * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). */ public final class BatteryStatsHelper { - - private static final boolean DEBUG = false; + static final boolean DEBUG = false; private static final String TAG = BatteryStatsHelper.class.getSimpleName(); @@ -81,14 +73,24 @@ public final class BatteryStatsHelper { private Intent mBatteryBroadcast; 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>(); + /** + * List of apps using power. + */ + private final List<BatterySipper> mUsageList = new ArrayList<>(); + + /** + * List of apps using wifi power. + */ + private final List<BatterySipper> mWifiSippers = new ArrayList<>(); + + /** + * List of apps using bluetooth power. + */ + private final List<BatterySipper> mBluetoothSippers = new ArrayList<>(); + + private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>(); - private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>(); + private final List<BatterySipper> mMobilemsppList = new ArrayList<>(); private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; @@ -102,29 +104,50 @@ public final class BatteryStatsHelper { long mChargeTimeRemaining; private long mStatsPeriod = 0; + + // The largest entry by power. private double mMaxPower = 1; + + // The largest real entry by power (not undercounted or overcounted). private double mMaxRealPower = 1; + + // Total computed power. private double mComputedPower; private double mTotalPower; - private double mWifiPower; - private double mBluetoothPower; private double mMinDrainedPower; private double mMaxDrainedPower; - // How much the apps together have kept the mobile radio active. - private long mAppMobileActive; + PowerCalculator mCpuPowerCalculator; + PowerCalculator mWakelockPowerCalculator; + MobileRadioPowerCalculator mMobileRadioPowerCalculator; + PowerCalculator mWifiPowerCalculator; + PowerCalculator mBluetoothPowerCalculator; + PowerCalculator mSensorPowerCalculator; + + public static boolean checkWifiOnly(Context context) { + ConnectivityManager cm = (ConnectivityManager)context.getSystemService( + Context.CONNECTIVITY_SERVICE); + return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } - // How much the apps together have left WIFI running. - private long mAppWifiRunning; + public static boolean checkHasWifiPowerReporting(Context context, PowerProfile profile) { + WifiManager manager = context.getSystemService(WifiManager.class); + if (manager.isEnhancedPowerReportingSupported()) { + if (profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 && + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 && + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0) { + return true; + } + } + return false; + } public BatteryStatsHelper(Context context) { this(context, true); } public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) { - mContext = context; - mCollectBatteryBroadcast = collectBatteryBroadcast; - mWifiOnly = checkWifiOnly(context); + this(context, collectBatteryBroadcast, checkWifiOnly(context)); } public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) { @@ -133,12 +156,6 @@ public final class BatteryStatsHelper { mWifiOnly = wifiOnly; } - public static boolean checkWifiOnly(Context context) { - ConnectivityManager cm = (ConnectivityManager)context.getSystemService( - Context.CONNECTIVITY_SERVICE); - return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); - } - public void storeStatsHistoryInFile(String fname) { synchronized (sFileXfer) { File path = makeFilePath(mContext, fname); @@ -260,7 +277,7 @@ public final class BatteryStatsHelper { * Refreshes the power usage list. */ public void refreshStats(int statsType, int asUser) { - SparseArray<UserHandle> users = new SparseArray<UserHandle>(1); + SparseArray<UserHandle> users = new SparseArray<>(1); users.put(asUser, new UserHandle(asUser)); refreshStats(statsType, users); } @@ -270,7 +287,7 @@ public final class BatteryStatsHelper { */ public void refreshStats(int statsType, List<UserHandle> asUsers) { final int n = asUsers.size(); - SparseArray<UserHandle> users = new SparseArray<UserHandle>(n); + SparseArray<UserHandle> users = new SparseArray<>(n); for (int i = 0; i < n; ++i) { UserHandle userHandle = asUsers.get(i); users.put(userHandle.getIdentifier(), userHandle); @@ -295,22 +312,52 @@ public final class BatteryStatsHelper { mMaxRealPower = 0; mComputedPower = 0; mTotalPower = 0; - mWifiPower = 0; - mBluetoothPower = 0; - mAppMobileActive = 0; - mAppWifiRunning = 0; mUsageList.clear(); mWifiSippers.clear(); mBluetoothSippers.clear(); mUserSippers.clear(); - mUserPower.clear(); mMobilemsppList.clear(); if (mStats == null) { return; } + if (mCpuPowerCalculator == null) { + mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile); + } + mCpuPowerCalculator.reset(); + + if (mWakelockPowerCalculator == null) { + mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile); + } + mWakelockPowerCalculator.reset(); + + if (mMobileRadioPowerCalculator == null) { + mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats); + } + mMobileRadioPowerCalculator.reset(mStats); + + if (mWifiPowerCalculator == null) { + if (checkHasWifiPowerReporting(mContext, mPowerProfile)) { + mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile); + } else { + mWifiPowerCalculator = new WifiPowerEstimator(mPowerProfile); + } + } + mWifiPowerCalculator.reset(); + + if (mBluetoothPowerCalculator == null) { + mBluetoothPowerCalculator = new BluetoothPowerCalculator(); + } + mBluetoothPowerCalculator.reset(); + + if (mSensorPowerCalculator == null) { + mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile, + (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE)); + } + mSensorPowerCalculator.reset(); + mStatsType = statsType; mRawUptime = rawUptimeUs; mRawRealtime = rawRealtimeUs; @@ -358,383 +405,113 @@ public final class BatteryStatsHelper { Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() { @Override public int compare(BatterySipper lhs, BatterySipper rhs) { - if (lhs.mobilemspp < rhs.mobilemspp) { - return 1; - } else if (lhs.mobilemspp > rhs.mobilemspp) { - return -1; - } - return 0; + return Double.compare(rhs.mobilemspp, lhs.mobilemspp); } }); processMiscUsage(); + Collections.sort(mUsageList); + + // At this point, we've sorted the list so we are guaranteed the max values are at the top. + // We have only added real powers so far. + if (!mUsageList.isEmpty()) { + mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah; + final int usageListCount = mUsageList.size(); + for (int i = 0; i < usageListCount; i++) { + mComputedPower += mUsageList.get(i).totalPowerMah; + } + } + if (DEBUG) { Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge=" + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower)); } + mTotalPower = mComputedPower; if (mStats.getLowDischargeAmountSinceCharge() > 1) { if (mMinDrainedPower > mComputedPower) { double amount = mMinDrainedPower - mComputedPower; mTotalPower = mMinDrainedPower; - addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount); + BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount); + + // Insert the BatterySipper in its sorted position. + int index = Collections.binarySearch(mUsageList, bs); + if (index < 0) { + index = -(index + 1); + } + mUsageList.add(index, bs); + mMaxPower = Math.max(mMaxPower, amount); } else if (mMaxDrainedPower < mComputedPower) { double amount = mComputedPower - mMaxDrainedPower; - addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount); + + // Insert the BatterySipper in its sorted position. + BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount); + int index = Collections.binarySearch(mUsageList, bs); + if (index < 0) { + index = -(index + 1); + } + mUsageList.add(index, bs); + mMaxPower = Math.max(mMaxPower, amount); } } - - Collections.sort(mUsageList); } private void processAppUsage(SparseArray<UserHandle> asUsers) { final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null); - final 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 mobilePowerPerMs = getMobilePowerPerMs(); - final double wifiPowerPerPacket = getWifiPowerPerPacket(); - long totalAppWakelockTimeUs = 0; - BatterySipper osApp = null; mStatsPeriod = mTypeBatteryRealtime; - final ArrayList<BatterySipper> appList = new ArrayList<>(); - - // Max values used to normalize later. - double maxWifiPower = 0; - double maxCpuPower = 0; - double maxWakeLockPower = 0; - double maxMobileRadioPower = 0; - double maxGpsPower = 0; - double maxSensorPower = 0; - final SparseArray<? extends Uid> uidStats = mStats.getUidStats(); final int NU = uidStats.size(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); - final BatterySipper app = new BatterySipper( - BatterySipper.DrainType.APP, u, new double[]{0}); - - final Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); - if (processStats.size() > 0) { - // Process CPU time. - - // Keep track of the package with highest drain. - double highestDrain = 0; - - for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent - : processStats.entrySet()) { - Uid.Proc ps = ent.getValue(); - app.cpuFgTime += ps.getForegroundTime(which); - final long totalCpuTime = ps.getUserTime(which) + ps.getSystemTime(which); - app.cpuTime += totalCpuTime; - - // Calculate the total CPU time spent at the various speed steps. - long totalTimeAtSpeeds = 0; - for (int step = 0; step < speedSteps; step++) { - cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); - totalTimeAtSpeeds += cpuSpeedStepTimes[step]; - } - totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1); - - // Then compute the ratio of time spent at each speed and figure out - // the total power consumption. - double cpuPower = 0; - for (int step = 0; step < speedSteps; step++) { - final double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; - final double cpuSpeedStepPower = - ratio * totalCpuTime * powerCpuNormal[step]; - if (DEBUG && ratio != 0) { - Log.d(TAG, "UID " + u.getUid() + ": CPU step #" - + step + " ratio=" + makemAh(ratio) + " power=" - + makemAh(cpuSpeedStepPower / (60 * 60 * 1000))); - } - cpuPower += cpuSpeedStepPower; - } - - if (DEBUG && cpuPower != 0) { - Log.d(TAG, String.format("process %s, cpu power=%s", - ent.getKey(), makemAh(cpuPower / (60 * 60 * 1000)))); - } - app.cpuPower += cpuPower; - - // Each App can have multiple packages and with multiple running processes. - // Keep track of the package who's process has the highest drain. - if (app.packageWithHighestDrain == null || - app.packageWithHighestDrain.startsWith("*")) { - highestDrain = cpuPower; - app.packageWithHighestDrain = ent.getKey(); - } else if (highestDrain < cpuPower && !ent.getKey().startsWith("*")) { - highestDrain = cpuPower; - app.packageWithHighestDrain = ent.getKey(); - } - } - } - - // Ensure that the CPU times make sense. - if (app.cpuFgTime > app.cpuTime) { - if (DEBUG && app.cpuFgTime > app.cpuTime + 10000) { - Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); - } - - // Statistics may not have been gathered yet. - app.cpuTime = app.cpuFgTime; - } - - // Convert the CPU power to mAh - app.cpuPower /= (60 * 60 * 1000); - maxCpuPower = Math.max(maxCpuPower, app.cpuPower); - - // Process wake lock usage - final Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = - u.getWakelockStats(); - long wakeLockTimeUs = 0; - for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry - : wakelockStats.entrySet()) { - final 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) { - wakeLockTimeUs += timer.getTotalTimeLocked(mRawRealtime, which); - } - } - app.wakeLockTime = wakeLockTimeUs / 1000; // convert to millis - totalAppWakelockTimeUs += wakeLockTimeUs; - - // Add cost of holding a wake lock. - app.wakeLockPower = (app.wakeLockTime * - mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60 * 60 * 1000); - if (DEBUG && app.wakeLockPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": wake " - + app.wakeLockTime + " power=" + makemAh(app.wakeLockPower)); - } - maxWakeLockPower = Math.max(maxWakeLockPower, app.wakeLockPower); - - // Add cost of mobile traffic. - final long mobileActive = u.getMobileRadioActiveTime(mStatsType); - app.mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); - app.mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); - app.mobileActive = mobileActive / 1000; - app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType); - app.mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType); - app.mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType); - - if (mobileActive > 0) { - // We are tracking when the radio is up, so can use the active time to - // determine power use. - mAppMobileActive += mobileActive; - app.mobileRadioPower = (mobilePowerPerMs * mobileActive) / 1000; - } else { - // We are not tracking when the radio is up, so must approximate power use - // based on the number of packets. - app.mobileRadioPower = (app.mobileRxPackets + app.mobileTxPackets) - * mobilePowerPerPacket; - } - if (DEBUG && app.mobileRadioPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": mobile packets " - + (app.mobileRxPackets + app.mobileTxPackets) - + " active time " + mobileActive - + " power=" + makemAh(app.mobileRadioPower)); - } - maxMobileRadioPower = Math.max(maxMobileRadioPower, app.mobileRadioPower); - - // Add cost of wifi traffic - app.wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType); - app.wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType); - app.wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType); - app.wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType); - - final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets) - * wifiPowerPerPacket; - if (DEBUG && wifiPacketPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": wifi packets " - + (app.wifiRxPackets + app.wifiTxPackets) - + " power=" + makemAh(wifiPacketPower)); - } - - // Add cost of keeping WIFI running. - app.wifiRunningTime = u.getWifiRunningTime(mRawRealtime, which) / 1000; - mAppWifiRunning += app.wifiRunningTime; - - final double wifiLockPower = (app.wifiRunningTime - * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60 * 60 * 1000); - if (DEBUG && wifiLockPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": wifi running " - + app.wifiRunningTime + " power=" + makemAh(wifiLockPower)); - } - - // Add cost of WIFI scans - final long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000; - final double wifiScanPower = (wifiScanTimeMs - * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) - / (60 * 60 * 1000); - if (DEBUG && wifiScanPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs - + " power=" + makemAh(wifiScanPower)); - } - - // Add cost of WIFI batch scans. - double wifiBatchScanPower = 0; - for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { - final long batchScanTimeMs = - u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000; - final double batchScanPower = ((batchScanTimeMs - * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin)) - ) / (60 * 60 * 1000); - if (DEBUG && batchScanPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin - + " time=" + batchScanTimeMs + " power=" + makemAh(batchScanPower)); - } - wifiBatchScanPower += batchScanPower; - } - - // Add up all the WiFi costs. - app.wifiPower = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower; - maxWifiPower = Math.max(maxWifiPower, app.wifiPower); - - // Process Sensor usage - final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); - final int NSE = sensorStats.size(); - for (int ise = 0; ise < NSE; ise++) { - final Uid.Sensor sensor = sensorStats.valueAt(ise); - final int sensorHandle = sensorStats.keyAt(ise); - final BatteryStats.Timer timer = sensor.getSensorTime(); - final long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000; - double sensorPower = 0; - switch (sensorHandle) { - case Uid.Sensor.GPS: - app.gpsTime = sensorTime; - app.gpsPower = (app.gpsTime - * mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON)) - / (60 * 60 * 1000); - sensorPower = app.gpsPower; - maxGpsPower = Math.max(maxGpsPower, app.gpsPower); - break; - default: - List<Sensor> sensorList = sensorManager.getSensorList( - android.hardware.Sensor.TYPE_ALL); - for (android.hardware.Sensor s : sensorList) { - if (s.getHandle() == sensorHandle) { - sensorPower = (sensorTime * s.getPower()) / (60 * 60 * 1000); - app.sensorPower += sensorPower; - break; - } - } - } - if (DEBUG && sensorPower != 0) { - Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle - + " time=" + sensorTime + " power=" + makemAh(sensorPower)); - } - } - maxSensorPower = Math.max(maxSensorPower, app.sensorPower); - - final double totalUnnormalizedPower = app.cpuPower + app.wifiPower + app.wakeLockPower - + app.mobileRadioPower + app.gpsPower + app.sensorPower; - if (DEBUG && totalUnnormalizedPower != 0) { - Log.d(TAG, String.format("UID %d: total power=%s", - u.getUid(), makemAh(totalUnnormalizedPower))); + final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0); + + mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); + mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); + mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); + mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); + mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); + mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); + + final double totalPower = app.sumPower(); + if (DEBUG && totalPower != 0) { + Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(), + makemAh(totalPower))); } // Add the app to the list if it is consuming power. - if (totalUnnormalizedPower != 0 || u.getUid() == 0) { - appList.add(app); - } - } - - // Fetch real power consumption from hardware. - double actualTotalWifiPower = 0.0; - if (mStats.getWifiControllerActivity(BatteryStats.CONTROLLER_ENERGY, mStatsType) != 0) { - final double kDefaultVoltage = 3.36; - final long energy = mStats.getWifiControllerActivity( - BatteryStats.CONTROLLER_ENERGY, mStatsType); - final double voltage = mPowerProfile.getAveragePowerOrDefault( - PowerProfile.OPERATING_VOLTAGE_WIFI, kDefaultVoltage); - actualTotalWifiPower = energy / (voltage * 1000*60*60); - } - - final int appCount = appList.size(); - for (int i = 0; i < appCount; i++) { - // Normalize power where possible. - final BatterySipper app = appList.get(i); - if (actualTotalWifiPower != 0) { - app.wifiPower = (app.wifiPower / maxWifiPower) * actualTotalWifiPower; - } - - // Assign the final power consumption here. - final double power = app.wifiPower + app.cpuPower + app.wakeLockPower - + app.mobileRadioPower + app.gpsPower + app.sensorPower; - app.values[0] = app.value = power; - - // - // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list. - // - - final int uid = app.getUid(); - final int userId = UserHandle.getUserId(uid); - if (uid == Process.WIFI_UID) { - mWifiSippers.add(app); - mWifiPower += power; - } else if (uid == Process.BLUETOOTH_UID) { - mBluetoothSippers.add(app); - mBluetoothPower += power; - } else if (!forAllUsers && asUsers.get(userId) == null - && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) { - // We are told to just report this user's apps as one large entry. - List<BatterySipper> list = mUserSippers.get(userId); - if (list == null) { - list = new ArrayList<>(); - mUserSippers.put(userId, list); - } - list.add(app); - - Double userPower = mUserPower.get(userId); - if (userPower == null) { - userPower = power; + if (totalPower != 0 || u.getUid() == 0) { + // + // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list. + // + final int uid = app.getUid(); + final int userId = UserHandle.getUserId(uid); + if (uid == Process.WIFI_UID) { + mWifiSippers.add(app); + } else if (uid == Process.BLUETOOTH_UID) { + mBluetoothSippers.add(app); + } else if (!forAllUsers && asUsers.get(userId) == null + && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) { + // We are told to just report this user's apps as one large entry. + List<BatterySipper> list = mUserSippers.get(userId); + if (list == null) { + list = new ArrayList<>(); + mUserSippers.put(userId, list); + } + list.add(app); } else { - userPower += power; + mUsageList.add(app); } - mUserPower.put(userId, userPower); - } else { - mUsageList.add(app); - if (power > mMaxPower) mMaxPower = power; - if (power > mMaxRealPower) mMaxRealPower = power; - mComputedPower += power; - } - - if (uid == 0) { - osApp = app; - } - } - // 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 -= (totalAppWakelockTimeUs / 1000) - + (mStats.getScreenOnTime(mRawRealtime, 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; - if (osApp.value > mMaxRealPower) mMaxRealPower = osApp.value; - mComputedPower += power; + if (uid == 0) { + // 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. + mWakelockPowerCalculator.calculateRemaining(app, mStats, mRawRealtime, + mRawUptime, mStatsType); + app.sumPower(); + } } } } @@ -744,7 +521,7 @@ public final class BatteryStatsHelper { double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) * phoneOnTimeMs / (60*60*1000); if (phoneOnPower != 0) { - BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); + addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); } } @@ -773,54 +550,19 @@ public final class BatteryStatsHelper { } 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, mRawRealtime, 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(mRawRealtime, 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; - long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType); - long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000; - if (remainingActiveTime > 0) { - power += getMobilePowerPerMs() * remainingActiveTime; - } - if (power != 0) { - BatterySipper bs = - addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power); - if (signalTimeMs != 0) { - bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; - } - bs.mobileActive = remainingActiveTime; - bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType); + BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); + mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtime, mRawUptime, + mStatsType); + radio.sumPower(); + if (radio.totalPowerMah > 0) { + mUsageList.add(radio); } } 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); + if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs); bs.add(wbs); } bs.computeMobilemspp(); @@ -847,41 +589,12 @@ public final class BatteryStatsHelper { * of WiFi to the WiFi subsystem. */ private void addWiFiUsage() { - final long idleTimeMs = mStats.getWifiControllerActivity( - BatteryStats.CONTROLLER_IDLE_TIME, mStatsType); - final long txTimeMs = mStats.getWifiControllerActivity( - BatteryStats.CONTROLLER_TX_TIME, mStatsType); - final long rxTimeMs = mStats.getWifiControllerActivity( - BatteryStats.CONTROLLER_RX_TIME, mStatsType); - final long energy = mStats.getWifiControllerActivity( - BatteryStats.CONTROLLER_ENERGY, mStatsType); - final long totalTimeRunning = idleTimeMs + txTimeMs + rxTimeMs; - - double powerDrain = 0; - if (energy == 0 && totalTimeRunning > 0) { - // Energy is not reported, which means we may have left over power drain not attributed - // to any app. Assign this power to the WiFi app. - // TODO(adamlesinski): This mimics the old behavior. However, mAppWifiRunningTime - // is the accumulation of the time each app kept the WiFi chip on. Multiple apps - // can do this at the same time, so these times do not add up to the total time - // the WiFi chip was on. Consider normalizing the time spent running and calculating - // power from that? Normalizing the times will assign a weight to each app which - // should better represent power usage. - powerDrain = ((totalTimeRunning - mAppWifiRunning) - * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000); - } - - if (DEBUG && powerDrain != 0) { - Log.d(TAG, "Wifi active: time=" + (txTimeMs + rxTimeMs) - + " power=" + makemAh(powerDrain)); - } - - // TODO(adamlesinski): mWifiPower is already added as a BatterySipper... - // Are we double counting here? - final double power = mWifiPower + powerDrain; - if (power > 0) { - BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, totalTimeRunning, power); + BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0); + mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType); + bs.sumPower(); + if (bs.totalPowerMah > 0 || !mWifiSippers.isEmpty()) { aggregateSippers(bs, mWifiSippers, "WIFI"); + mUsageList.add(bs); } } @@ -890,30 +603,10 @@ public final class BatteryStatsHelper { * Bluetooth Category. */ private void addBluetoothUsage() { - final double kDefaultVoltage = 3.36; - final long idleTimeMs = mStats.getBluetoothControllerActivity( - BatteryStats.CONTROLLER_IDLE_TIME, mStatsType); - final long txTimeMs = mStats.getBluetoothControllerActivity( - BatteryStats.CONTROLLER_TX_TIME, mStatsType); - final long rxTimeMs = mStats.getBluetoothControllerActivity( - BatteryStats.CONTROLLER_RX_TIME, mStatsType); - final long energy = mStats.getBluetoothControllerActivity( - BatteryStats.CONTROLLER_ENERGY, mStatsType); - final double voltage = mPowerProfile.getAveragePowerOrDefault( - PowerProfile.OPERATING_VOLTAGE_BLUETOOTH, kDefaultVoltage); - - // energy is measured in mA * V * ms, and we are interested in mAh - final double powerDrain = energy / (voltage * 60*60*1000); - - if (DEBUG && powerDrain != 0) { - Log.d(TAG, "Bluetooth active: time=" + (txTimeMs + rxTimeMs) - + " power=" + makemAh(powerDrain)); - } - - final long totalTime = idleTimeMs + txTimeMs + rxTimeMs; - final double power = mBluetoothPower + powerDrain; - if (power > 0) { - BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, totalTime, power); + BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0); + mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, + mStatsType); + if (bs.sumPower() > 0) { aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); } } @@ -928,55 +621,16 @@ public final class BatteryStatsHelper { } private void addUserUsage() { - for (int i=0; i<mUserSippers.size(); i++) { + 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); + BatterySipper bs = new BatterySipper(DrainType.USER, null, 0); bs.userId = userId; - aggregateSippers(bs, sippers, "User"); + aggregateSippers(bs, mUserSippers.valueAt(i), "User"); + bs.sumPower(); + mUsageList.add(bs); } } - /** - * 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.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000; - final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0) - ? (mobileData / (double)radioDataUptimeMs) - : (((double)MOBILE_BPS) / 8 / 2048); - - return (MOBILE_POWER / mobilePps) / (60*60); - } - - /** - * Return estimated power (in mAs) of keeping the radio up - */ - private double getMobilePowerPerMs() { - return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000); - } - - /** - * 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(); @@ -992,15 +646,10 @@ public final class BatteryStatsHelper { } private BatterySipper addEntry(DrainType drainType, long time, double power) { - mComputedPower += power; - if (power > mMaxRealPower) mMaxRealPower = power; - return addEntryNoTotal(drainType, time, power); - } - - private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) { - if (power > mMaxPower) mMaxPower = power; - BatterySipper bs = new BatterySipper(drainType, null, new double[] {power}); - bs.usageTime = time; + BatterySipper bs = new BatterySipper(drainType, null, 0); + bs.usagePowerMah = power; + bs.usageTimeMs = time; + bs.sumPower(); mUsageList.add(bs); return bs; } @@ -1015,7 +664,7 @@ public final class BatteryStatsHelper { public long getStatsPeriod() { return mStatsPeriod; } - public int getStatsType() { return mStatsType; }; + public int getStatsType() { return mStatsType; } public double getMaxPower() { return mMaxPower; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 05ed3ab..793d0d3 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -55,6 +55,7 @@ import android.util.Printer; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; import android.view.Display; @@ -95,6 +96,7 @@ import java.util.concurrent.locks.ReentrantLock; public final class BatteryStatsImpl extends BatteryStats { private static final String TAG = "BatteryStatsImpl"; private static final boolean DEBUG = false; + private static final boolean DEBUG_ENERGY = false; private static final boolean DEBUG_HISTORY = false; private static final boolean USE_OLD_HISTORY = false; // for debugging. @@ -182,22 +184,20 @@ public final class BatteryStatsImpl extends BatteryStats { // elapsed time by the number of active timers to arrive at that timer's share of the time. // In order to do this, we must refresh each timer whenever the number of active timers // changes. - final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<StopwatchTimer>(); - final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<StopwatchTimer>(); - final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<StopwatchTimer>(); - final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers - = new SparseArray<ArrayList<StopwatchTimer>>(); - final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<StopwatchTimer>(); - final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>(); - final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>(); - final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>(); - final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = - new SparseArray<ArrayList<StopwatchTimer>>(); - final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<StopwatchTimer>(); - final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<StopwatchTimer>(); + final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>(); + final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>(); + final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>(); + final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>(); + final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>(); + final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>(); // Last partial timers we use for distributing CPU usage. - final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>(); + final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>(); // These are the objects that will want to do something when the device // is unplugged from power. @@ -227,7 +227,7 @@ public final class BatteryStatsImpl extends BatteryStats { final HistoryItem mHistoryLastLastWritten = new HistoryItem(); final HistoryItem mHistoryReadTmp = new HistoryItem(); final HistoryItem mHistoryAddTmp = new HistoryItem(); - final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap(); + final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>(); String[] mReadHistoryStrings; int[] mReadHistoryUids; int mReadHistoryChars; @@ -450,6 +450,8 @@ public final class BatteryStatsImpl extends BatteryStats { private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry(); + private PowerProfile mPowerProfile; + /* * Holds a SamplingTimer associated with each kernel wakelock name being tracked. */ @@ -928,6 +930,12 @@ public final class BatteryStatsImpl extends BatteryStats { long mUnpluggedTime; /** + * The total time this timer has been running until the latest mark has been set. + * Subtract this from mTotalTime to get the time spent running since the mark was set. + */ + long mTimeBeforeMark; + + /** * Constructs from a parcel. * @param type * @param timeBase @@ -945,6 +953,7 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedTime = in.readLong(); mLastTime = 0; mUnpluggedTime = in.readLong(); + mTimeBeforeMark = in.readLong(); timeBase.add(this); if (DEBUG) Log.i(TAG, "**** READ TIMER #" + mType + ": mTotalTime=" + mTotalTime); } @@ -964,7 +973,7 @@ public final class BatteryStatsImpl extends BatteryStats { * so can be completely dropped. */ boolean reset(boolean detachIfReset) { - mTotalTime = mLoadedTime = mLastTime = 0; + mTotalTime = mLoadedTime = mLastTime = mTimeBeforeMark = 0; mCount = mLoadedCount = mLastCount = 0; if (detachIfReset) { detach(); @@ -985,8 +994,10 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs))); out.writeLong(mLoadedTime); out.writeLong(mUnpluggedTime); + out.writeLong(mTimeBeforeMark); } + @Override public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) { if (DEBUG && mType < 0) { Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime @@ -1002,6 +1013,7 @@ public final class BatteryStatsImpl extends BatteryStats { } } + @Override public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { if (DEBUG && mType < 0) { Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime @@ -1055,6 +1067,13 @@ public final class BatteryStatsImpl extends BatteryStats { return val; } + @Override + public long getTimeSinceMarkLocked(long elapsedRealtimeUs) { + long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)); + return val - mTimeBeforeMark; + } + + @Override public void logState(Printer pw, String prefix) { pw.println(prefix + "mCount=" + mCount + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount @@ -1080,6 +1099,9 @@ public final class BatteryStatsImpl extends BatteryStats { mCount = mLoadedCount = in.readInt(); mLastCount = 0; mUnpluggedCount = mCount; + + // When reading the summary, we set the mark to be the latest information. + mTimeBeforeMark = mTotalTime; } } @@ -1475,21 +1497,6 @@ public final class BatteryStatsImpl extends BatteryStats { return mNesting > 0; } - long checkpointRunningLocked(long elapsedRealtimeMs) { - if (mNesting > 0) { - // We are running... - final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); - if (mTimerPool != null) { - return refreshTimersLocked(batteryRealtime, mTimerPool, this); - } - final long heldTime = batteryRealtime - mUpdateTime; - mUpdateTime = batteryRealtime; - mTotalTime += heldTime; - return heldTime; - } - return 0; - } - void stopRunningLocked(long elapsedRealtimeMs) { // Ignore attempt to stop a timer that isn't running if (mNesting == 0) { @@ -1567,6 +1574,7 @@ public final class BatteryStatsImpl extends BatteryStats { return mCount; } + @Override boolean reset(boolean detachIfReset) { boolean canDetach = mNesting <= 0; super.reset(canDetach && detachIfReset); @@ -1577,6 +1585,7 @@ public final class BatteryStatsImpl extends BatteryStats { return canDetach; } + @Override void detach() { super.detach(); if (mTimerPool != null) { @@ -1584,10 +1593,31 @@ public final class BatteryStatsImpl extends BatteryStats { } } + @Override void readSummaryFromParcelLocked(Parcel in) { super.readSummaryFromParcelLocked(in); mNesting = 0; } + + /** + * Set the mark so that we can query later for the total time the timer has + * accumulated since this point. The timer can be running or not. + * + * @param elapsedRealtimeMs the current elapsed realtime in milliseconds. + */ + public void setMark(long elapsedRealtimeMs) { + final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); + if (mNesting > 0) { + // We are running. + if (mTimerPool != null) { + refreshTimersLocked(batteryRealtime, mTimerPool, this); + } else { + mTotalTime += batteryRealtime - mUpdateTime; + mUpdateTime = batteryRealtime; + } + } + mTimeBeforeMark = mTotalTime; + } } public abstract class OverflowArrayMap<T> { @@ -3890,7 +3920,6 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); - scheduleSyncExternalStatsLocked(); } mWifiFullLockNesting++; getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime); @@ -3906,7 +3935,6 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); - scheduleSyncExternalStatsLocked(); } getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime); } @@ -3964,7 +3992,6 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); - scheduleSyncExternalStatsLocked(); } mWifiMulticastNesting++; getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime); @@ -3980,7 +4007,6 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); - scheduleSyncExternalStatsLocked(); } getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime); } @@ -4088,7 +4114,8 @@ public final class BatteryStatsImpl extends BatteryStats { // During device boot, qtaguid isn't enabled until after the inital // loading of battery stats. Now that they're enabled, take our initial // snapshot for future delta calculation. - updateMobileRadioStateLocked(SystemClock.elapsedRealtime()); + final long elapsedRealtimeMs = SystemClock.elapsedRealtime(); + updateMobileRadioStateLocked(elapsedRealtimeMs); updateWifiStateLocked(null); } @@ -4369,6 +4396,18 @@ public final class BatteryStatsImpl extends BatteryStats { LongSamplingCounter mMobileRadioActiveCount; /** + * The amount of time this uid has kept the WiFi controller in idle, tx, and rx mode. + */ + LongSamplingCounter[] mWifiControllerTime = + new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES]; + + /** + * The amount of time this uid has kept the Bluetooth controller in idle, tx, and rx mode. + */ + LongSamplingCounter[] mBluetoothControllerTime = + new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES]; + + /** * The CPU times we had at the last history details update. */ long mLastStepUserTime; @@ -4404,22 +4443,22 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics we have collected for this uid's sensor activations. */ - final SparseArray<Sensor> mSensorStats = new SparseArray<Sensor>(); + final SparseArray<Sensor> mSensorStats = new SparseArray<>(); /** * The statistics we have collected for this uid's processes. */ - final ArrayMap<String, Proc> mProcessStats = new ArrayMap<String, Proc>(); + final ArrayMap<String, Proc> mProcessStats = new ArrayMap<>(); /** * The statistics we have collected for this uid's processes. */ - final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<String, Pkg>(); + final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<>(); /** * The transient wake stats we have collected for this uid's pids. */ - final SparseArray<Pid> mPids = new SparseArray<Pid>(); + final SparseArray<Pid> mPids = new SparseArray<>(); public Uid(int uid) { mUid = uid; @@ -4580,6 +4619,13 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteWifiControllerActivityLocked(int type, long timeMs) { + if (mWifiControllerTime[type] == null) { + mWifiControllerTime[type] = new LongSamplingCounter(mOnBatteryTimeBase); + } + mWifiControllerTime[type].addCountLocked(timeMs); + } + public StopwatchTimer createAudioTurnedOnTimerLocked() { if (mAudioTurnedOnTimer == null) { mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, @@ -4895,6 +4941,15 @@ public final class BatteryStatsImpl extends BatteryStats { ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0; } + @Override + public long getWifiControllerActivity(int type, int which) { + if (type >= 0 && type < NUM_CONTROLLER_ACTIVITY_TYPES && + mWifiControllerTime[type] != null) { + return mWifiControllerTime[type].getCountLocked(which); + } + return 0; + } + void initNetworkActivityLocked() { mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; @@ -4978,6 +5033,16 @@ public final class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveCount.reset(false); } + for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { + if (mWifiControllerTime[i] != null) { + mWifiControllerTime[i].reset(false); + } + + if (mBluetoothControllerTime[i] != null) { + mBluetoothControllerTime[i].reset(false); + } + } + final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap(); for (int iw=wakeStats.size()-1; iw>=0; iw--) { Wakelock wl = wakeStats.valueAt(iw); @@ -5100,6 +5165,16 @@ public final class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[i].detach(); } } + + for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { + if (mWifiControllerTime[i] != null) { + mWifiControllerTime[i].detach(); + } + + if (mBluetoothControllerTime[i] != null) { + mBluetoothControllerTime[i].detach(); + } + } mPids.clear(); } @@ -5189,6 +5264,7 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } + if (mAudioTurnedOnTimer != null) { out.writeInt(1); mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs); @@ -5240,6 +5316,24 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } + + for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { + if (mWifiControllerTime[i] != null) { + out.writeInt(1); + mWifiControllerTime[i].writeToParcel(out); + } else { + out.writeInt(0); + } + } + + for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { + if (mBluetoothControllerTime[i] != null) { + out.writeInt(1); + mBluetoothControllerTime[i].writeToParcel(out); + } else { + out.writeInt(0); + } + } } void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) { @@ -5389,6 +5483,22 @@ public final class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters = null; mNetworkPacketActivityCounters = null; } + + for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { + if (in.readInt() != 0) { + mWifiControllerTime[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); + } else { + mWifiControllerTime[i] = null; + } + } + + for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { + if (in.readInt() != 0) { + mBluetoothControllerTime[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); + } else { + mBluetoothControllerTime[i] = null; + } + } } /** @@ -6644,6 +6754,12 @@ public final class BatteryStatsImpl extends BatteryStats { readFromParcel(p); } + public void setPowerProfile(PowerProfile profile) { + synchronized (this) { + mPowerProfile = profile; + } + } + public void setCallback(BatteryCallback cb) { mCallback = cb; } @@ -7367,9 +7483,12 @@ public final class BatteryStatsImpl extends BatteryStats { * @param info The energy information from the WiFi controller. */ public void updateWifiStateLocked(@Nullable final WifiActivityEnergyInfo info) { - final NetworkStats delta; + final long elapsedRealtimeMs = SystemClock.elapsedRealtime(); + NetworkStats delta = null; try { - delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats); + if (!ArrayUtils.isEmpty(mWifiIfaces)) { + delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats); + } } catch (IOException e) { Slog.wtf(TAG, "Failed to get wifi network stats", e); return; @@ -7379,14 +7498,19 @@ public final class BatteryStatsImpl extends BatteryStats { return; } + SparseLongArray rxPackets = new SparseLongArray(); + SparseLongArray txPackets = new SparseLongArray(); + long totalTxPackets = 0; + long totalRxPackets = 0; if (delta != null) { final int size = delta.size(); for (int i = 0; i < size; i++) { final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); - if (DEBUG) { + if (DEBUG_ENERGY) { Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes - + " tx=" + entry.txBytes); + + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets + + " txPackets=" + entry.txPackets); } if (entry.rxBytes == 0 || entry.txBytes == 0) { @@ -7398,6 +7522,13 @@ public final class BatteryStatsImpl extends BatteryStats { entry.rxPackets); u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes, entry.txPackets); + rxPackets.put(u.getUid(), entry.rxPackets); + txPackets.put(u.getUid(), entry.txPackets); + + // Sum the total number of packets so that the Rx Power and Tx Power can + // be evenly distributed amongst the apps. + totalRxPackets += entry.rxPackets; + totalTxPackets += entry.txPackets; mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( entry.rxBytes); @@ -7411,6 +7542,119 @@ public final class BatteryStatsImpl extends BatteryStats { } if (info != null) { + // Measured in mAms + final long txTimeMs = info.getControllerTxTimeMillis(); + final long rxTimeMs = info.getControllerRxTimeMillis(); + final long idleTimeMs = info.getControllerIdleTimeMillis(); + final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs; + + long leftOverRxTimeMs = rxTimeMs; + + if (DEBUG_ENERGY) { + Slog.d(TAG, "------ BEGIN WiFi power blaming ------"); + Slog.d(TAG, " Tx Time: " + txTimeMs + " ms"); + Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms"); + Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms"); + Slog.d(TAG, " Total Time: " + totalTimeMs + " ms"); + } + + long totalWifiLockTimeMs = 0; + long totalScanTimeMs = 0; + + // On the first pass, collect some totals so that we can normalize power + // calculations if we need to. + final int uidStatsSize = mUidStats.size(); + for (int i = 0; i < uidStatsSize; i++) { + final Uid uid = mUidStats.valueAt(i); + + // Sum the total scan power for all apps. + totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + + // Sum the total time holding wifi lock for all apps. + totalWifiLockTimeMs += uid.mFullWifiLockTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + } + + if (DEBUG_ENERGY && totalScanTimeMs > rxTimeMs) { + Slog.d(TAG, " !Estimated scan time > Actual rx time (" + totalScanTimeMs + " ms > " + + rxTimeMs + " ms). Normalizing scan time."); + } + + // Actually assign and distribute power usage to apps. + for (int i = 0; i < uidStatsSize; i++) { + final Uid uid = mUidStats.valueAt(i); + + long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + if (scanTimeSinceMarkMs > 0) { + // Set the new mark so that next time we get new data since this point. + uid.mWifiScanTimer.setMark(elapsedRealtimeMs); + + if (totalScanTimeMs > rxTimeMs) { + // Our total scan time is more than the reported Rx time. + // This is possible because the cost of a scan is approximate. + // Let's normalize the result so that we evenly blame each app + // scanning. + // + // This means that we may have apps that received packets not be blamed + // for this, but this is fine as scans are relatively more expensive. + scanTimeSinceMarkMs = (rxTimeMs * scanTimeSinceMarkMs) / totalScanTimeMs; + } + + if (DEBUG_ENERGY) { + Slog.d(TAG, " ScanTime for UID " + uid.getUid() + ": " + + scanTimeSinceMarkMs + " ms)"); + } + uid.noteWifiControllerActivityLocked(CONTROLLER_RX_TIME, scanTimeSinceMarkMs); + leftOverRxTimeMs -= scanTimeSinceMarkMs; + } + + // Distribute evenly the power consumed while Idle to each app holding a WiFi + // lock. + final long wifiLockTimeSinceMarkMs = uid.mFullWifiLockTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + if (wifiLockTimeSinceMarkMs > 0) { + // Set the new mark so that next time we get new data since this point. + uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs); + + final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs) + / totalWifiLockTimeMs; + if (DEBUG_ENERGY) { + Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": " + + myIdleTimeMs + " ms"); + } + uid.noteWifiControllerActivityLocked(CONTROLLER_IDLE_TIME, myIdleTimeMs); + } + } + + if (DEBUG_ENERGY) { + Slog.d(TAG, " New RxPower: " + leftOverRxTimeMs + " ms"); + } + + // Distribute the Tx power appropriately between all apps that transmitted packets. + for (int i = 0; i < txPackets.size(); i++) { + final Uid uid = getUidStatsLocked(txPackets.keyAt(i)); + final long myTxTimeMs = (txPackets.valueAt(i) * txTimeMs) / totalTxPackets; + if (DEBUG_ENERGY) { + Slog.d(TAG, " TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms"); + } + uid.noteWifiControllerActivityLocked(CONTROLLER_TX_TIME, myTxTimeMs); + } + + // Distribute the remaining Rx power appropriately between all apps that received + // packets. + for (int i = 0; i < rxPackets.size(); i++) { + final Uid uid = getUidStatsLocked(rxPackets.keyAt(i)); + final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs) / totalRxPackets; + if (DEBUG_ENERGY) { + Slog.d(TAG, " RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms"); + } + uid.noteWifiControllerActivityLocked(CONTROLLER_RX_TIME, myRxTimeMs); + } + + // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper. + // Update WiFi controller stats. mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked( info.getControllerRxTimeMillis()); @@ -7418,19 +7662,29 @@ public final class BatteryStatsImpl extends BatteryStats { info.getControllerTxTimeMillis()); mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( info.getControllerIdleTimeMillis()); - mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked( - info.getControllerEnergyUsed()); + + final double powerDrainMaMs; + if (mPowerProfile.getAveragePower( + PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) == 0) { + powerDrainMaMs = 0.0; + } else { + powerDrainMaMs = info.getControllerEnergyUsed() + / mPowerProfile.getAveragePower( + PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE); + } + mWifiActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked((long) powerDrainMaMs); } } /** * Distribute Cell radio energy info and network traffic to apps. */ - public void updateMobileRadioStateLocked(long elapsedRealtimeMs) { - final NetworkStats delta; - + public void updateMobileRadioStateLocked(final long elapsedRealtimeMs) { + NetworkStats delta = null; try { - delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats); + if (!ArrayUtils.isEmpty(mMobileIfaces)) { + delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats); + } } catch (IOException e) { Slog.wtf(TAG, "Failed to get mobile network stats", e); return; @@ -7440,14 +7694,24 @@ public final class BatteryStatsImpl extends BatteryStats { return; } - long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(elapsedRealtimeMs); + long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000); + mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs); long totalPackets = delta.getTotalPackets(); final int size = delta.size(); for (int i = 0; i < size; i++) { final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); - if (entry.rxBytes == 0 || entry.txBytes == 0) continue; + if (entry.rxBytes == 0 || entry.txBytes == 0) { + continue; + } + + if (DEBUG_ENERGY) { + Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes + + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets + + " txPackets=" + entry.txPackets); + } final Uid u = getUidStatsLocked(mapUid(entry.uid)); u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes, @@ -7488,14 +7752,14 @@ public final class BatteryStatsImpl extends BatteryStats { * @param info The energy information from the bluetooth controller. */ public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) { - if (info != null && mOnBatteryInternal) { + if (info != null && mOnBatteryInternal && false) { mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked( info.getControllerRxTimeMillis()); mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked( info.getControllerTxTimeMillis()); mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( info.getControllerIdleTimeMillis()); - mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked( + mBluetoothActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked( info.getControllerEnergyUsed()); } } diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java new file mode 100644 index 0000000..3557209 --- /dev/null +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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; +import android.util.Log; + +public class BluetoothPowerCalculator extends PowerCalculator { + private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + private static final String TAG = "BluetoothPowerCalculator"; + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + // No per-app distribution yet. + } + + @Override + public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + final long idleTimeMs = stats.getBluetoothControllerActivity( + BatteryStats.CONTROLLER_IDLE_TIME, statsType); + final long txTimeMs = stats.getBluetoothControllerActivity( + BatteryStats.CONTROLLER_TX_TIME, statsType); + final long rxTimeMs = stats.getBluetoothControllerActivity( + BatteryStats.CONTROLLER_RX_TIME, statsType); + final long powerMaMs = stats.getBluetoothControllerActivity( + BatteryStats.CONTROLLER_POWER_DRAIN, statsType); + final double powerMah = powerMaMs / (double)(1000*60*60); + final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs; + + if (DEBUG && powerMah != 0) { + Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs) + + " power=" + BatteryStatsHelper.makemAh(powerMah)); + } + + app.usagePowerMah = powerMah; + app.usageTimeMs = totalTimeMs; + } +} diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java new file mode 100644 index 0000000..6c3f958 --- /dev/null +++ b/core/java/com/android/internal/os/CpuPowerCalculator.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 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; +import android.util.ArrayMap; +import android.util.Log; + +public class CpuPowerCalculator extends PowerCalculator { + private static final String TAG = "CpuPowerCalculator"; + private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + + private final double[] mPowerCpuNormal; + + /** + * Reusable array for calculations. + */ + private final long[] mSpeedStepTimes; + + public CpuPowerCalculator(PowerProfile profile) { + final int speedSteps = profile.getNumSpeedSteps(); + mPowerCpuNormal = new double[speedSteps]; + mSpeedStepTimes = new long[speedSteps]; + for (int p = 0; p < speedSteps; p++) { + mPowerCpuNormal[p] = profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); + } + } + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + final int speedSteps = mSpeedStepTimes.length; + + // Keep track of the package with highest drain. + double highestDrain = 0; + + final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); + final int processStatsCount = processStats.size(); + for (int i = 0; i < processStatsCount; i++) { + final BatteryStats.Uid.Proc ps = processStats.valueAt(i); + final String processName = processStats.keyAt(i); + + app.cpuFgTimeMs += ps.getForegroundTime(statsType); + final long totalCpuTime = ps.getUserTime(statsType) + ps.getSystemTime(statsType); + app.cpuTimeMs += totalCpuTime; + + // Calculate the total CPU time spent at the various speed steps. + long totalTimeAtSpeeds = 0; + for (int step = 0; step < speedSteps; step++) { + mSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, statsType); + totalTimeAtSpeeds += mSpeedStepTimes[step]; + } + totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1); + + // Then compute the ratio of time spent at each speed and figure out + // the total power consumption. + double cpuPower = 0; + for (int step = 0; step < speedSteps; step++) { + final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds; + final double cpuSpeedStepPower = ratio * totalCpuTime * mPowerCpuNormal[step]; + if (DEBUG && ratio != 0) { + Log.d(TAG, "UID " + u.getUid() + ": CPU step #" + + step + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power=" + + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000))); + } + cpuPower += cpuSpeedStepPower; + } + + if (DEBUG && cpuPower != 0) { + Log.d(TAG, String.format("process %s, cpu power=%s", + processName, BatteryStatsHelper.makemAh(cpuPower / (60 * 60 * 1000)))); + } + app.cpuPowerMah += cpuPower; + + // Each App can have multiple packages and with multiple running processes. + // Keep track of the package who's process has the highest drain. + if (app.packageWithHighestDrain == null || + app.packageWithHighestDrain.startsWith("*")) { + highestDrain = cpuPower; + app.packageWithHighestDrain = processName; + } else if (highestDrain < cpuPower && !processName.startsWith("*")) { + highestDrain = cpuPower; + app.packageWithHighestDrain = processName; + } + } + + // Ensure that the CPU times make sense. + if (app.cpuFgTimeMs > app.cpuTimeMs) { + if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) { + Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); + } + + // Statistics may not have been gathered yet. + app.cpuTimeMs = app.cpuFgTimeMs; + } + + // Convert the CPU power to mAh + app.cpuPowerMah /= (60 * 60 * 1000); + } +} diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java new file mode 100644 index 0000000..9711c3b --- /dev/null +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2015 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; +import android.telephony.SignalStrength; +import android.util.Log; + +public class MobileRadioPowerCalculator extends PowerCalculator { + private static final String TAG = "MobileRadioPowerController"; + private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + private final double mPowerRadioOn; + private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS]; + private final double mPowerScan; + private BatteryStats mStats; + private long mTotalAppMobileActiveMs = 0; + + /** + * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio. + */ + private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) { + final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system + final double MOBILE_POWER = mPowerRadioOn / 3600; + + final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, + statsType); + final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, + statsType); + final long mobileData = mobileRx + mobileTx; + + final long radioDataUptimeMs = + mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; + final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0) + ? (mobileData / (double)radioDataUptimeMs) + : (((double)MOBILE_BPS) / 8 / 2048); + return (MOBILE_POWER / mobilePps) / (60*60); + } + + public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) { + mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE); + for (int i = 0; i < mPowerBins.length; i++) { + mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE, i); + } + mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING); + mStats = stats; + } + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + // Add cost of mobile traffic. + app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, + statsType); + app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, + statsType); + app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000; + app.mobileActiveCount = u.getMobileRadioActiveCount(statsType); + app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA, + statsType); + app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA, + statsType); + + if (app.mobileActive > 0) { + // We are tracking when the radio is up, so can use the active time to + // determine power use. + mTotalAppMobileActiveMs += app.mobileActive; + app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60); + } else { + // We are not tracking when the radio is up, so must approximate power use + // based on the number of packets. + app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets) + * getMobilePowerPerPacket(rawRealtimeUs, statsType); + } + if (DEBUG && app.mobileRadioPowerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": mobile packets " + + (app.mobileRxPackets + app.mobileTxPackets) + + " active time " + app.mobileActive + + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah)); + } + } + + @Override + public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + double power = 0; + long signalTimeMs = 0; + long noCoverageTimeMs = 0; + for (int i = 0; i < mPowerBins.length; i++) { + long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType) + / 1000; + final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + + BatteryStatsHelper.makemAh(p)); + } + power += p; + signalTimeMs += strengthTimeMs; + if (i == 0) { + noCoverageTimeMs = strengthTimeMs; + } + } + + final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType) + / 1000; + final double p = (scanningTimeMs * mPowerScan) / (60*60*1000); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + + " power=" + BatteryStatsHelper.makemAh(p)); + } + power += p; + long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; + long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs; + if (remainingActiveTimeMs > 0) { + power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60); + } + + if (power != 0) { + app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; + app.mobileActive = remainingActiveTimeMs; + app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType); + app.mobileRadioPowerMah = power; + } + } + + @Override + public void reset() { + mTotalAppMobileActiveMs = 0; + } + + public void reset(BatteryStats stats) { + reset(); + mStats = stats; + } +} diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java new file mode 100644 index 0000000..cd69d68 --- /dev/null +++ b/core/java/com/android/internal/os/PowerCalculator.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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; + +/** + * Calculates power use of a device subsystem for an app. + */ +public abstract class PowerCalculator { + /** + * Calculate the amount of power an app used for this subsystem. + * @param app The BatterySipper that represents the power use of an app. + * @param u The recorded stats for the app. + * @param rawRealtimeUs The raw system realtime in microseconds. + * @param rawUptimeUs The raw system uptime in microseconds. + * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT}, + * {@link BatteryStats#STATS_SINCE_CHARGED}, or + * {@link BatteryStats#STATS_SINCE_UNPLUGGED}. + */ + public abstract void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType); + + /** + * Calculate the remaining power that can not be attributed to an app. + * @param app The BatterySipper that will represent this remaining power. + * @param stats The BatteryStats object from which to retrieve data. + * @param rawRealtimeUs The raw system realtime in microseconds. + * @param rawUptimeUs The raw system uptime in microseconds. + * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT}, + * {@link BatteryStats#STATS_SINCE_CHARGED}, or + * {@link BatteryStats#STATS_SINCE_UNPLUGGED}. + */ + public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + } + + /** + * Reset any state maintained in this calculator. + */ + public void reset() { + } +} diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 944eb5a..7e6706c 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -18,6 +18,7 @@ package com.android.internal.os; import android.content.Context; +import android.content.res.Resources; import android.content.res.XmlResourceParser; import com.android.internal.util.XmlUtils; @@ -75,10 +76,21 @@ public class PowerProfile { */ public static final String POWER_WIFI_ACTIVE = "wifi.active"; - /** - * Operating voltage of the WiFi controller. - */ - public static final String OPERATING_VOLTAGE_WIFI = "wifi.voltage"; + // + // Updated power constants. These are not estimated, they are real world + // currents and voltages for the underlying bluetooth and wifi controllers. + // + + public static final String POWER_WIFI_CONTROLLER_IDLE = "wifi.controller.idle"; + public static final String POWER_WIFI_CONTROLLER_RX = "wifi.controller.rx"; + public static final String POWER_WIFI_CONTROLLER_TX = "wifi.controller.tx"; + public static final String POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE = "wifi.controller.voltage"; + + public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle"; + public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx"; + public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx"; + public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE = + "bluetooth.controller.voltage"; /** * Power consumption when GPS is on. @@ -100,10 +112,6 @@ public class PowerProfile { */ public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at"; - /** - * Operating voltage of the Bluetooth controller. - */ - public static final String OPERATING_VOLTAGE_BLUETOOTH = "bluetooth.voltage"; /** * Power consumption when screen is on, not including the backlight power. @@ -162,7 +170,7 @@ public class PowerProfile { */ public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; - static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>(); + static final HashMap<String, Object> sPowerMap = new HashMap<>(); private static final String TAG_DEVICE = "device"; private static final String TAG_ITEM = "item"; @@ -180,7 +188,8 @@ public class PowerProfile { private void readPowerValuesFromXml(Context context) { int id = com.android.internal.R.xml.power_profile; - XmlResourceParser parser = context.getResources().getXml(id); + final Resources resources = context.getResources(); + XmlResourceParser parser = resources.getXml(id); boolean parsingArray = false; ArrayList<Double> array = new ArrayList<Double>(); String arrayName = null; @@ -231,6 +240,36 @@ public class PowerProfile { } finally { parser.close(); } + + // Now collect other config variables. + int[] configResIds = new int[] { + com.android.internal.R.integer.config_bluetooth_idle_cur_ma, + com.android.internal.R.integer.config_bluetooth_rx_cur_ma, + com.android.internal.R.integer.config_bluetooth_tx_cur_ma, + com.android.internal.R.integer.config_bluetooth_operating_voltage_mv, + com.android.internal.R.integer.config_wifi_idle_receive_cur_ma, + com.android.internal.R.integer.config_wifi_active_rx_cur_ma, + com.android.internal.R.integer.config_wifi_tx_cur_ma, + com.android.internal.R.integer.config_wifi_operating_voltage_mv, + }; + + String[] configResIdKeys = new String[] { + POWER_BLUETOOTH_CONTROLLER_IDLE, + POWER_BLUETOOTH_CONTROLLER_RX, + POWER_BLUETOOTH_CONTROLLER_TX, + POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE, + POWER_WIFI_CONTROLLER_IDLE, + POWER_WIFI_CONTROLLER_RX, + POWER_WIFI_CONTROLLER_TX, + POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE, + }; + + for (int i = 0; i < configResIds.length; i++) { + int value = resources.getInteger(configResIds[i]); + if (value > 0) { + sPowerMap.put(configResIdKeys[i], (double) value); + } + } } /** diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java new file mode 100644 index 0000000..c98639b --- /dev/null +++ b/core/java/com/android/internal/os/SensorPowerCalculator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.BatteryStats; +import android.util.SparseArray; + +import java.util.List; + +public class SensorPowerCalculator extends PowerCalculator { + private final List<Sensor> mSensors; + private final double mGpsPowerOn; + + public SensorPowerCalculator(PowerProfile profile, SensorManager sensorManager) { + mSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + mGpsPowerOn = profile.getAveragePower(PowerProfile.POWER_GPS_ON); + } + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + // Process Sensor usage + final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); + final int NSE = sensorStats.size(); + for (int ise = 0; ise < NSE; ise++) { + final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise); + final int sensorHandle = sensorStats.keyAt(ise); + final BatteryStats.Timer timer = sensor.getSensorTime(); + final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; + switch (sensorHandle) { + case BatteryStats.Uid.Sensor.GPS: + app.gpsTimeMs = sensorTime; + app.gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000*60*60); + break; + default: + final int sensorsCount = mSensors.size(); + for (int i = 0; i < sensorsCount; i++) { + final Sensor s = mSensors.get(i); + if (s.getHandle() == sensorHandle) { + app.sensorPowerMah += (sensorTime * s.getPower()) / (1000*60*60); + break; + } + } + break; + } + } + } +} diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java new file mode 100644 index 0000000..7575010f --- /dev/null +++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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; +import android.util.ArrayMap; +import android.util.Log; + +public class WakelockPowerCalculator extends PowerCalculator { + private static final String TAG = "WakelockPowerCalculator"; + private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + private final double mPowerWakelock; + private long mTotalAppWakelockTimeMs = 0; + + public WakelockPowerCalculator(PowerProfile profile) { + mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE); + } + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawUptimeUs, + long rawRealtimeUs, int statsType) { + long wakeLockTimeUs = 0; + final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = + u.getWakelockStats(); + final int wakelockStatsCount = wakelockStats.size(); + for (int i = 0; i < wakelockStatsCount; i++) { + final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(i); + + // 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) { + wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType); + } + } + app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis + mTotalAppWakelockTimeMs += app.wakeLockTimeMs; + + // Add cost of holding a wake lock. + app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60); + if (DEBUG && app.wakeLockPowerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs + + " power=" + BatteryStatsHelper.makemAh(app.wakeLockPowerMah)); + } + } + + @Override + public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000; + wakeTimeMillis -= mTotalAppWakelockTimeMs + + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000); + if (wakeTimeMillis > 0) { + final double power = (wakeTimeMillis * mPowerWakelock) / (1000*60*60); + if (DEBUG) { + Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + + BatteryStatsHelper.makemAh(power)); + } + app.wakeLockTimeMs += wakeTimeMillis; + app.wakeLockPowerMah += power; + } + } + + @Override + public void reset() { + mTotalAppWakelockTimeMs = 0; + } +} diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java new file mode 100644 index 0000000..4e77f6b --- /dev/null +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 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; + +/** + * WiFi power calculator for when BatteryStats supports energy reporting + * from the WiFi controller. + */ +public class WifiPowerCalculator extends PowerCalculator { + private final double mIdleCurrentMa; + private final double mTxCurrentMa; + private final double mRxCurrentMa; + private double mTotalAppPowerDrain = 0; + + public WifiPowerCalculator(PowerProfile profile) { + mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); + mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX); + mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX); + } + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + final long idleTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME, + statsType); + final long txTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, statsType); + final long rxTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, statsType); + app.wifiRunningTimeMs = idleTime + rxTime + txTime; + app.wifiPowerMah = + ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) + / (1000*60*60); + mTotalAppPowerDrain += app.wifiPowerMah; + + app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, + statsType); + app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, + statsType); + app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, + statsType); + app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, + statsType); + } + + @Override + public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + final long idleTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME, + statsType); + final long rxTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, + statsType); + final long txTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, + statsType); + app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs; + + double powerDrain = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN, + statsType) / (1000*60*60); + if (powerDrain == 0) { + // Some controllers do not report power drain, so we can calculate it here. + powerDrain = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) + + (rxTimeMs * mRxCurrentMa)) / (1000*60*60); + } + app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain); + } + + @Override + public void reset() { + mTotalAppPowerDrain = 0; + } +} diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java new file mode 100644 index 0000000..0172367 --- /dev/null +++ b/core/java/com/android/internal/os/WifiPowerEstimator.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 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; + +/** + * Estimates WiFi power usage based on timers in BatteryStats. + */ +public class WifiPowerEstimator extends PowerCalculator { + private final double mWifiPowerPerPacket; + private final double mWifiPowerOn; + private final double mWifiPowerScan; + private final double mWifiPowerBatchScan; + private long mTotalAppWifiRunningTimeMs = 0; + + public WifiPowerEstimator(PowerProfile profile) { + mWifiPowerPerPacket = getWifiPowerPerPacket(profile); + mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON); + mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN); + mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN); + } + + /** + * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio. + */ + private static double getWifiPowerPerPacket(PowerProfile profile) { + final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system + final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) + / 3600; + return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60); + } + + @Override + public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, + statsType); + app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, + statsType); + app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, + statsType); + app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, + statsType); + + final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets) + * mWifiPowerPerPacket; + + app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; + mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs; + final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60); + + final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType); + final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60); + + double wifiBatchScanPower = 0; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + final long batchScanTimeMs = + u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; + final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60); + wifiBatchScanPower += batchScanPower; + } + + app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower; + } + + @Override + public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) + / 1000; + final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) + / (1000*60*60); + app.wifiRunningTimeMs = totalRunningTimeMs; + app.wifiPowerMah = Math.max(0, powerDrain); + } + + @Override + public void reset() { + mTotalAppWifiRunningTimeMs = 0; + } +} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index eb394c3..1ac1c8a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1487,7 +1487,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkCapabilities.TRANSPORT_WIFI)) { timeout = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI, - 0); + 5); type = ConnectivityManager.TYPE_WIFI; } else { // do not track any other networks diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 7b542be..b5b62b4 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -210,6 +210,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub private boolean mMobileActivityFromRadio = false; private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners = new RemoteCallbackList<INetworkActivityListener>(); @@ -434,6 +435,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } + if (ConnectivityManager.isNetworkTypeWifi(type)) { + if (mLastPowerStateFromWifi != powerState) { + mLastPowerStateFromWifi = powerState; + try { + getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos); + } catch (RemoteException e) { + } + } + } + boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c8db3be..ac70d88 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -40,6 +40,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; +import android.telephony.DataConnectionRealTimeInfo; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Slog; @@ -122,6 +123,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mStats.setRadioScanningTimeout(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); + mStats.setPowerProfile(new PowerProfile(context)); } /** @@ -541,6 +543,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + @Override + public void noteWifiRadioPowerState(int powerState, long tsNanos) { + enforceCallingPermission(); + + // There was a change in WiFi power state. + // Collect data now for the past activity. + mHandler.scheduleSync(); + } + public void noteWifiRunning(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { @@ -1095,13 +1106,29 @@ public final class BatteryStatsService extends IBatteryStats.Stub result.mTimestamp = info.getTimeStamp(); result.mStackState = info.getStackState(); result.mControllerTxTimeMs = - info.getControllerTxTimeMillis()- mLastInfo.mControllerTxTimeMs; + info.mControllerTxTimeMs - mLastInfo.mControllerTxTimeMs; result.mControllerRxTimeMs = - info.getControllerRxTimeMillis() - mLastInfo.mControllerRxTimeMs; - result.mControllerIdleTimeMs = - info.getControllerIdleTimeMillis() - mLastInfo.mControllerIdleTimeMs; + info.mControllerRxTimeMs - mLastInfo.mControllerRxTimeMs; result.mControllerEnergyUsed = - info.getControllerEnergyUsed() - mLastInfo.mControllerEnergyUsed; + info.mControllerEnergyUsed - mLastInfo.mControllerEnergyUsed; + + // WiFi calculates the idle time as a difference from the on time and the various + // Rx + Tx times. There seems to be some missing time there because this sometimes + // becomes negative. Just cap it at 0 and move on. + result.mControllerIdleTimeMs = + Math.max(0, info.mControllerIdleTimeMs - mLastInfo.mControllerIdleTimeMs); + + if (result.mControllerTxTimeMs < 0 || + result.mControllerRxTimeMs < 0) { + // The stats were reset by the WiFi system (which is why our delta is negative). + // Returns the unaltered stats. + result.mControllerEnergyUsed = info.mControllerEnergyUsed; + result.mControllerRxTimeMs = info.mControllerRxTimeMs; + result.mControllerTxTimeMs = info.mControllerTxTimeMs; + result.mControllerIdleTimeMs = info.mControllerIdleTimeMs; + + Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + result); + } mLastInfo = info; return result; } @@ -1133,6 +1160,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub */ void updateExternalStats() { synchronized (mExternalStatsLock) { + if (mContext == null) { + // We haven't started yet (which means the BatteryStatsImpl object has + // no power profile. Don't consume data we can't compute yet. + return; + } + final WifiActivityEnergyInfo wifiEnergyInfo = pullWifiEnergyInfoLocked(); final BluetoothActivityEnergyInfo bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked(); synchronized (mStats) { |
