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

Commit 7422164a authored by Dmitri Plotnikov's avatar Dmitri Plotnikov Committed by Android (Google) Code Review
Browse files

Merge "Improve power stats formatting" into main

parents 550349ab 6569c1ed
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