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

Commit 6569c1ed authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Improve power stats formatting

Instead of manually implementing the dump/toString logic
for different power components, use a generic mechanism.

Bug: 323970018
Test: atest PowerStatsTestsRavenwood && atest PowerStatsTests
Flag: com.android.server.power.optimization.streamlined_connectivity_battery_stats
Change-Id: I12f19f3c772a04b4cd6def0a4af1cf919cdc57d4
parent 14513965
Loading
Loading
Loading
Loading
+186 −13
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Parcel;
import android.os.PersistableBundle;
@@ -34,8 +35,12 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
@@ -75,6 +80,10 @@ public final class PowerStats {
     */
    @android.ravenwood.annotation.RavenwoodKeepWholeClass
    public static class Descriptor {
        public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
        public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
        public static final String EXTRA_UID_STATS_FORMAT = "format-uid";

        public static final String XML_TAG_DESCRIPTOR = "descriptor";
        private static final String XML_ATTR_ID = "id";
        private static final String XML_ATTR_NAME = "name";
@@ -120,6 +129,10 @@ public final class PowerStats {
         */
        public final PersistableBundle extras;

        private PowerStatsFormatter mDeviceStatsFormatter;
        private PowerStatsFormatter mStateStatsFormatter;
        private PowerStatsFormatter mUidStatsFormatter;

        public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
                int statsArrayLength, @Nullable SparseArray<String> stateLabels,
                int stateStatsArrayLength, int uidStatsArrayLength,
@@ -131,7 +144,7 @@ public final class PowerStats {

        public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
                @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
                int uidStatsArrayLength, PersistableBundle extras) {
                int uidStatsArrayLength, @NonNull PersistableBundle extras) {
            if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
                throw new IllegalArgumentException(
                        "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
@@ -153,6 +166,39 @@ public final class PowerStats {
            this.extras = extras;
        }

        /**
         * Returns a custom formatter for this type of power stats.
         */
        public PowerStatsFormatter getDeviceStatsFormatter() {
            if (mDeviceStatsFormatter == null) {
                mDeviceStatsFormatter = new PowerStatsFormatter(
                        extras.getString(EXTRA_DEVICE_STATS_FORMAT));
            }
            return mDeviceStatsFormatter;
        }

        /**
         * Returns a custom formatter for this type of power stats, specifically per-state stats.
         */
        public PowerStatsFormatter getStateStatsFormatter() {
            if (mStateStatsFormatter == null) {
                mStateStatsFormatter = new PowerStatsFormatter(
                        extras.getString(EXTRA_STATE_STATS_FORMAT));
            }
            return mStateStatsFormatter;
        }

        /**
         * Returns a custom formatter for this type of power stats, specifically per-UID stats.
         */
        public PowerStatsFormatter getUidStatsFormatter() {
            if (mUidStatsFormatter == null) {
                mUidStatsFormatter = new PowerStatsFormatter(
                        extras.getString(EXTRA_UID_STATS_FORMAT));
            }
            return mUidStatsFormatter;
        }

        /**
         * Returns the label associated with the give state key, e.g. "5G-high" for the
         * state of Mobile Radio representing the 5G mode and high signal power.
@@ -491,20 +537,22 @@ public final class PowerStats {
        StringBuilder sb = new StringBuilder();
        sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
        if (stats.length > 0) {
            sb.append("=").append(Arrays.toString(stats));
            sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
        }
        if (descriptor.stateStatsArrayLength != 0) {
            PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
            for (int i = 0; i < stateStats.size(); i++) {
                sb.append(" [");
                sb.append(" (");
                sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
                sb.append("]=");
                sb.append(Arrays.toString(stateStats.valueAt(i)));
                sb.append(") ");
                sb.append(formatter.format(stateStats.valueAt(i)));
            }
        }
        PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
        for (int i = 0; i < uidStats.size(); i++) {
            sb.append(uidPrefix)
                    .append(UserHandle.formatUid(uidStats.keyAt(i)))
                    .append(": ").append(Arrays.toString(uidStats.valueAt(i)));
                    .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
        }
        return sb.toString();
    }
@@ -513,26 +561,29 @@ public final class PowerStats {
     * Prints the contents of the stats snapshot.
     */
    public void dump(IndentingPrintWriter pw) {
        pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
        pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
        pw.increaseIndent();
        pw.print("duration", durationMs).println();

        if (descriptor.statsArrayLength != 0) {
            pw.print("stats", Arrays.toString(stats)).println();
            pw.println(descriptor.getDeviceStatsFormatter().format(stats));
        }
        if (descriptor.stateStatsArrayLength != 0) {
            PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
            for (int i = 0; i < stateStats.size(); i++) {
                pw.print("state ");
                pw.print(" (");
                pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
                pw.print(": ");
                pw.print(Arrays.toString(stateStats.valueAt(i)));
                pw.print(") ");
                pw.print(formatter.format(stateStats.valueAt(i)));
                pw.println();
            }
        }
        PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
        for (int i = 0; i < uidStats.size(); i++) {
            pw.print("UID ");
            pw.print(uidStats.keyAt(i));
            pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
            pw.print(": ");
            pw.print(Arrays.toString(uidStats.valueAt(i)));
            pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
            pw.println();
        }
        pw.decreaseIndent();
@@ -542,4 +593,126 @@ public final class PowerStats {
    public String toString() {
        return "PowerStats: " + formatForBatteryHistory(" UID ");
    }

    public static class PowerStatsFormatter {
        private static class Section {
            public String label;
            public int position;
            public int length;
            public boolean optional;
            public boolean typePower;
        }

        private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
        private static final Pattern SECTION_PATTERN =
                Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
        private final List<Section> mSections;

        public PowerStatsFormatter(String format) {
            mSections = parseFormat(format);
        }

        /**
         * Produces a formatted string representing the supplied array, with labels
         * and other adornments specific to the power stats layout.
         */
        public String format(long[] stats) {
            return format(mSections, stats);
        }

        private List<Section> parseFormat(String format) {
            if (format == null || format.isBlank()) {
                return null;
            }

            ArrayList<Section> sections = new ArrayList<>();
            Matcher matcher = SECTION_PATTERN.matcher(format);
            for (int position = 0; position < format.length(); position = matcher.end()) {
                if (!matcher.find() || matcher.start() != position) {
                    Slog.wtf(TAG, "Bad power stats format '" + format + "'");
                    return null;
                }
                Section section = new Section();
                section.label = matcher.group(1);
                section.position = Integer.parseUnsignedInt(matcher.group(2));
                String length = matcher.group("L");
                if (length != null) {
                    section.length = Integer.parseUnsignedInt(length);
                } else {
                    section.length = 1;
                }
                String flags = matcher.group("F");
                if (flags != null) {
                    for (int i = 0; i < flags.length(); i++) {
                        char flag = flags.charAt(i);
                        switch (flag) {
                            case '?':
                                section.optional = true;
                                break;
                            case 'p':
                                section.typePower = true;
                                break;
                            default:
                                Slog.e(TAG,
                                        "Unsupported format option '" + flag + "' in " + format);
                                break;
                        }
                    }
                }
                sections.add(section);
            }

            return sections;
        }

        private String format(List<Section> sections, long[] stats) {
            if (sections == null) {
                return Arrays.toString(stats);
            }

            StringBuilder sb = new StringBuilder();
            for (int i = 0, count = sections.size(); i < count; i++) {
                Section section = sections.get(i);
                if (section.length == 0) {
                    continue;
                }

                if (section.optional) {
                    boolean nonZero = false;
                    for (int offset = 0; offset < section.length; offset++) {
                        if (stats[section.position + offset] != 0) {
                            nonZero = true;
                            break;
                        }
                    }
                    if (!nonZero) {
                        continue;
                    }
                }

                if (!sb.isEmpty()) {
                    sb.append(' ');
                }
                sb.append(section.label).append(": ");
                if (section.length != 1) {
                    sb.append('[');
                }
                for (int offset = 0; offset < section.length; offset++) {
                    if (offset != 0) {
                        sb.append(", ");
                    }
                    if (section.typePower) {
                        sb.append(BatteryStats.formatCharge(
                                stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
                    } else {
                        sb.append(stats[section.position + offset]);
                    }
                }
                if (section.length != 1) {
                    sb.append(']');
                }
            }
            return sb.toString();
        }
    }
}
+71 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.os.BatteryConsumer;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.util.Xml;

@@ -31,6 +32,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import com.google.common.truth.StringSubject;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -38,6 +41,7 @@ import org.junit.runner.RunWith;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;

@RunWith(AndroidJUnit4.class)
@@ -56,6 +60,9 @@ public class PowerStatsTest {
        extras.putBoolean("hasPowerMonitor", true);
        SparseArray<String> stateLabels = new SparseArray<>();
        stateLabels.put(0x0F, "idle");
        extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, "device:0[3]");
        extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, "state:0");
        extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, "a:0 b:1");
        mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels,
                1, 2, extras);
        mRegistry.register(mDescriptor);
@@ -191,4 +198,68 @@ public class PowerStatsTest {
        newParcel.setDataPosition(0);
        return newParcel;
    }

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

        assertThat(stats.formatForBatteryHistory(" #"))
                .isEqualTo("duration=1234 cpu="
                        + "device: [10, 20, 30]"
                        + " (idle) state: 16"
                        + " (cpu-f0) state: 17"
                        + " #42: a: 40 b: 50"
                        + " #99: a: 60 b: 70");
    }

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

        StringWriter sw = new StringWriter();
        IndentingPrintWriter pw = new IndentingPrintWriter(sw);
        stats.dump(pw);
        pw.flush();
        String dump = sw.toString();

        assertThat(dump).contains("duration=1234");
        assertThat(dump).contains("device: [10, 20, 30]");
        assertThat(dump).contains("(idle) state: 16");
        assertThat(dump).contains("(cpu-f0) state: 17");
        assertThat(dump).contains("UID 42: a: 40 b: 50");
        assertThat(dump).contains("UID 99: a: 60 b: 70");
    }

    @Test
    public void formatter() {
        assertThatFormatted(new long[]{12, 34, 56}, "a:0 b:1[2]")
                .isEqualTo("a: 12 b: [34, 56]");
        assertThatFormatted(new long[]{12, 0, 0}, "a:0? b:1[2]?")
                .isEqualTo("a: 12");
        assertThatFormatted(new long[]{0, 34, 56}, "a:0? b:1[2]?")
                .isEqualTo("b: [34, 56]");
        assertThatFormatted(new long[]{3141592, 2000000, 1414213}, "pi:0p sqrt:1[2]p")
                .isEqualTo("pi: 3.14 sqrt: [2.00, 1.41]");
    }

    private static StringSubject assertThatFormatted(long[] stats, String format) {
        return assertThat(new PowerStats.PowerStatsFormatter(format)
                .format(stats));
    }
}
+5 −24
Original line number Diff line number Diff line
@@ -19,12 +19,9 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.BatteryConsumer;

import com.android.internal.os.PowerStats;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
@@ -206,25 +203,9 @@ public class AggregatedPowerStatsConfig {
        return mPowerComponents;
    }

    private static final PowerStatsProcessor NO_OP_PROCESSOR =
            new PowerStatsProcessor() {
    private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() {
        @Override
        void finish(PowerComponentAggregatedPowerStats stats) {
        }

                @Override
                String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
                    return Arrays.toString(stats);
                }

                @Override
                String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
                    return descriptor.getStateLabel(key) + " " + Arrays.toString(stats);
                }

                @Override
                String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
                    return Arrays.toString(stats);
                }
    };
}
+3 −3
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout {
     * Declare that the stats array has a section capturing CPU time per scaling step
     */
    public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
        mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
        mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps");
        mDeviceCpuTimeByScalingStepCount = scalingStepCount;
    }

@@ -72,7 +72,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout {
     * Declare that the stats array has a section capturing CPU time in each cluster
     */
    public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
        mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
        mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters");
        mDeviceCpuTimeByClusterCount = clusterCount;
    }

@@ -102,7 +102,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout {
    public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
        mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
        updatePowerBracketCount();
        mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
        mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time");
    }

    private void updatePowerBracketCount() {
+0 −61
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.server.power.stats;

import android.os.BatteryStats;
import android.util.ArraySet;
import android.util.Log;

@@ -487,64 +486,4 @@ public class CpuPowerStatsProcessor extends PowerStatsProcessor {
            stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
        }
    }

    @Override
    public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
        unpackPowerStatsDescriptor(descriptor);
        StringBuilder sb = new StringBuilder();
        int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
        sb.append("steps: [");
        for (int step = 0; step < cpuScalingStepCount; step++) {
            if (step != 0) {
                sb.append(", ");
            }
            sb.append(mStatsLayout.getTimeByScalingStep(stats, step));
        }
        int clusterCount = mStatsLayout.getCpuClusterCount();
        sb.append("] clusters: [");
        for (int cluster = 0; cluster < clusterCount; cluster++) {
            if (cluster != 0) {
                sb.append(", ");
            }
            sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
        }
        sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats));
        int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
        if (energyConsumerCount > 0) {
            sb.append(" energy: [");
            for (int i = 0; i < energyConsumerCount; i++) {
                if (i != 0) {
                    sb.append(", ");
                }
                sb.append(mStatsLayout.getConsumedEnergy(stats, i));
            }
            sb.append("]");
        }
        sb.append(" power: ").append(
                BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats)));
        return sb.toString();
    }

    @Override
    String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
        // Unsupported for this power component
        return null;
    }

    @Override
    public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
        unpackPowerStatsDescriptor(descriptor);
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
        for (int bracket = 0; bracket < powerBracketCount; bracket++) {
            if (bracket != 0) {
                sb.append(", ");
            }
            sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket));
        }
        sb.append("] power: ").append(
                BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats)));
        return sb.toString();
    }
}
Loading