Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 853fd9ba authored by Milo Sredkov's avatar Milo Sredkov
Browse files

Add per-UID measured camera stats

Attribute camera energy consumption to individual UIDs based on their
camera-on time.

Works similarly to how GNSS energy consumption is attributed to UIDs.

Bug: 258319905
Test: atest BatteryStatsTests
Change-Id: Ia2191fc0a96d8b966459b127a8949508427d9531
parent 91467df8
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1142,6 +1142,16 @@ public abstract class BatteryStats {
                @BatteryConsumer.ProcessState int processState);



        /**
         * Returns the battery consumption (in microcoulombs) of UID's camera usage, derived from
         * on-device power measurement data.
         * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
         *
         * {@hide}
         */
        public abstract long getCameraEnergyConsumptionUC();

        /**
         * Returns the battery consumption (in microcoulombs) used by this uid for each
         * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+43 −2
Original line number Diff line number Diff line
@@ -8442,6 +8442,12 @@ public class BatteryStatsImpl extends BatteryStats {
                    processState);
        }
        @GuardedBy("mBsi")
        @Override
        public long getCameraEnergyConsumptionUC() {
            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CAMERA);
        }
        /**
         * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
         * since last marked. Also sets the mark time for both these timers.
@@ -8492,6 +8498,20 @@ public class BatteryStatsImpl extends BatteryStats {
            return gnssTimeUs;
        }
        /**
         * Gets the uid's time spent using the camera since last marked. Also sets the mark time for
         * the camera timer.
         */
        private long markCameraTimeUs(long elapsedRealtimeMs) {
            final StopwatchTimer timer = mCameraTurnedOnTimer;
            if (timer == null) {
                return 0;
            }
            final long cameraTimeUs = timer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000);
            timer.setMark(elapsedRealtimeMs);
            return cameraTimeUs;
        }
        public StopwatchTimer createAudioTurnedOnTimerLocked() {
            if (mAudioTurnedOnTimer == null) {
                mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClock, Uid.this, AUDIO_TURNED_ON,
@@ -12934,12 +12954,33 @@ public class BatteryStatsImpl extends BatteryStats {
            return;
        }
        // TODO(b/258319905): Handle mIgnoreNextExternalStats
        if (mIgnoreNextExternalStats) {
            // Although under ordinary resets we won't get here, and typically a new sync will
            // happen right after the reset, strictly speaking we need to set all mark times to now.
            final int uidStatsSize = mUidStats.size();
            for (int i = 0; i < uidStatsSize; i++) {
                final Uid uid = mUidStats.valueAt(i);
                uid.markCameraTimeUs(elapsedRealtimeMs);
            }
            return;
        }
        mGlobalEnergyConsumerStats.updateStandardBucket(
                EnergyConsumerStats.POWER_BUCKET_CAMERA, chargeUC);
        // TODO(b/258319905): Update per-UID stats.
        // Collect the per uid time since mark so that we can normalize power.
        final SparseDoubleArray cameraTimeUsArray = new SparseDoubleArray();
        // Note: Iterating over all UIDs may be suboptimal.
        final int uidStatsSize = mUidStats.size();
        for (int i = 0; i < uidStatsSize; i++) {
            final Uid uid = mUidStats.valueAt(i);
            final long cameraTimeUs = uid.markCameraTimeUs(elapsedRealtimeMs);
            if (cameraTimeUs == 0) continue;
            cameraTimeUsArray.put(uid.getUid(), (double) cameraTimeUs);
        }
        distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_CAMERA, chargeUC,
                cameraTimeUsArray, 0, elapsedRealtimeMs);
    }
    /**
+11 −3
Original line number Diff line number Diff line
@@ -73,11 +73,19 @@ public class CameraPowerCalculator extends PowerCalculator {
    @Override
    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
        final long durationMs =
        long consumptionUc = app.getBatteryStatsUid().getCameraEnergyConsumptionUC();
        int powerModel = getPowerModel(consumptionUc, query);
        long durationMs =
                mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(), rawRealtimeUs,
                        BatteryStats.STATS_SINCE_CHARGED);
        final double powerMah = mPowerEstimator.calculatePower(durationMs);
        double powerMah;
        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
            powerMah = uCtoMah(consumptionUc);
        } else {
            powerMah = mPowerEstimator.calculatePower(durationMs);
        }

        app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
    }
}
+46 −26
Original line number Diff line number Diff line
@@ -36,7 +36,8 @@ import org.junit.runner.RunWith;
public class CameraPowerCalculatorTest {
    private static final double PRECISION = 0.00001;

    private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
    private static final int APP1_UID = Process.FIRST_APPLICATION_UID + 42;
    private static final int APP2_UID = Process.FIRST_APPLICATION_UID + 43;

    @Rule
    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
@@ -47,8 +48,10 @@ public class CameraPowerCalculatorTest {
    public void testTimerBasedModel() {
        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
        synchronized (stats) { // To keep the GuardedBy check happy
            stats.noteCameraOnLocked(APP_UID, 1000, 1000);
            stats.noteCameraOffLocked(APP_UID, 2000, 2000);
            stats.noteCameraOnLocked(APP1_UID, 1000, 1000);
            stats.noteCameraOffLocked(APP1_UID, 2000, 2000);
            stats.noteCameraOnLocked(APP2_UID, 3000, 3000);
            stats.noteCameraOffLocked(APP2_UID, 5000, 5000);
        }

        CameraPowerCalculator calculator =
@@ -56,29 +59,37 @@ public class CameraPowerCalculatorTest {

        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);

        UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
        UidBatteryConsumer app1Consumer = mStatsRule.getUidBatteryConsumer(APP1_UID);
        assertThat(app1Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(1000);
        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
        assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.1);
        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);

        UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
        assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(2000);
        assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.2);
        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);

        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
        assertThat(deviceBatteryConsumer
                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(1000);
                .isEqualTo(3000);
        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.1);
                .isWithin(PRECISION).of(0.3);
        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);

        final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
        assertThat(appsBatteryConsumer
                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(1000);
                .isEqualTo(3000);
        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.1);
                .isWithin(PRECISION).of(0.3);
        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
    }
@@ -87,9 +98,12 @@ public class CameraPowerCalculatorTest {
    public void testEnergyConsumptionBasedModel() {
        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
        synchronized (stats) { // To keep the GuardedBy check happy
            stats.noteCameraOnLocked(APP_UID, 1000, 1000);
            stats.noteCameraOffLocked(APP_UID, 2000, 2000);
            stats.updateCameraEnergyConsumerStatsLocked(720_000, 6000); // 0.72C == 0.2mAh
            stats.noteCameraOnLocked(APP1_UID, 1000, 1000);
            stats.noteCameraOffLocked(APP1_UID, 2000, 2000);
            stats.updateCameraEnergyConsumerStatsLocked(720_000, 2100); // 0.72C == 0.2mAh
            stats.noteCameraOnLocked(APP2_UID, 3000, 3000);
            stats.noteCameraOffLocked(APP2_UID, 5000, 5000);
            stats.updateCameraEnergyConsumerStatsLocked(1_080_000, 5100); // 0.3mAh
        }

        CameraPowerCalculator calculator =
@@ -97,31 +111,37 @@ public class CameraPowerCalculatorTest {

        mStatsRule.apply(calculator);

        UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
        UidBatteryConsumer app1Consumer = mStatsRule.getUidBatteryConsumer(APP1_UID);
        assertThat(app1Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(1000);
        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.1);
        // Per-UID energy consumer model is not implemented yet, so the result will be based on the
        // power profile
        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
        assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.2);
        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);

        UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
        assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(2000);
        assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.3);
        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);

        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
        assertThat(deviceBatteryConsumer
                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(1000);
                .isEqualTo(3000);
        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.2);
                .isWithin(PRECISION).of(0.5);
        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);

        final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
        assertThat(appsBatteryConsumer
                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(1000);
                .isEqualTo(3000);
        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isWithin(PRECISION).of(0.2);
                .isWithin(PRECISION).of(0.5);
        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
    }