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

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

Merge changes from topic "scaling_policy"

* changes:
  Use CpuScalingPolicy in BatteryStats
  Change PowerProfile to allow flexible CPU scaling policies
  Add CpuScalingPolicyReader to obtain CPU topology from kernel
parents c434ef5a cbd3a2e6
Loading
Loading
Loading
Loading
+45 −32
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import android.view.Display;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.CpuScalingPolicies;

import com.google.android.collect.Lists;

@@ -1008,9 +1009,11 @@ public abstract class BatteryStats {
         * @param cluster the index of the CPU cluster.
         * @param step the index of the CPU speed. This is not the actual speed of the CPU.
         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
         * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
         * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
         * @see com.android.internal.os.CpuScalingPolicies#getPolicies
         * @see com.android.internal.os.CpuScalingPolicies#getFrequencies
         * @deprecated Unused except in tests
         */
        @Deprecated
        public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);

        /**
@@ -1648,11 +1651,9 @@ public abstract class BatteryStats {
    public abstract long getNextMaxDailyDeadline();

    /**
     * Returns the total number of frequencies across all CPU clusters.
     * Returns the CPU scaling policies.
     */
    public abstract int getCpuFreqCount();

    public abstract long[] getCpuFreqs();
    public abstract CpuScalingPolicies getCpuScalingPolicies();

    public final static class HistoryTag {
        public String string;
@@ -3390,8 +3391,8 @@ public abstract class BatteryStats {
     *     cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
     * </pre>
     *
     * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
     * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
     * @see com.android.internal.os.CpuScalingPolicies#getPolicies
     * @see com.android.internal.os.CpuScalingPolicies#getFrequencies
     */
    @Nullable
    public abstract long[] getSystemServiceTimeAtCpuSpeeds();
@@ -4677,12 +4678,14 @@ public abstract class BatteryStats {
                            proportionalAttributionCalculator.getProportionalPowerMah(consumer)));
        }

        final long[] cpuFreqs = getCpuFreqs();
        if (cpuFreqs != null) {
        final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
        if (scalingPolicies != null) {
            sb.setLength(0);
            for (int i = 0; i < cpuFreqs.length; ++i) {
                if (i != 0) sb.append(',');
                sb.append(cpuFreqs[i]);
            for (int policy : scalingPolicies.getPolicies()) {
                for (int frequency : scalingPolicies.getFrequencies(policy)) {
                    if (sb.length() != 0) sb.append(',');
                    sb.append(frequency);
                }
            }
            dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
        }
@@ -4994,11 +4997,12 @@ public abstract class BatteryStats {
            }

            // If the cpuFreqs is null, then don't bother checking for cpu freq times.
            if (cpuFreqs != null) {
            if (scalingPolicies != null) {
                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
                // If total cpuFreqTimes is null, then we don't need to check for
                // screenOffCpuFreqTimes.
                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
                if (cpuFreqTimeMs != null
                        && cpuFreqTimeMs.length == scalingPolicies.getScalingStepCount()) {
                    sb.setLength(0);
                    for (int i = 0; i < cpuFreqTimeMs.length; ++i) {
                        if (i != 0) sb.append(',');
@@ -5018,7 +5022,8 @@ public abstract class BatteryStats {
                            cpuFreqTimeMs.length, sb.toString());
                }

                final long[] timesInFreqMs = new long[getCpuFreqCount()];
                final long[] timesInFreqMs =
                        new long[getCpuScalingPolicies().getScalingStepCount()];
                for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
                    if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
                        sb.setLength(0);
@@ -6000,14 +6005,18 @@ public abstract class BatteryStats {
            }
        }

        final long[] cpuFreqs = getCpuFreqs();
        if (cpuFreqs != null) {
        final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
        if (scalingPolicies != null) {
            sb.setLength(0);
            sb.append("  CPU freqs:");
            for (int i = 0; i < cpuFreqs.length; ++i) {
                sb.append(' ').append(cpuFreqs[i]);
            sb.append("  CPU scaling: ");
            for (int policy : scalingPolicies.getPolicies()) {
                sb.append(" policy").append(policy).append(':');
                for (int frequency : scalingPolicies.getFrequencies(policy)) {
                    sb.append(' ').append(frequency);
                }
            pw.println(sb.toString());
            }

            pw.println(sb);
            pw.println();
        }

@@ -6628,7 +6637,7 @@ public abstract class BatteryStats {
                pw.println(sb.toString());
            }

            final long[] timesInFreqMs = new long[getCpuFreqCount()];
            final long[] timesInFreqMs = new long[getCpuScalingPolicies().getScalingStepCount()];
            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
                if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
                    sb.setLength(0);
@@ -8078,12 +8087,13 @@ public abstract class BatteryStats {
            proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
            proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));

            final long[] cpuFreqs = getCpuFreqs();
            if (cpuFreqs != null) {
            final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
            if (scalingPolicies != null) {
                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
                // If total cpuFreqTimes is null, then we don't need to check for
                // screenOffCpuFreqTimes.
                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
                if (cpuFreqTimeMs != null
                        && cpuFreqTimeMs.length == scalingPolicies.getScalingStepCount()) {
                    long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
                    if (screenOffCpuFreqTimeMs == null) {
                        screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
@@ -8100,8 +8110,9 @@ public abstract class BatteryStats {
                }
            }

            final long[] timesInFreqMs = new long[getCpuFreqCount()];
            final long[] timesInFreqScreenOffMs = new long[getCpuFreqCount()];
            final int stepCount = getCpuScalingPolicies().getScalingStepCount();
            final long[] timesInFreqMs = new long[stepCount];
            final long[] timesInFreqScreenOffMs = new long[stepCount];
            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
                if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
                    if (!u.getScreenOffCpuFreqTimes(timesInFreqScreenOffMs, procState)) {
@@ -8571,10 +8582,12 @@ public abstract class BatteryStats {
        dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());

        // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
        final long[] cpuFreqs = getCpuFreqs();
        if (cpuFreqs != null) {
            for (long i : cpuFreqs) {
                proto.write(SystemProto.CPU_FREQUENCY, i);
        final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
        if (scalingPolicies != null) {
            for (int policy : scalingPolicies.getPolicies()) {
                for (int frequency : scalingPolicies.getFrequencies(policy)) {
                    proto.write(SystemProto.CPU_FREQUENCY, frequency);
                }
            }
        }

+98 −0
Original line number 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 android.annotation.NonNull;
import android.util.SparseArray;

import libcore.util.EmptyArray;

import java.util.Arrays;

/**
 * CPU scaling policies: the policy IDs and corresponding supported scaling for those
 * policies.
 */
public class CpuScalingPolicies {
    private final SparseArray<int[]> mCpusByPolicy;
    private final SparseArray<int[]> mFreqsByPolicy;
    private final int[] mPolicies;
    private final int mScalingStepCount;

    public CpuScalingPolicies(@NonNull SparseArray<int[]> cpusByPolicy,
            @NonNull SparseArray<int[]> freqsByPolicy) {
        mCpusByPolicy = cpusByPolicy;
        mFreqsByPolicy = freqsByPolicy;

        mPolicies = new int[cpusByPolicy.size()];
        for (int i = 0; i < mPolicies.length; i++) {
            mPolicies[i] = cpusByPolicy.keyAt(i);
        }

        Arrays.sort(mPolicies);

        int count = 0;
        for (int i = freqsByPolicy.size() - 1; i >= 0; i--) {
            count += freqsByPolicy.valueAt(i).length;
        }
        mScalingStepCount = count;
    }

    /**
     * Returns available policies (aka clusters).
     */
    @NonNull
    public int[] getPolicies() {
        return mPolicies;
    }

    /**
     * CPUs covered by the specified policy.
     */
    @NonNull
    public int[] getRelatedCpus(int policy) {
        return mCpusByPolicy.get(policy, EmptyArray.INT);
    }

    /**
     * Scaling frequencies supported for the specified policy.
     */
    @NonNull
    public int[] getFrequencies(int policy) {
        return mFreqsByPolicy.get(policy, EmptyArray.INT);
    }

    /**
     * Returns the overall number of supported scaling steps: grand total of available frequencies
     * across all scaling policies.
     */
    public int getScalingStepCount() {
        return mScalingStepCount;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int policy : mPolicies) {
            sb.append("policy").append(policy)
                    .append("\n CPUs: ").append(Arrays.toString(mCpusByPolicy.get(policy)))
                    .append("\n freqs: ").append(Arrays.toString(mFreqsByPolicy.get(policy)))
                    .append("\n");
        }
        return sb.toString();
    }
}
+144 −0
Original line number 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 android.annotation.NonNull;
import android.os.FileUtils;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import libcore.util.EmptyArray;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Captures a CPU scaling policies such as available scaling frequencies as well as
 * CPUs (cores) for each policy.
 *
 * See <a
 * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
 * #policy-interface-in-sysfs">Policy Interface in sysfs</a>
 */
public class CpuScalingPolicyReader {
    private static final String TAG = "CpuScalingPolicyReader";
    private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq";
    private static final Pattern POLICY_PATTERN = Pattern.compile("policy(\\d+)");
    private static final String FILE_NAME_RELATED_CPUS = "related_cpus";
    private static final String FILE_NAME_SCALING_AVAILABLE_FREQUENCIES =
            "scaling_available_frequencies";
    private static final String FILE_NAME_SCALING_BOOST_FREQUENCIES = "scaling_boost_frequencies";
    private static final String FILE_NAME_CPUINFO_CUR_FREQ = "cpuinfo_cur_freq";

    private final String mCpuFreqDir;

    public CpuScalingPolicyReader() {
        this(CPUFREQ_DIR);
    }

    @VisibleForTesting
    public CpuScalingPolicyReader(String cpuFreqDir) {
        mCpuFreqDir = cpuFreqDir;
    }

    /**
     * Reads scaling policy info from sysfs files in /sys/devices/system/cpu/cpufreq
     */
    @NonNull
    public CpuScalingPolicies read() {
        SparseArray<int[]> cpusByPolicy = new SparseArray<>();
        SparseArray<int[]> freqsByPolicy = new SparseArray<>();

        File cpuFreqDir = new File(mCpuFreqDir);
        File[] policyDirs = cpuFreqDir.listFiles();
        if (policyDirs != null) {
            for (File policyDir : policyDirs) {
                Matcher matcher = POLICY_PATTERN.matcher(policyDir.getName());
                if (matcher.matches()) {
                    int[] relatedCpus = readIntsFromFile(
                            new File(policyDir, FILE_NAME_RELATED_CPUS));
                    if (relatedCpus.length == 0) {
                        continue;
                    }

                    int[] availableFreqs = readIntsFromFile(
                            new File(policyDir, FILE_NAME_SCALING_AVAILABLE_FREQUENCIES));
                    int[] boostFreqs = readIntsFromFile(
                            new File(policyDir, FILE_NAME_SCALING_BOOST_FREQUENCIES));
                    int[] freqs;
                    if (boostFreqs.length == 0) {
                        freqs = availableFreqs;
                    } else {
                        freqs = Arrays.copyOf(availableFreqs,
                                availableFreqs.length + boostFreqs.length);
                        System.arraycopy(boostFreqs, 0, freqs, availableFreqs.length,
                                boostFreqs.length);
                    }
                    if (freqs.length == 0) {
                        freqs = readIntsFromFile(new File(policyDir, FILE_NAME_CPUINFO_CUR_FREQ));
                        if (freqs.length == 0) {
                            freqs = new int[]{0};  // Unknown frequency
                        }
                    }
                    int policy = Integer.parseInt(matcher.group(1));
                    cpusByPolicy.put(policy, relatedCpus);
                    freqsByPolicy.put(policy, freqs);
                }
            }
        }

        if (cpusByPolicy.size() == 0) {
            // There just has to be at least one CPU - otherwise, what's executing this code?
            cpusByPolicy.put(0, new int[]{0});
            freqsByPolicy.put(0, new int[]{0});
        }

        return new CpuScalingPolicies(cpusByPolicy, freqsByPolicy);
    }

    @NonNull
    private static int[] readIntsFromFile(File file) {
        if (!file.exists()) {
            return EmptyArray.INT;
        }

        IntArray intArray = new IntArray(16);
        try {
            String contents = FileUtils.readTextFile(file, 0, null).trim();
            String[] strings = contents.split(" ");
            intArray.clear();
            for (String s : strings) {
                try {
                    intArray.add(Integer.parseInt(s));
                } catch (NumberFormatException e) {
                    Slog.e(TAG, "Unexpected file format " + file
                            + ": " + contents, e);
                }
            }
            return intArray.toArray();
        } catch (IOException e) {
            Slog.e(TAG, "Cannot read " + file, e);
            return EmptyArray.INT;
        }
    }
}
+14 −59
Original line number Diff line number Diff line
@@ -16,9 +16,7 @@
package com.android.internal.os;

import static com.android.internal.os.KernelCpuProcStringReader.asLongs;
import static com.android.internal.util.Preconditions.checkNotNull;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.StrictMode;
import android.util.IntArray;
@@ -29,11 +27,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@@ -352,7 +348,7 @@ public abstract class KernelCpuUidTimeReader<T> {
        private int mFreqCount = 0;
        private int mErrors = 0;
        private boolean mPerClusterTimesAvailable;
        private boolean mAllUidTimesAvailable = true;
        private boolean mAllUidTimesAvailable;

        public KernelCpuUidFreqTimeReader(boolean throttle) {
            this(throttle, Clock.SYSTEM_CLOCK);
@@ -375,11 +371,23 @@ public abstract class KernelCpuUidTimeReader<T> {
            mProcFilePath = Paths.get(procFile);
        }

        /**
         * Initializes the reader.  Should be called during the system-ready boot phase.
         */
        public void onSystemReady() {
            if (mBpfTimesAvailable && mCpuFreqs == null) {
                readFreqsThroughBpf();
                // By extension: if we can read CPU frequencies through eBPF, we can also
                // read per-UID CPU time-in-state
                mAllUidTimesAvailable = mCpuFreqs != null;
            }
        }

        /**
         * @return Whether per-cluster times are available.
         */
        public boolean perClusterTimesAvailable() {
            return mPerClusterTimesAvailable;
            return mBpfTimesAvailable;
        }

        /**
@@ -396,59 +404,6 @@ public abstract class KernelCpuUidTimeReader<T> {
            return mLastTimes;
        }

        /**
         * Reads a list of CPU frequencies from /proc/uid_time_in_state. Uses a given PowerProfile
         * to determine if per-cluster times are available.
         *
         * @param powerProfile The PowerProfile to compare against.
         * @return A long[] of CPU frequencies in Hz.
         */
        public long[] readFreqs(@NonNull PowerProfile powerProfile) {
            checkNotNull(powerProfile);
            if (mCpuFreqs != null) {
                // No need to read cpu freqs more than once.
                return mCpuFreqs;
            }
            if (!mAllUidTimesAvailable) {
                return null;
            }
            if (mBpfTimesAvailable) {
                readFreqsThroughBpf();
            }
            if (mCpuFreqs == null) {
                final int oldMask = StrictMode.allowThreadDiskReadsMask();
                try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
                    if (readFreqs(reader.readLine()) == null) {
                        return null;
                    }
                } catch (IOException e) {
                    if (++mErrors >= MAX_ERROR_COUNT) {
                        mAllUidTimesAvailable = false;
                    }
                    Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
                    return null;
                } finally {
                    StrictMode.setThreadPolicyMask(oldMask);
                }
            }
            // Check if the freqs in the proc file correspond to per-cluster freqs.
            final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
            final int numClusters = powerProfile.getNumCpuClusters();
            if (numClusterFreqs.size() == numClusters) {
                mPerClusterTimesAvailable = true;
                for (int i = 0; i < numClusters; ++i) {
                    if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
                        mPerClusterTimesAvailable = false;
                        break;
                    }
                }
            } else {
                mPerClusterTimesAvailable = false;
            }
            Slog.i(mTag, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
            return mCpuFreqs;
        }

        private long[] readFreqsThroughBpf() {
            if (!mBpfTimesAvailable || mBpfReader == null) {
                return null;
+195 −46

File changed.

Preview size limit exceeded, changes collapsed.

Loading