summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/BatteryConsumer.java3
-rw-r--r--core/java/android/os/BatteryStats.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java16
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java16
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java6
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsProcessor.java4
-rw-r--r--services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java1
-rw-r--r--services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java81
-rw-r--r--services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java311
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java241
10 files changed, 663 insertions, 18 deletions
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 000a537cf3c3..623196b637f6 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -209,7 +209,8 @@ public abstract class BatteryConsumer {
POWER_COMPONENT_VIDEO,
POWER_COMPONENT_FLASHLIGHT,
POWER_COMPONENT_CAMERA,
- POWER_COMPONENT_GNSS};
+ POWER_COMPONENT_GNSS,
+ POWER_COMPONENT_SENSORS};
Arrays.sort(supportedPowerComponents);
SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = IntArray.wrap(supportedPowerComponents);
};
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cdffea4e9ede..c7751e3e5cea 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3077,7 +3077,7 @@ public abstract class BatteryStats {
public static final String[] HISTORY_EVENT_NAMES = new String[] {
"null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
"active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive",
- "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity", "state"
+ "tmpwhitelist", "screenwake", "wakeupap", "longwake", "state"
};
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e46ab8f0f1cb..03fbfd37cbdb 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -144,6 +144,7 @@ import com.android.server.power.stats.PowerStatsScheduler;
import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.PowerStatsUidResolver;
import com.android.server.power.stats.ScreenPowerStatsProcessor;
+import com.android.server.power.stats.SensorPowerStatsProcessor;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.VideoPowerStatsProcessor;
import com.android.server.power.stats.WifiPowerStatsProcessor;
@@ -595,6 +596,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub
.setProcessor(
new GnssPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(new SensorPowerStatsProcessor(
+ () -> mContext.getSystemService(SensorManager.class)));
+
config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new)
.trackDeviceStates(
AggregatedPowerStatsConfig.STATE_POWER,
@@ -706,6 +718,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
BatteryConsumer.POWER_COMPONENT_GNSS,
Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_SENSORS,
+ Flags.streamlinedMiscBatteryStats());
+
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
Flags.streamlinedMiscBatteryStats());
mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index c4b37c6939b7..143b3ffa93af 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -5431,8 +5431,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- int mSensorNesting;
-
@GuardedBy("this")
public void noteStartSensorLocked(int uid, int sensor) {
noteStartSensorLocked(uid, sensor, mClock.elapsedRealtime(), mClock.uptimeMillis());
@@ -5441,11 +5439,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
- if (mSensorNesting == 0) {
- mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
- HistoryItem.STATE_SENSOR_ON_FLAG);
- }
- mSensorNesting++;
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_SENSOR_ON_FLAG, uid, "sensor:0x" + Integer.toHexString(sensor));
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteStartSensor(sensor, elapsedRealtimeMs);
}
@@ -5458,11 +5453,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteStopSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
- mSensorNesting--;
- if (mSensorNesting == 0) {
- mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
- HistoryItem.STATE_SENSOR_ON_FLAG);
- }
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_SENSOR_ON_FLAG, uid, "sensor:0x" + Integer.toHexString(sensor));
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteStopSensor(sensor, elapsedRealtimeMs);
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index a5e4cf5a0e33..b308f3840383 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -93,8 +93,10 @@ public class BatteryUsageStatsProvider {
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) {
mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile));
}
- mPowerCalculators.add(new SensorPowerCalculator(
- mContext.getSystemService(SensorManager.class)));
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SENSORS)) {
+ mPowerCalculators.add(new SensorPowerCalculator(
+ mContext.getSystemService(SensorManager.class)));
+ }
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 7d7b3c2fa3c5..c81c7ffe5371 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -257,8 +257,8 @@ public abstract class PowerStatsProcessor {
for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
deviceStateEstimations.get(i).intermediates = null;
}
- for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
- deviceStateEstimations.get(i).intermediates = null;
+ for (int i = combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ combinedDeviceStateEstimations.get(i).intermediates = null;
}
for (int i = uidStateEstimates.size() - 1; i >= 0; i--) {
UidStateEstimate uidStateEstimate = uidStateEstimates.get(i);
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
index e203e4a4175b..908c75155f42 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
@@ -119,6 +119,7 @@ public class ScreenPowerStatsProcessor extends PowerStatsProcessor {
if (!uids.isEmpty()) {
computeUidPowerEstimates(stats, uids);
}
+ mPlan.resetIntermediates();
}
private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
diff --git a/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java
new file mode 100644
index 000000000000..e66cd3970d2f
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.server.power.stats;
+
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+public class SensorPowerStatsLayout extends PowerStatsLayout {
+ private static final String TAG = "SensorPowerStatsLayout";
+ private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dsh";
+ private static final String EXTRA_UID_SENSOR_POSITIONS = "usp";
+
+ private final SparseIntArray mSensorPositions = new SparseIntArray();
+
+ void addUidSensorSection(int handle, String label) {
+ mSensorPositions.put(handle, addUidSection(1, label, FLAG_OPTIONAL));
+ }
+
+ /**
+ * Returns the position in the uid stats array of the duration element corresponding
+ * to the specified sensor identified by its handle.
+ */
+ public int getUidSensorDurationPosition(int handle) {
+ return mSensorPositions.get(handle, UNSUPPORTED);
+ }
+
+ /**
+ * Adds the specified duration to the accumulated timer for the specified sensor.
+ */
+ public void addUidSensorDuration(long[] stats, int handle, long durationMs) {
+ int position = mSensorPositions.get(handle, UNSUPPORTED);
+ if (position == UNSUPPORTED) {
+ Slog.e(TAG, "Unknown sensor: " + handle);
+ return;
+ }
+ stats[position] += durationMs;
+ }
+
+ @Override
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+
+ int[] handlers = new int[mSensorPositions.size()];
+ int[] uidDurationPositions = new int[mSensorPositions.size()];
+
+ for (int i = 0; i < mSensorPositions.size(); i++) {
+ handlers[i] = mSensorPositions.keyAt(i);
+ uidDurationPositions[i] = mSensorPositions.valueAt(i);
+ }
+
+ extras.putIntArray(EXTRA_DEVICE_SENSOR_HANDLES, handlers);
+ extras.putIntArray(EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions);
+ }
+
+ @Override
+ public void fromExtras(PersistableBundle extras) {
+ super.fromExtras(extras);
+
+ int[] handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES);
+ int[] uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS);
+
+ for (int i = 0; i < handlers.length; i++) {
+ mSensorPositions.put(handlers[i], uidDurationPositions[i]);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java
new file mode 100644
index 000000000000..7b815d91b325
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2024 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.server.power.stats;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class SensorPowerStatsProcessor extends PowerStatsProcessor {
+ private static final String TAG = "SensorPowerStatsProcessor";
+ private static final String ANDROID_SENSOR_TYPE_PREFIX = "android.sensor.";
+
+ private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+ private static final String SENSOR_EVENT_TAG_PREFIX = "sensor:0x";
+ private final Supplier<SensorManager> mSensorManagerSupplier;
+
+ private static final long INITIAL_TIMESTAMP = -1;
+ private SensorManager mSensorManager;
+ private SensorPowerStatsLayout mStatsLayout;
+ private PowerStats mPowerStats;
+ private boolean mIsInitialized;
+ private PowerStats.Descriptor mDescriptor;
+ private long mLastUpdateTimestamp;
+ private PowerEstimationPlan mPlan;
+
+ private static class SensorState {
+ public int sensorHandle;
+ public boolean stateOn;
+ public int uid;
+ public long startTime = INITIAL_TIMESTAMP;
+ }
+
+ private static class Intermediates {
+ public double power;
+ }
+
+ private final SparseArray<SensorState> mSensorStates = new SparseArray<>();
+ private long[] mTmpDeviceStatsArray;
+ private long[] mTmpUidStatsArray;
+
+ public SensorPowerStatsProcessor(Supplier<SensorManager> sensorManagerSupplier) {
+ mSensorManagerSupplier = sensorManagerSupplier;
+ }
+
+ private boolean ensureInitialized() {
+ if (mIsInitialized) {
+ return true;
+ }
+
+ mSensorManager = mSensorManagerSupplier.get();
+ if (mSensorManager == null) {
+ return false;
+ }
+
+ mStatsLayout = new SensorPowerStatsLayout();
+ List<Sensor> sensorList = new ArrayList<>(mSensorManager.getSensorList(Sensor.TYPE_ALL));
+ sensorList.sort(Comparator.comparingInt(Sensor::getId));
+ for (int i = 0; i < sensorList.size(); i++) {
+ Sensor sensor = sensorList.get(i);
+ String label = makeLabel(sensor, sensorList);
+ mStatsLayout.addUidSensorSection(sensor.getHandle(), label);
+ }
+ mStatsLayout.addUidSectionPowerEstimate();
+ mStatsLayout.addDeviceSectionPowerEstimate();
+
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(
+ BatteryConsumer.POWER_COMPONENT_SENSORS, mStatsLayout.getDeviceStatsArrayLength(),
+ null, 0, mStatsLayout.getUidStatsArrayLength(),
+ extras);
+
+ mPowerStats = new PowerStats(mDescriptor);
+ mTmpUidStatsArray = new long[mDescriptor.uidStatsArrayLength];
+ mTmpDeviceStatsArray = new long[mDescriptor.statsArrayLength];
+
+ mIsInitialized = true;
+ return true;
+ }
+
+ private String makeLabel(Sensor sensor, List<Sensor> sensorList) {
+ int type = sensor.getType();
+ String label = sensor.getStringType();
+
+ boolean isSingleton = true;
+ for (int i = sensorList.size() - 1; i >= 0; i--) {
+ Sensor s = sensorList.get(i);
+ if (s == sensor) {
+ continue;
+ }
+ if (s.getType() == type) {
+ isSingleton = false;
+ break;
+ }
+ }
+ if (!isSingleton) {
+ StringBuilder sb = new StringBuilder(label).append('.');
+ if (sensor.getId() > 0) { // 0 and -1 are reserved
+ sb.append(sensor.getId());
+ } else {
+ sb.append(sensor.getName());
+ }
+ label = sb.toString();
+ }
+ if (label.startsWith(ANDROID_SENSOR_TYPE_PREFIX)) {
+ label = label.substring(ANDROID_SENSOR_TYPE_PREFIX.length());
+ }
+ return label.replace(' ', '_');
+ }
+
+ @Override
+ void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ if (!ensureInitialized()) {
+ return;
+ }
+
+ // Establish a baseline at the beginning of an accumulation pass
+ mLastUpdateTimestamp = timestampMs;
+ flushPowerStats(stats, timestampMs);
+ }
+
+ @Override
+ void noteStateChange(PowerComponentAggregatedPowerStats stats, BatteryStats.HistoryItem item) {
+ if (!mIsInitialized) {
+ return;
+ }
+
+ if (item.eventTag == null || !item.eventTag.string.startsWith(SENSOR_EVENT_TAG_PREFIX)) {
+ return;
+ }
+
+ int sensorHandle;
+ try {
+ sensorHandle = Integer.parseInt(item.eventTag.string, SENSOR_EVENT_TAG_PREFIX.length(),
+ item.eventTag.string.length(), 16);
+ } catch (NumberFormatException e) {
+ Slog.wtf(TAG, "Bad format of event tag: " + item.eventTag.string);
+ return;
+ }
+
+ SensorState sensor = mSensorStates.get(sensorHandle);
+ if (sensor == null) {
+ sensor = new SensorState();
+ sensor.sensorHandle = sensorHandle;
+ mSensorStates.put(sensorHandle, sensor);
+ }
+
+ int uid = item.eventTag.uid;
+ boolean sensorOn = (item.states & BatteryStats.HistoryItem.STATE_SENSOR_ON_FLAG) != 0;
+ if (sensorOn) {
+ if (!sensor.stateOn) {
+ sensor.stateOn = true;
+ sensor.uid = uid;
+ sensor.startTime = item.time;
+ } else if (sensor.uid != uid) {
+ recordUsageDuration(sensor, item.time);
+ sensor.uid = uid;
+ }
+ } else {
+ if (sensor.stateOn) {
+ recordUsageDuration(sensor, item.time);
+ sensor.stateOn = false;
+ }
+ }
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ if (!mIsInitialized) {
+ return;
+ }
+
+ for (int i = mSensorStates.size() - 1; i >= 0; i--) {
+ SensorState sensor = mSensorStates.valueAt(i);
+ if (sensor.stateOn) {
+ recordUsageDuration(sensor, timestampMs);
+ }
+ }
+ flushPowerStats(stats, timestampMs);
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ List<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+
+ computeUidPowerEstimates(stats, uids);
+ computeDevicePowerEstimates(stats);
+
+ mPlan.resetIntermediates();
+ }
+
+ protected void recordUsageDuration(SensorState sensorState, long time) {
+ long durationMs = Math.max(0, time - sensorState.startTime);
+ if (durationMs > 0) {
+ long[] uidStats = mPowerStats.uidStats.get(sensorState.uid);
+ if (uidStats == null) {
+ uidStats = new long[mDescriptor.uidStatsArrayLength];
+ mPowerStats.uidStats.put(sensorState.uid, uidStats);
+ }
+ mStatsLayout.addUidSensorDuration(uidStats, sensorState.sensorHandle, durationMs);
+ }
+ sensorState.startTime = time;
+ }
+
+ private void flushPowerStats(PowerComponentAggregatedPowerStats stats, long timestamp) {
+ mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
+ stats.addPowerStats(mPowerStats, timestamp);
+
+ Arrays.fill(mPowerStats.stats, 0);
+ mPowerStats.uidStats.clear();
+ mLastUpdateTimestamp = timestamp;
+ }
+
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
+ List<Integer> uids) {
+ List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+ int[] uidSensorDurationPositions = new int[sensorList.size()];
+ double[] sensorPower = new double[sensorList.size()];
+ for (int i = sensorList.size() - 1; i >= 0; i--) {
+ Sensor sensor = sensorList.get(i);
+ uidSensorDurationPositions[i] =
+ mStatsLayout.getUidSensorDurationPosition(sensor.getHandle());
+ sensorPower[i] = sensor.getPower() / MILLIS_IN_HOUR;
+ }
+
+ for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+ List<UidStateProportionalEstimate> proportionalEstimates =
+ uidStateEstimate.proportionalEstimates;
+ for (int j = proportionalEstimates.size() - 1; j >= 0; j--) {
+ UidStateProportionalEstimate proportionalEstimate = proportionalEstimates.get(j);
+ for (int k = uids.size() - 1; k >= 0; k--) {
+ int uid = uids.get(k);
+ if (!stats.getUidStats(mTmpUidStatsArray, uid,
+ proportionalEstimate.stateValues)) {
+ continue;
+ }
+ double power = 0;
+ for (int m = 0; m < uidSensorDurationPositions.length; m++) {
+ int position = uidSensorDurationPositions[m];
+ if (position == PowerStatsLayout.UNSUPPORTED
+ || mTmpUidStatsArray[position] == 0) {
+ continue;
+ }
+ power += sensorPower[m] * mTmpUidStatsArray[position];
+ }
+ if (power == 0) {
+ continue;
+ }
+
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+
+ Intermediates intermediates = (Intermediates) uidStateEstimate
+ .combinedDeviceStateEstimate.intermediates;
+ if (intermediates == null) {
+ intermediates = new Intermediates();
+ uidStateEstimate.combinedDeviceStateEstimate.intermediates = intermediates;
+ }
+ intermediates.power += power;
+ }
+ }
+ }
+ }
+
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate estimation =
+ mPlan.combinedDeviceStateEstimations.get(i);
+ if (estimation.intermediates == null) {
+ continue;
+ }
+
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+ continue;
+ }
+
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
+ ((Intermediates) estimation.intermediates).power);
+ stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java
new file mode 100644
index 000000000000..7000487e7912
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 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.server.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.input.InputSensorInfo;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class SensorPowerStatsProcessorTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .initMeasuredEnergyStatsLocked();
+
+ private static final double PRECISION = 0.00001;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int SENSOR_HANDLE_1 = 77;
+ private static final int SENSOR_HANDLE_2 = 88;
+ private static final int SENSOR_HANDLE_3 = 99;
+
+ @Mock
+ private SensorManager mSensorManager;
+
+ private MonotonicClock mMonotonicClock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+ Sensor sensor1 = createSensor(SENSOR_HANDLE_1, Sensor.TYPE_STEP_COUNTER,
+ Sensor.STRING_TYPE_STEP_COUNTER, "dancing", 100);
+ Sensor sensor2 = createSensor(SENSOR_HANDLE_2, Sensor.TYPE_MOTION_DETECT,
+ "com.example", "tango", 200);
+ Sensor sensor3 = createSensor(SENSOR_HANDLE_3, Sensor.TYPE_MOTION_DETECT,
+ "com.example", "waltz", 300);
+ when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(
+ List.of(sensor1, sensor2, sensor3));
+ }
+
+ @Test
+ public void testPowerEstimation() {
+ SensorPowerStatsProcessor processor = new SensorPowerStatsProcessor(() -> mSensorManager);
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+ processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1, SENSOR_HANDLE_1));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+ processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1, SENSOR_HANDLE_1));
+ processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2, SENSOR_HANDLE_1));
+ processor.noteStateChange(stats, buildHistoryItem(8000, true, APP_UID2, SENSOR_HANDLE_2));
+ processor.noteStateChange(stats, buildHistoryItem(9000, false, APP_UID2, SENSOR_HANDLE_1));
+
+ processor.finish(stats, 10000);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ SensorPowerStatsLayout statsLayout = new SensorPowerStatsLayout();
+ statsLayout.fromExtras(descriptor.extras);
+
+ String dump = stats.toString();
+ assertThat(dump).contains(" step_counter: ");
+ assertThat(dump).contains(" com.example.tango: ");
+
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+
+ // For UID1:
+ // SENSOR1 was on for 6000 ms.
+ // Estimated power: 6000 * 100 = 0.167 mAh
+ // split between three different states
+ // fg screen-on: 6000 * 2500/10000
+ // bg screen-off: 6000 * 2500/10000
+ // fgs screen-off: 6000 * 5000/10000
+ double expectedPower1 = 0.166666;
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 10000);
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 10000);
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 5000 / 10000);
+
+ // For UID2:
+ // SENSOR1 was on for 2000 ms.
+ // Estimated power: 2000 * 100 = 0.0556 mAh
+ // split between three different states
+ // cached screen-on: 2000 * 2500/10000
+ // cached screen-off: 2000 * 7500/10000
+ // SENSOR2 was on for 2000 ms.
+ // Estimated power: 2000 * 200 = 0.11111 mAh
+ // split between three different states
+ // cached screen-on: 2000 * 2500/10000
+ // cached screen-off: 2000 * 7500/10000
+ double expectedPower2 = 0.05555 + 0.11111;
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 2500 / 10000);
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 7500 / 10000);
+
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of((expectedPower1 + expectedPower2) * 2500 / 10000);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of((expectedPower1 + expectedPower2) * 7500 / 10000);
+ }
+
+ private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+ int uid, int sensor) {
+ mStatsRule.setTime(timestamp, timestamp);
+ BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+ historyItem.time = mMonotonicClock.monotonicTime();
+ historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_SENSOR_ON_FLAG : 0;
+ if (stateOn) {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_START;
+ } else {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+ }
+ historyItem.eventTag = historyItem.localEventTag;
+ historyItem.eventTag.uid = uid;
+ historyItem.eventTag.string = "sensor:0x" + Integer.toHexString(sensor);
+ return historyItem;
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ SensorPowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ aggregatedPowerStats.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_SENSORS);
+ processor.start(powerComponentStats, 0);
+
+ powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ return powerComponentStats;
+ }
+
+ private Sensor createSensor(int handle, int type, String stringType, String name, float power) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ Sensor sensor = mock(Sensor.class);
+ when(sensor.getHandle()).thenReturn(handle);
+ when(sensor.getType()).thenReturn(type);
+ when(sensor.getStringType()).thenReturn(stringType);
+ when(sensor.getName()).thenReturn(name);
+ when(sensor.getPower()).thenReturn(power);
+ return sensor;
+ } else {
+ return new Sensor(new InputSensorInfo(name, "vendor", 0 /* version */,
+ handle, type, 100.0f /*maxRange */, 0.02f /* resolution */,
+ (float) power, 1000 /* minDelay */, 0 /* fifoReservedEventCount */,
+ 0 /* fifoMaxEventCount */, stringType /* stringType */,
+ "" /* requiredPermission */, 0 /* maxDelay */, 0 /* flags */, 0 /* id */));
+ }
+ }
+}