Statsd atom: Power Use
BatteryStats calculates power usage of the device and various components
(such as apps). This information is used, e.g., in the battery panel of
Settings. We now log it to statsd. It can be used for validating how
good the information displayed in Settings is. In the long-term, it is
likely not ideal for off-device calculations, since that can be
hopefully estimated using statsd's raw data.
Three atoms: one for the total power use, one for the power use of each
uid, and one for each non-uid component. Since they will all likely be
pulled together, StatsCompanionService will provide stale data for
BatteryStats pulls called within a second of a previous BatteryStats
pull.
Also in this cl:
Remove StatsLogEventWrapper.writeDouble. Statsd doesn't support actually
writing doubles into its proto reports, so having this function is
misleading (the data will get to statsd and then be completely ignored).
It's less confusing if we don't pretend it does something.
Change-Id: If80bab8ea938afa4632535bb88ff59879fbe8099
Fixes: 119111972
Test: cts-tradefed run cts-dev -m CtsStatsdHostTestCases -t android.cts.statsd.atom.UidAtomTests#testDeviceCalculatedPowerUse
Test: cts-tradefed run cts-dev -m CtsStatsdHostTestCases -t android.cts.statsd.atom.UidAtomTests#testDeviceCalculatedPowerBlameUid
Test: BatteryStatsHelperTest#testDrainTypesSyncedWithProto
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 53d9673..244974d 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -192,6 +192,9 @@
NativeProcessMemoryState native_process_memory_state = 10036;
CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037;
OnDevicePowerMeasurement on_device_power_measurement = 10038;
+ DeviceCalculatedPowerUse device_calculated_power_use = 10039;
+ DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid = 10040;
+ DeviceCalculatedPowerBlameOther device_calculated_power_blame_other = 10041;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -3198,3 +3201,63 @@
// Time spent in frequency in milliseconds, since thread start.
optional uint32 time_millis = 7;
}
+
+/**
+ * Pulls on-device BatteryStats power use calculations for the overall device.
+ */
+message DeviceCalculatedPowerUse {
+ // Power used by the device in mAh, as computed by BatteryStats, since BatteryStats last reset
+ // (i.e. roughly since device was last significantly charged).
+ // Currently, this is BatteryStatsHelper.getComputedPower() (not getTotalPower()).
+ optional float computed_power_milli_amp_hours = 1;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations broken down by uid.
+ * This atom should be complemented by DeviceCalculatedPowerBlameOther, which contains the power use
+ * that is attributed to non-uid items. They must all be included to get the total power use.
+ */
+message DeviceCalculatedPowerBlameUid {
+ // Uid being blamed. Note: isolated uids have already been mapped to host uid.
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // Power used by this uid in mAh, as computed by BatteryStats, since BatteryStats last reset
+ // (i.e. roughly since device was last significantly charged).
+ optional float power_milli_amp_hours = 2;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations that are not due to a uid, broken down by
+ * drain type.
+ * This atom should be complemented by DeviceCalculatedPowerBlameUid, which contains the blame that
+ * is attributed uids. They must all be included to get the total power use.
+ */
+message DeviceCalculatedPowerBlameOther {
+ // The type of item whose power use is being reported.
+ enum DrainType {
+ AMBIENT_DISPLAY = 0;
+ // reserved 1; reserved "APP"; // Logged instead in DeviceCalculatedPowerBlameUid.
+ BLUETOOTH = 2;
+ CAMERA = 3;
+ // Cell-standby
+ CELL = 4;
+ FLASHLIGHT = 5;
+ IDLE = 6;
+ MEMORY = 7;
+ // Amount that total computed drain exceeded the drain estimated using the
+ // battery level changes and capacity.
+ OVERCOUNTED = 8;
+ PHONE = 9;
+ SCREEN = 10;
+ // Amount that total computed drain was below the drain estimated using the
+ // battery level changes and capacity.
+ UNACCOUNTED = 11;
+ // reserved 12; reserved "USER"; // Entire drain for a user. This is NOT supported.
+ WIFI = 13;
+ }
+ optional DrainType drain_type = 1;
+
+ // Power used by this item in mAh, as computed by BatteryStats, since BatteryStats last reset
+ // (i.e. roughly since device was last significantly charged).
+ optional float power_milli_amp_hours = 2;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 8378ae1..0e131cb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -243,6 +243,20 @@
{2, 3, 4, 5, 6},
1 * NS_PER_SEC,
new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
+ // DeviceCalculatedPowerUse.
+ {android::util::DEVICE_CALCULATED_POWER_USE,
+ {{}, {}, 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
+ // DeviceCalculatedPowerBlameUid.
+ {android::util::DEVICE_CALCULATED_POWER_BLAME_UID,
+ {{}, {}, // BatteryStats already merged isolated with host ids so it's unnecessary here.
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
+ // DeviceCalculatedPowerBlameOther.
+ {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
+ {{}, {},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
index 72e1ab9..866bd9a 100644
--- a/core/java/android/os/StatsLogEventWrapper.java
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -104,14 +104,6 @@
}
/**
- * Write a double value.
- */
- public void writeDouble(double val) {
- mTypes.add(EVENT_TYPE_DOUBLE);
- mValues.add(val);
- }
-
- /**
* Write a storage value.
*/
public void writeStorage(byte[] val) {
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 0baf73c..02c9542 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -130,6 +130,10 @@
public double wakeLockPowerMah;
public double wifiPowerMah;
+ // ****************
+ // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
+ // so the ordinal values (and therefore the order) must never change.
+ // ****************
public enum DrainType {
AMBIENT_DISPLAY,
@UnsupportedAppUsage
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index e81f678..7467114 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -41,6 +41,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.format.DateUtils;
+import android.util.StatsLog;
import junit.framework.TestCase;
@@ -258,6 +259,36 @@
assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
}
+ @Test
+ public void testDrainTypesSyncedWithProto() {
+ assertEquals(BatterySipper.DrainType.AMBIENT_DISPLAY.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__AMBIENT_DISPLAY);
+ // AtomsProto has no "APP"
+ assertEquals(BatterySipper.DrainType.BLUETOOTH.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__BLUETOOTH);
+ assertEquals(BatterySipper.DrainType.CAMERA.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__CAMERA);
+ assertEquals(BatterySipper.DrainType.CELL.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__CELL);
+ assertEquals(BatterySipper.DrainType.FLASHLIGHT.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__FLASHLIGHT);
+ assertEquals(BatterySipper.DrainType.IDLE.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__IDLE);
+ assertEquals(BatterySipper.DrainType.MEMORY.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__MEMORY);
+ assertEquals(BatterySipper.DrainType.OVERCOUNTED.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__OVERCOUNTED);
+ assertEquals(BatterySipper.DrainType.PHONE.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__PHONE);
+ assertEquals(BatterySipper.DrainType.SCREEN.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__SCREEN);
+ assertEquals(BatterySipper.DrainType.UNACCOUNTED.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__UNACCOUNTED);
+ // AtomsProto has no "USER"
+ assertEquals(BatterySipper.DrainType.WIFI.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__WIFI);
+ }
+
private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
int uidCode, boolean isUidNull) {
final BatterySipper sipper = mock(BatterySipper.class);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 68f24fb..a4c5ed2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -173,6 +173,7 @@
<assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
<assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
+ <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" />
<assign-permission name="android.permission.DUMP" uid="statsd" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
index a1a6d9f..04c4629 100644
--- a/libs/services/src/os/StatsLogEventWrapper.cpp
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -85,9 +85,6 @@
case StatsLogValue::FLOAT:
mElements.push_back(StatsLogValue(in->readFloat()));
break;
- case StatsLogValue::DOUBLE:
- mElements.push_back(StatsLogValue(in->readDouble()));
- break;
case StatsLogValue::STORAGE:
mElements.push_back(StatsLogValue());
mElements.back().setType(StatsLogValue::STORAGE);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 01d02d6..d111702 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -47,6 +47,7 @@
import android.net.NetworkStats;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Bundle;
@@ -87,6 +88,8 @@
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelCpuThreadReader;
@@ -205,6 +208,10 @@
@Nullable
private final KernelCpuThreadReader mKernelCpuThreadReader;
+ private BatteryStatsHelper mBatteryStatsHelper = null;
+ private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
+ private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
+
private static IThermalService sThermalService;
private File mBaseDir =
new File(SystemServiceManager.ensureSystemDir(), "stats_companion");
@@ -1430,6 +1437,73 @@
pulledData.add(e);
}
+ private BatteryStatsHelper getBatteryStatsHelper() {
+ if (mBatteryStatsHelper == null) {
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
+ mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ mBatteryStatsHelper.create((Bundle) null);
+ }
+ long currentTime = SystemClock.elapsedRealtime();
+ if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
+ // Load BatteryStats and do all the calculations.
+ mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+ // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
+ mBatteryStatsHelper.clearStats();
+ mBatteryStatsHelperTimestampMs = currentTime;
+ }
+ return mBatteryStatsHelper;
+ }
+
+ private void pullDeviceCalculatedPowerUse(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ BatteryStatsHelper bsHelper = getBatteryStatsHelper();
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeFloat((float) bsHelper.getComputedPower());
+ pulledData.add(e);
+ }
+
+ private void pullDeviceCalculatedPowerBlameUid(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return;
+ }
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType != bs.drainType.APP) {
+ continue;
+ }
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(bs.uidObj.getUid());
+ e.writeFloat((float) bs.totalPowerMah);
+ pulledData.add(e);
+ }
+ }
+
+ private void pullDeviceCalculatedPowerBlameOther(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return;
+ }
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType == bs.drainType.APP) {
+ continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
+ }
+ if (bs.drainType == bs.drainType.USER) {
+ continue; // This is not supported. We purposefully calculate over USER_ALL.
+ }
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(bs.drainType.ordinal());
+ e.writeFloat((float) bs.totalPowerMah);
+ pulledData.add(e);
+ }
+ }
+
private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
@@ -1655,6 +1729,18 @@
pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.DEVICE_CALCULATED_POWER_USE: {
+ pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
+ case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
+ pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
+ case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
+ pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;