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

Commit 2f3abb44 authored by Adam Bookatz's avatar Adam Bookatz
Browse files

BatteryUsageStats atom - frameworks/base

Writes the BatteryUsageStats atoms.proto atoms
based on the current BatteryUsageStats data in BatteryStats.

Does NOT write the past pre-reset snapshot atoms; that is
an adventure for a future cl.

Bug: 184095105
Test: atest BatteryUsageStatsProtoTests
Test: statsd_testdrive <atomId>
Change-Id: I2fc5a983deb58d7d393c0696db2165b124c94dc2
parent c73c0986
Loading
Loading
Loading
Loading
+73 −18
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.os;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.proto.ProtoOutputStream;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -59,27 +61,32 @@ public abstract class BatteryConsumer {
    public static @interface PowerComponent {
    }

    public static final int POWER_COMPONENT_SCREEN = 0;
    public static final int POWER_COMPONENT_CPU = 1;
    public static final int POWER_COMPONENT_BLUETOOTH = 2;
    public static final int POWER_COMPONENT_CAMERA = 3;
    public static final int POWER_COMPONENT_AUDIO = 4;
    public static final int POWER_COMPONENT_VIDEO = 5;
    public static final int POWER_COMPONENT_FLASHLIGHT = 6;
    public static final int POWER_COMPONENT_SYSTEM_SERVICES = 7;
    public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
    public static final int POWER_COMPONENT_SENSORS = 9;
    public static final int POWER_COMPONENT_GNSS = 10;
    public static final int POWER_COMPONENT_WIFI = 11;
    public static final int POWER_COMPONENT_WAKELOCK = 12;
    public static final int POWER_COMPONENT_MEMORY = 13;
    public static final int POWER_COMPONENT_PHONE = 14;
    public static final int POWER_COMPONENT_AMBIENT_DISPLAY = 15;
    public static final int POWER_COMPONENT_IDLE = 16;
    public static final int POWER_COMPONENT_SCREEN = OsProtoEnums.POWER_COMPONENT_SCREEN; // 0
    public static final int POWER_COMPONENT_CPU = OsProtoEnums.POWER_COMPONENT_CPU; // 1
    public static final int POWER_COMPONENT_BLUETOOTH = OsProtoEnums.POWER_COMPONENT_BLUETOOTH; // 2
    public static final int POWER_COMPONENT_CAMERA = OsProtoEnums.POWER_COMPONENT_CAMERA; // 3
    public static final int POWER_COMPONENT_AUDIO = OsProtoEnums.POWER_COMPONENT_AUDIO; // 4
    public static final int POWER_COMPONENT_VIDEO = OsProtoEnums.POWER_COMPONENT_VIDEO; // 5
    public static final int POWER_COMPONENT_FLASHLIGHT =
            OsProtoEnums.POWER_COMPONENT_FLASHLIGHT; // 6
    public static final int POWER_COMPONENT_SYSTEM_SERVICES =
            OsProtoEnums.POWER_COMPONENT_SYSTEM_SERVICES; // 7
    public static final int POWER_COMPONENT_MOBILE_RADIO =
            OsProtoEnums.POWER_COMPONENT_MOBILE_RADIO; // 8
    public static final int POWER_COMPONENT_SENSORS = OsProtoEnums.POWER_COMPONENT_SENSORS; // 9
    public static final int POWER_COMPONENT_GNSS = OsProtoEnums.POWER_COMPONENT_GNSS; // 10
    public static final int POWER_COMPONENT_WIFI = OsProtoEnums.POWER_COMPONENT_WIFI; // 11
    public static final int POWER_COMPONENT_WAKELOCK = OsProtoEnums.POWER_COMPONENT_WAKELOCK; // 12
    public static final int POWER_COMPONENT_MEMORY = OsProtoEnums.POWER_COMPONENT_MEMORY; // 13
    public static final int POWER_COMPONENT_PHONE = OsProtoEnums.POWER_COMPONENT_PHONE; // 14
    public static final int POWER_COMPONENT_AMBIENT_DISPLAY =
            OsProtoEnums.POWER_COMPONENT_AMBIENT_DISPLAY; // 15
    public static final int POWER_COMPONENT_IDLE = OsProtoEnums.POWER_COMPONENT_IDLE; // 16
    // Power that is re-attributed to other battery consumers. For example, for System Server
    // this represents the power attributed to apps requesting system services.
    // The value should be negative or zero.
    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 17;
    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS =
            OsProtoEnums.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS; // 17

    public static final int POWER_COMPONENT_COUNT = 18;

@@ -263,6 +270,54 @@ public abstract class BatteryConsumer {
     */
    public abstract void dump(PrintWriter pw, boolean skipEmptyComponents);

    /** Returns whether there are any atoms.proto BATTERY_CONSUMER_DATA data to write to a proto. */
    boolean hasStatsProtoData() {
        return writeStatsProtoImpl(null, /* Irrelevant fieldId: */ 0);
    }

    /** Writes the atoms.proto BATTERY_CONSUMER_DATA for this BatteryConsumer to the given proto. */
    void writeStatsProto(@NonNull ProtoOutputStream proto, long fieldId) {
        writeStatsProtoImpl(proto, fieldId);
    }

    /**
     * Returns whether there are any atoms.proto BATTERY_CONSUMER_DATA data to write to a proto,
     * and writes it to the given proto if it is non-null.
     */
    private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto, long fieldId) {
        final long totalConsumedPowerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower());

        if (totalConsumedPowerDeciCoulombs == 0) {
            // NOTE: Strictly speaking we should also check !mPowerComponents.hasStatsProtoData().
            // However, that call is a bit expensive (a for loop). And the only way that
            // totalConsumedPower can be 0 while mPowerComponents.hasStatsProtoData() is true is
            // if POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS (which is the only negative
            // allowed) happens to exactly equal the sum of all other components, which
            // can't really happen in practice.
            // So we'll just adopt the rule "if total==0, don't write any details".
            // If negative values are used for other things in the future, this can be revisited.
            return false;
        }
        if (proto == null) {
            // We're just asked whether there is data, not to actually write it. And there is.
            return true;
        }

        final long token = proto.start(fieldId);
        proto.write(
                BatteryUsageStatsAtomsProto.BatteryConsumerData.TOTAL_CONSUMED_POWER_DECI_COULOMBS,
                totalConsumedPowerDeciCoulombs);
        mPowerComponents.writeStatsProto(proto);
        proto.end(token);

        return true;
    }

    /** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
    static long convertMahToDeciCoulombs(double powerMah) {
        return (long) (powerMah * (10 * 3600 / 1000) + 0.5);
    }

    protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
        final PowerComponents.Builder mPowerComponentsBuilder;

+67 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.util.Range;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;

import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
@@ -158,7 +159,8 @@ public final class BatteryUsageStats implements Parcelable {

    /**
     * Portion of battery charge drained since BatteryStats reset (e.g. due to being fully
     * charged), as percentage of the full charge in the range [0:100]
     * charged), as percentage of the full charge in the range [0:100]. May exceed 100 if
     * the device repeatedly charged and discharged prior to the reset.
     */
    public int getDischargePercentage() {
        return mDischargePercentage;
@@ -335,6 +337,70 @@ public final class BatteryUsageStats implements Parcelable {
        }
    };

    /** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */
    public byte[] getStatsProto(long sessionEndTimestampMs) {

        final long sessionStartMillis = getStatsStartTimestamp();
        // TODO(b/187223764): Use the getStatsEndTimestamp() instead, once that is added.
        final long sessionEndMillis = sessionEndTimestampMs;
        final long sessionDurationMillis = sessionEndTimestampMs - getStatsStartTimestamp();

        final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
                AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);

        final int sessionDischargePercentage = getDischargePercentage();

        final ProtoOutputStream proto = new ProtoOutputStream();
        proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, sessionStartMillis);
        proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, sessionEndMillis);
        proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, sessionDurationMillis);
        deviceBatteryConsumer.writeStatsProto(proto,
                BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
        writeUidBatteryConsumersProto(proto);
        proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
                sessionDischargePercentage);
        return proto.getBytes();
    }

    /**
     * Writes the UidBatteryConsumers data, held by this BatteryUsageStats, to the proto (as used
     * for atoms.proto).
     */
    private void writeUidBatteryConsumersProto(ProtoOutputStream proto) {
        final List<UidBatteryConsumer> consumers = getUidBatteryConsumers();

        // TODO: Sort the list by power consumption. If during the for, proto.getRawSize() > 45kb,
        //       truncate the remainder of the list.
        final int size = consumers.size();
        for (int i = 0; i < size; i++) {
            final UidBatteryConsumer consumer = consumers.get(i);

            final long fgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
            final long bgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
            final boolean hasBaseData = consumer.hasStatsProtoData();

            if (fgMs == 0 && bgMs == 0 && !hasBaseData) {
                continue;
            }

            final long token = proto.start(BatteryUsageStatsAtomsProto.UID_BATTERY_CONSUMERS);
            proto.write(
                    BatteryUsageStatsAtomsProto.UidBatteryConsumer.UID,
                    consumer.getUid());
            if (hasBaseData) {
                consumer.writeStatsProto(proto,
                        BatteryUsageStatsAtomsProto.UidBatteryConsumer.BATTERY_CONSUMER_DATA);
            }
            proto.write(
                    BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_FOREGROUND_MILLIS,
                    fgMs);
            proto.write(
                    BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS,
                    bgMs);
            proto.end(token);
        }
    }

    /**
     * Prints the stats in a human-readable format.
     */
+57 −0
Original line number Diff line number Diff line
@@ -15,7 +15,11 @@
 */
package android.os;

import static android.os.BatteryConsumer.convertMahToDeciCoulombs;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.proto.ProtoOutputStream;

import com.android.internal.os.PowerCalculator;

@@ -237,6 +241,59 @@ class PowerComponents {
        }
    }

    /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
    boolean hasStatsProtoData() {
        return writeStatsProtoImpl(null);
    }

    /** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */
    void writeStatsProto(@NonNull ProtoOutputStream proto) {
        writeStatsProtoImpl(proto);
    }

    /**
     * Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto,
     * and writes it to the given proto if it is non-null.
     */
    private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
        boolean interestingData = false;

        for (int idx = 0; idx < mPowerComponentsMah.length; idx++) {
            final int componentId = idx < BatteryConsumer.POWER_COMPONENT_COUNT ?
                    idx : idx - CUSTOM_POWER_COMPONENT_OFFSET;
            final long powerDeciCoulombs = convertMahToDeciCoulombs(mPowerComponentsMah[idx]);
            final long durationMs = mUsageDurationsMs[idx];

            if (powerDeciCoulombs == 0 && durationMs == 0) {
                // No interesting data. Make sure not to even write the COMPONENT int.
                continue;
            }

            interestingData = true;
            if (proto == null) {
                // We're just asked whether there is data, not to actually write it. And there is.
                return true;
            }

            final long token =
                    proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS);
            proto.write(
                    BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
                            .COMPONENT,
                    componentId);
            proto.write(
                    BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
                            .POWER_DECI_COULOMBS,
                    powerDeciCoulombs);
            proto.write(
                    BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
                            .DURATION_MILLIS,
                    durationMs);
            proto.end(token);
        }
        return interestingData;
    }

    /**
     * Builder for PowerComponents.
     */
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.
 */

syntax = "proto2";
package android.os;

option java_multiple_files = true;

import "frameworks/proto_logging/stats/enums/os/enums.proto";

// This message is used for statsd logging and should be kept in sync with
// frameworks/proto_logging/stats/atoms.proto
/**
 * Represents a device's BatteryUsageStats, with power usage information about the device
 * and each app.
 */
message BatteryUsageStatsAtomsProto {

    // The session start timestamp in UTC milliseconds since January 1, 1970, per Date#getTime().
    // All data is no older than this time.
    optional int64 session_start_millis = 1;

    // The session end timestamp in UTC milliseconds since January 1, 1970, per Date#getTime().
    // All data is no more recent than this time.
    optional int64 session_end_millis = 2;

    // Length that the reported data covered. This usually will be equal to the entire session,
    // session_end_millis - session_start_millis, but may not be if some data during this time frame
    // is missing.
    optional int64 session_duration_millis = 3;

    // Represents usage of a consumer, storing all of its power component usage.
    message BatteryConsumerData {
        // Total power consumed by this BatteryConsumer (including all of its PowerComponents).
        // May not equal the sum of the PowerComponentUsage due to under- or over-estimations.
        // Multiply by 1/36 to obtain mAh.
        optional int64 total_consumed_power_deci_coulombs = 1;

        // Represents power and time usage of a particular power component.
        message PowerComponentUsage {
            // Holds android.os.PowerComponentEnum, or custom component value between 1000 and 9999.
            // Evidently, if one attempts to write an int to an enum field that is out of range, it
            // is treated as 0, so we must make this an int32.
            optional int32 component = 1;

            // Power consumed by this component. Multiply by 1/36 to obtain mAh.
            optional int64 power_deci_coulombs = 2;

            optional int64 duration_millis = 3;
        }
        repeated PowerComponentUsage power_components = 2;
    }

    // Total power usage for the device during this session.
    optional BatteryConsumerData device_battery_consumer = 4;

    // Power usage by a uid during this session.
    message UidBatteryConsumer {
        optional int32 uid = 1;
        optional BatteryConsumerData battery_consumer_data = 2;
        optional int64 time_in_foreground_millis = 3;
        optional int64 time_in_background_millis = 4;
    }
    repeated UidBatteryConsumer uid_battery_consumers = 5;

    // Sum of all discharge percentage point drops during the reported session.
    optional int32 session_discharge_percentage = 6;
}
 No newline at end of file
+28 −0
Original line number Diff line number Diff line
package {
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
    // all of the 'license_kinds' from "frameworks_base_license"
    // to get the below license kinds:
    //   SPDX-license-identifier-Apache-2.0
    default_applicable_licenses: ["frameworks_base_license"],
}

android_test {
    name: "BatteryUsageStatsProtoTests",
    srcs: ["src/**/*.java"],

    static_libs: [
        "androidx.test.rules",
        "junit",
        "platform-test-annotations",
        "platformprotosnano",
        "statsdprotolite",
    ],

    libs: ["android.test.runner"],

    platform_apis: true,
    certificate: "platform",

    test_suites: ["device-tests"],
}
Loading