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

Commit 478d1436 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Make PowerStats parcelable

Bug: 285646152
Test: atest FrameworksCoreTests:BatteryStatsTests
Change-Id: I6fb550e7a3c00ad771a5da97c4d5be36366c5efe
parent 5e309ba6
Loading
Loading
Loading
Loading
+206 −4
Original line number Original line Diff line number Diff line
@@ -16,8 +16,13 @@


package com.android.internal.os;
package com.android.internal.os;


import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryConsumer;
import android.os.Bundle;
import android.os.Parcel;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseArray;


import java.util.Arrays;
import java.util.Arrays;
@@ -27,10 +32,138 @@ import java.util.Arrays;
 * details.
 * details.
 */
 */
public final class PowerStats {
public final class PowerStats {
    private static final String TAG = "PowerStats";

    private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
            new BatteryStatsHistory.VarintParceler();
    private static final byte PARCEL_FORMAT_VERSION = 1;

    private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
    private static final int PARCEL_FORMAT_VERSION_SHIFT =
            Integer.numberOfTrailingZeros(PARCEL_FORMAT_VERSION_MASK);
    private static final int STATS_ARRAY_LENGTH_MASK = 0x0000FF00;
    private static final int STATS_ARRAY_LENGTH_SHIFT =
            Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
    public static final int MAX_STATS_ARRAY_LENGTH =
            2 ^ Integer.bitCount(STATS_ARRAY_LENGTH_MASK) - 1;
    private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
    private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
            Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
    public static final int MAX_UID_STATS_ARRAY_LENGTH =
            (2 ^ Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;

    /**
     * Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
     * This descriptor is used for storing PowerStats and can also be used by power models
     * to adjust the algorithm in accordance with the stats available on the device.
     */
    public static class Descriptor {
        /**
         * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
         * to; or a custom power component ID (if the value
         * is >= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
         */
        public final int powerComponentId;
        public final String name;

        public final int statsArrayLength;
        public final int uidStatsArrayLength;

        /**
         * Extra parameters specific to the power component, e.g. the availability of power
         * monitors.
         */
        public final Bundle extras;

        public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
                int statsArrayLength, int uidStatsArrayLength, @NonNull Bundle extras) {
            this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
                    statsArrayLength, uidStatsArrayLength, extras);
        }

        public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
                int uidStatsArrayLength, Bundle extras) {
            if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
                throw new IllegalArgumentException(
                        "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
            }
            if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
                throw new IllegalArgumentException(
                        "uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
            }
            this.powerComponentId = customPowerComponentId;
            this.name = name;
            this.statsArrayLength = statsArrayLength;
            this.uidStatsArrayLength = uidStatsArrayLength;
            this.extras = extras;
        }

        /**
         * Writes the Descriptor into the parcel.
         */
        public void writeSummaryToParcel(Parcel parcel) {
            int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
                    & PARCEL_FORMAT_VERSION_MASK)
                    | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
                    & STATS_ARRAY_LENGTH_MASK)
                    | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
                    & UID_STATS_ARRAY_LENGTH_MASK);
            parcel.writeInt(firstWord);
            parcel.writeInt(powerComponentId);
            parcel.writeString(name);
            extras.writeToParcel(parcel, 0);
        }

        /**
         * Reads a Descriptor from the parcel.  If the parcel has an incompatible format,
         * returns null.
         */
        @Nullable
        public static Descriptor readSummaryFromParcel(Parcel parcel) {
            int firstWord = parcel.readInt();
            int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
            if (version != PARCEL_FORMAT_VERSION) {
                Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
                        + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
                return null;
            }
            int statsArrayLength =
                    (firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
            int uidStatsArrayLength =
                    (firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
            int powerComponentId = parcel.readInt();
            String name = parcel.readString();
            Bundle extras = parcel.readBundle(PowerStats.class.getClassLoader());
            return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
                    extras);
        }
    }

    /**
     * A registry for all supported power component types (e.g. CPU, WiFi).
     */
    public static class DescriptorRegistry {
        private final SparseArray<Descriptor> mDescriptors = new SparseArray<>();

        /**
        /**
     * Power component (e.g. CPU, WIFI etc) that this snapshot relates to.
         * Adds the specified descriptor to the registry. If the registry already
         * contained a descriptor for the same power component, then the new one replaces
         * the old one.
         */
         */
    public @BatteryConsumer.PowerComponent int powerComponentId;
        public void register(Descriptor descriptor) {
            mDescriptors.put(descriptor.powerComponentId, descriptor);
        }

        /**
         * @param powerComponentId either a BatteryConsumer.PowerComponent or a custom power
         *                         component ID
         */
        public Descriptor get(int powerComponentId) {
            return mDescriptors.get(powerComponentId);
        }
    }

    public final Descriptor descriptor;


    /**
    /**
     * Duration, in milliseconds, covered by this snapshot.
     * Duration, in milliseconds, covered by this snapshot.
@@ -47,12 +180,81 @@ public final class PowerStats {
     */
     */
    public final SparseArray<long[]> uidStats = new SparseArray<>();
    public final SparseArray<long[]> uidStats = new SparseArray<>();


    public PowerStats(Descriptor descriptor) {
        this.descriptor = descriptor;
        stats = new long[descriptor.statsArrayLength];
    }

    /**
     * Writes the object into the parcel.
     */
    public void writeToParcel(Parcel parcel) {
        int lengthPos = parcel.dataPosition();
        parcel.writeInt(0);     // Placeholder for length

        int startPos = parcel.dataPosition();
        parcel.writeInt(descriptor.powerComponentId);
        parcel.writeLong(durationMs);
        VARINT_PARCELER.writeLongArray(parcel, stats);
        parcel.writeInt(uidStats.size());
        for (int i = 0; i < uidStats.size(); i++) {
            parcel.writeInt(uidStats.keyAt(i));
            VARINT_PARCELER.writeLongArray(parcel, uidStats.valueAt(i));
        }

        int endPos = parcel.dataPosition();
        parcel.setDataPosition(lengthPos);
        parcel.writeInt(endPos - startPos);
        parcel.setDataPosition(endPos);
    }

    /**
     * Reads a PowerStats object from the supplied Parcel. If the parcel has an incompatible
     * format, returns null.
     */
    @Nullable
    public static PowerStats readFromParcel(Parcel parcel, DescriptorRegistry registry) {
        int length = parcel.readInt();
        int startPos = parcel.dataPosition();
        int endPos = startPos + length;

        try {
            int powerComponentId = parcel.readInt();

            Descriptor descriptor = registry.get(powerComponentId);
            if (descriptor == null) {
                Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
                return null;
            }
            PowerStats stats = new PowerStats(descriptor);
            stats.durationMs = parcel.readLong();
            stats.stats = new long[descriptor.statsArrayLength];
            VARINT_PARCELER.readLongArray(parcel, stats.stats);
            int uidCount = parcel.readInt();
            for (int i = 0; i < uidCount; i++) {
                int uid = parcel.readInt();
                long[] uidStats = new long[descriptor.uidStatsArrayLength];
                VARINT_PARCELER.readLongArray(parcel, uidStats);
                stats.uidStats.put(uid, uidStats);
            }
            if (parcel.dataPosition() != endPos) {
                Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
                        + ", actual length: " + (parcel.dataPosition() - startPos));
                return null;
            }
            return stats;
        } finally {
            // Unconditionally skip to the end of the written data, even if the actual parcel
            // format is incompatible
            parcel.setDataPosition(endPos);
        }
    }

    /**
    /**
     * Prints the contents of the stats snapshot.
     * Prints the contents of the stats snapshot.
     */
     */
    public void dump(IndentingPrintWriter pw) {
    public void dump(IndentingPrintWriter pw) {
        pw.print("PowerStats: ");
        pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
        pw.println(BatteryConsumer.powerComponentIdToString(powerComponentId));
        pw.increaseIndent();
        pw.increaseIndent();
        pw.print("duration", durationMs).println();
        pw.print("duration", durationMs).println();
        for (int i = 0; i < uidStats.size(); i++) {
        for (int i = 0; i < uidStats.size(); i++) {
+2 −1
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import org.junit.runners.Suite;
        LongArrayMultiStateCounterTest.class,
        LongArrayMultiStateCounterTest.class,
        LongMultiStateCounterTest.class,
        LongMultiStateCounterTest.class,
        PowerProfileTest.class,
        PowerProfileTest.class,
        PowerStatsTest.class,


        EnergyConsumerStatsTest.class
        EnergyConsumerStatsTest.class
    })
    })
+132 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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 static com.google.common.truth.Truth.assertThat;

import android.os.BatteryConsumer;
import android.os.Bundle;
import android.os.Parcel;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class PowerStatsTest {

    private PowerStats.DescriptorRegistry mRegistry;
    private PowerStats.Descriptor mDescriptor;

    @Before
    public void setup() {
        mRegistry = new PowerStats.DescriptorRegistry();
        Bundle extras = new Bundle();
        extras.putBoolean("hasPowerMonitor", true);
        mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, 2, extras);
        mRegistry.register(mDescriptor);
    }

    @Test
    public void parceling_compatibleParcel() {
        PowerStats stats = new PowerStats(mDescriptor);
        stats.durationMs = 1234;
        stats.stats[0] = 10;
        stats.stats[1] = 20;
        stats.stats[2] = 30;
        stats.uidStats.put(42, new long[] {40, 50});
        stats.uidStats.put(99, new long[] {60, 70});

        Parcel parcel = Parcel.obtain();
        mDescriptor.writeSummaryToParcel(parcel);
        stats.writeToParcel(parcel);
        parcel.writeString("END");

        Parcel newParcel = marshallAndUnmarshall(parcel);

        PowerStats.Descriptor newDescriptor =
                PowerStats.Descriptor.readSummaryFromParcel(newParcel);
        assertThat(newDescriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
        assertThat(newDescriptor.name).isEqualTo("cpu");
        assertThat(newDescriptor.statsArrayLength).isEqualTo(3);
        assertThat(newDescriptor.uidStatsArrayLength).isEqualTo(2);
        assertThat(newDescriptor.extras.getBoolean("hasPowerMonitor")).isTrue();

        mRegistry.register(newDescriptor);

        PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
        assertThat(newStats.durationMs).isEqualTo(1234);
        assertThat(newStats.stats).isEqualTo(new long[] {10, 20, 30});
        assertThat(newStats.uidStats.size()).isEqualTo(2);
        assertThat(newStats.uidStats.get(42)).isEqualTo(new long[] {40, 50});
        assertThat(newStats.uidStats.get(99)).isEqualTo(new long[] {60, 70});

        String end = newParcel.readString();
        assertThat(end).isEqualTo("END");
    }

    @Test
    public void parceling_unrecognizedPowerComponent() {
        PowerStats stats = new PowerStats(
                new PowerStats.Descriptor(777, "luck", 3, 2, new Bundle()));
        stats.durationMs = 1234;

        Parcel parcel = Parcel.obtain();
        stats.writeToParcel(parcel);
        parcel.writeString("END");

        Parcel newParcel = marshallAndUnmarshall(parcel);

        PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
        assertThat(newStats).isNull();

        String end = newParcel.readString();
        assertThat(end).isEqualTo("END");
    }

    @Test
    public void parceling_wrongArrayLength() {
        PowerStats stats = new PowerStats(mDescriptor);
        stats.stats = new long[5];      // Is expected to be 3

        Parcel parcel = Parcel.obtain();
        stats.writeToParcel(parcel);
        parcel.writeString("END");

        Parcel newParcel = marshallAndUnmarshall(parcel);

        PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
        assertThat(newStats).isNull();

        String end = newParcel.readString();
        assertThat(end).isEqualTo("END");
    }

    private static Parcel marshallAndUnmarshall(Parcel parcel) {
        byte[] bytes = parcel.marshall();
        parcel.recycle();

        Parcel newParcel = Parcel.obtain();
        newParcel.unmarshall(bytes, 0, bytes.length);
        newParcel.setDataPosition(0);
        return newParcel;
    }
}
+7 −1
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package com.android.server.power.stats;
package com.android.server.power.stats;


import android.os.BatteryConsumer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.util.SparseArray;
import android.util.SparseArray;


@@ -42,7 +44,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
    private final SparseArray<UidStats> mUidStats = new SparseArray<>();
    private final SparseArray<UidStats> mUidStats = new SparseArray<>();
    private final int mUidStatsSize;
    private final int mUidStatsSize;
    // Reusable instance
    // Reusable instance
    private final PowerStats mCpuPowerStats = new PowerStats();
    private final PowerStats mCpuPowerStats;
    private long mLastUpdateTimestampNanos;
    private long mLastUpdateTimestampNanos;


    public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
    public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
@@ -69,6 +71,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
        }
        }
        mUidStatsSize = powerProfile.getCpuPowerBracketCount();
        mUidStatsSize = powerProfile.getCpuPowerBracketCount();
        mTempUidStats = new long[mUidStatsSize];
        mTempUidStats = new long[mUidStatsSize];

        mCpuPowerStats = new PowerStats(
                new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize,
                        new Bundle()));
    }
    }


    /**
    /**
+1 −0
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ android_test {
        "androidx.test.uiautomator_uiautomator",
        "androidx.test.uiautomator_uiautomator",
        "mockito-target-minus-junit4",
        "mockito-target-minus-junit4",
        "servicestests-utils",
        "servicestests-utils",
        "platform-test-annotations",
        "flag-junit",
        "flag-junit",
    ],
    ],


Loading