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

Commit 822ed39c authored by Adam Bookatz's avatar Adam Bookatz
Browse files

BatteryStats receives custom measured energies

EnergyConsumer data of type OTHER is now pulled and
processed by BatteryExternalStatsWorker.
This means that 'custom energy buckets' energy data is
now available.
It is pulled by BESW, stored/snapshotted in
MeasuredEnergySnapshot, and deltas are sent to
BatteryStatsImpl, which stores them (depending on its
internal state).

MeasuredEnergyArray is removed, in favour of using
EnergyConsumerResults[] directly.

Bug: 176988041
Bug: 174818228
Bug: 179107328
Test: atest BatteryStatsNoteTest
Test: atest FrameworksServicesTests:com.android.server.am.MeasuredEnergySnapshotTest
Test: atest FrameworksCoreTests:com.android.internal.power.MeasuredEnergyStatsTest
Change-Id: Ib6a88d1579f97bc653cdb67a671f4fdd05f1dfe5
parent 6ed22e53
Loading
Loading
Loading
Loading
+73 −0
Original line number Diff line number Diff line
@@ -7172,6 +7172,20 @@ public class BatteryStatsImpl extends BatteryStats {
                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
    }
    /**
     * Returns the energy in microjoules that the given custom energy bucket consumed.
     * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
     *
     * @param customEnergyBucket custom energy bucket of interest
     * @return energy (in microjoules) used by this uid for this energy bucket
     */
    public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) {
        if (mGlobalMeasuredEnergyStats == null) {
            return ENERGY_DATA_UNAVAILABLE;
        }
        return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket);
    }
    @Override public long getStartClockTime() {
        final long currentTimeMs = System.currentTimeMillis();
        if ((currentTimeMs > MILLISECONDS_IN_YEAR
@@ -7940,6 +7954,13 @@ public class BatteryStatsImpl extends BatteryStats {
                    .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
        }
        /** Adds the given energy to the given custom energy bucket for this uid. */
        private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
                boolean accumulate) {
            getOrCreateMeasuredEnergyStatsLocked()
                    .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
        }
        /**
         * Returns the energy used by this uid for a standard energy bucket of interest.
         * @param bucket standard energy bucket of interest
@@ -7956,6 +7977,22 @@ public class BatteryStatsImpl extends BatteryStats {
            return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
        }
        /**
         * Returns the energy used by this uid for a custom energy bucket of interest.
         * @param customEnergyBucket custom energy bucket of interest
         * @return energy (in microjoules) used by this uid for this energy bucket
         */
        public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) {
            if (mBsi.mGlobalMeasuredEnergyStats == null
                    || !mBsi.mGlobalMeasuredEnergyStats.isValidCustomBucket(customEnergyBucket)) {
                return ENERGY_DATA_UNAVAILABLE;
            }
            if (mUidMeasuredEnergyStats == null) {
                return 0L; // It is supported, but was never filled, so it must be 0
            }
            return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket);
        }
        /**
         * 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.
@@ -12459,6 +12496,42 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    /**
     * Accumulate Custom energy bucket energy, globally and for each app.
     *
     * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
     * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
     *                    Data inside uidEnergies will not be modified (treated immutable).
     */
    public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
            long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
        if (DEBUG_ENERGY) {
            Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
                    + customEnergyBucket
                    + " with total energy " + totalEnergyUJ
                    + " and uid energies " + String.valueOf(uidEnergies));
        }
        if (mGlobalMeasuredEnergyStats == null) return;
        if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
        mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
        if (uidEnergies == null) return;
        final int numUids = uidEnergies.size();
        for (int i = 0; i < numUids; i++) {
            final int uidInt = mapUid(uidEnergies.keyAt(i));
            final long uidEnergyUJ = uidEnergies.valueAt(i);
            if (uidEnergyUJ == 0) continue;
            // TODO: Worry about uids not in BSI currently, including uninstalled uids 'coming back'
            //  Specifically: What if the uid had been removed? We'll re-create it now.
            //  And if we instead use getAvailableUidStatsLocked() and chec for null, then we might
            //  not create a Uid even when we should be (say, the app's first event, somehow, was to
            //  use GPU). I guess that CPU/kernel data might already have this problem?
            final Uid uidObj = getUidStatsLocked(uidInt);
            uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
        }
    }
    /**
     * Read and record Rail Energy data.
     */
+0 −66
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.power;


import android.annotation.IntDef;

import com.android.internal.os.RailStats;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Interface to provide subsystem energy data.
 * TODO: replace this and {@link RailStats} once b/173077356 is done
 */
public interface MeasuredEnergyArray {
    int SUBSYSTEM_UNKNOWN = -1;
    int SUBSYSTEM_DISPLAY = 0;
    int NUMBER_SUBSYSTEMS = 1;
    String[] SUBSYSTEM_NAMES = {"display"};


    @IntDef(prefix = { "SUBSYSTEM_" }, value = {
            SUBSYSTEM_UNKNOWN,
            SUBSYSTEM_DISPLAY,
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface MeasuredEnergySubsystem {}

    /**
     * Get the subsystem at an index in array.
     *
     * @param index into the array.
     * @return subsystem.
     */
    @MeasuredEnergySubsystem
    int getSubsystem(int index);

    /**
     * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
     *
     * @param index into the array.
     * @return energy (in microjoules) consumed since boot.
     */
    long getEnergy(int index);

    /**
     * Return number of subsystems in the array.
     */
    int size();
}
+3 −3
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.util.Slog;
import android.view.Display;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -247,7 +246,7 @@ public class MeasuredEnergyStats {
    }

    /**
     * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
     * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
     */
    public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
        if (Display.isOnState(screenState)) {
@@ -450,7 +449,8 @@ public class MeasuredEnergyStats {
        return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
    }

    private boolean isValidCustomBucket(int customBucket) {
    /** Returns whether the given custom bucket is valid (exists) on this device. */
    public boolean isValidCustomBucket(int customBucket) {
        return customBucket >= 0
                && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
    }
+115 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
import android.os.WorkSource;
import android.util.SparseLongArray;
import android.view.Display;

import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@ public class BatteryStatsNoteTest extends TestCase {
        checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
    }

    @SmallTest
    public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
        bi.setOnBatteryInternal(true);

        final int uid1 = 11500;
        final int uid2 = 11501;

        // Initially, all custom buckets report energy of 0.
        checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
    }

    @SmallTest
    public void testUpdateCustomMeasuredEnergyDataLocked() {
        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);

        final int bucketA = 0; // Custom bucket 0
        final int bucketB = 1; // Custom bucket 1

        long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
        long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)

        final int uid1 = 10500;
        long blame1A = 0; // Blame for uid1 in bucketA
        long blame1B = 0; // Blame for uid1 in bucketB

        final int uid2 = 10501;
        long blame2A = 0; // Blame for uid2 in bucketA
        long blame2B = 0; // Blame for uid2 in bucketB

        final SparseLongArray newEnergiesA = new SparseLongArray(2);
        final SparseLongArray newEnergiesB = new SparseLongArray(2);


        // ----- Case A: battery off (so blame does not increase)
        bi.setOnBatteryInternal(false);

        newEnergiesA.put(uid1, 20_000);
        // Implicit newEnergiesA.put(uid2, 0);
        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);

        newEnergiesB.put(uid1, 60_000);
        // Implicit newEnergiesB.put(uid2, 0);
        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);

        checkCustomMeasuredEnergy(
                "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);


        // ----- Case B: battery on
        bi.setOnBatteryInternal(true);

        newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
        // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
        totalBlameA += 310_000;

        newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
        newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
        totalBlameB += 790_000;

        checkCustomMeasuredEnergy(
                "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);


        // ----- Case C: battery still on
        newEnergiesA.delete(uid1); blame1A += 0;
        newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
        totalBlameA += 560_000;

        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
        totalBlameB += 10_000;

        checkCustomMeasuredEnergy(
                "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);


        // ----- Case D: battery still on
        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
        totalBlameB += 15_000;
        checkCustomMeasuredEnergy(
                "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
    }

    private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
        // Note that noteUidProcessStateLocked uses ActivityManager process states.
        if (fgOn) {
@@ -610,4 +700,29 @@ public class BatteryStatsNoteTest extends TestCase {
        assertEquals("Wrong doze for Case " + caseName, globalDoze,
                bi.getScreenDozeEnergy());
    }

    private void checkCustomMeasuredEnergy(String caseName,
            long totalBlameA, long totalBlameB,
            int uid1, long blame1A, long blame1B,
            int uid2, long blame2A, long blame2B,
            MockBatteryStatsImpl bi) {

        assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
                bi.getCustomMeasuredEnergyMicroJoules(0));

        assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
                bi.getCustomMeasuredEnergyMicroJoules(1));

        assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A,
                bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(0));

        assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B,
                bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(1));

        assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A,
                bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(0));

        assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B,
                bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(1));
    }
}
+18 −0
Original line number Diff line number Diff line
@@ -386,6 +386,24 @@ public class MeasuredEnergyStatsTest {
        assertEquals(60, stats.getAccumulatedCustomBucketEnergy(1));
    }

    @Test
    public void testIsValidCustomBucket() {
        final MeasuredEnergyStats stats
                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
        assertFalse(stats.isValidCustomBucket(-1));
        assertTrue(stats.isValidCustomBucket(0));
        assertTrue(stats.isValidCustomBucket(1));
        assertTrue(stats.isValidCustomBucket(2));
        assertFalse(stats.isValidCustomBucket(3));
        assertFalse(stats.isValidCustomBucket(4));

        final MeasuredEnergyStats boringStats
                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
        assertFalse(boringStats.isValidCustomBucket(-1));
        assertFalse(boringStats.isValidCustomBucket(0));
        assertFalse(boringStats.isValidCustomBucket(1));
    }

    @Test
    public void testReset() {
        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
Loading