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

Commit d407ac2a authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Add CpuScalingPolicyReader to obtain CPU topology from kernel

Bug: 283012390
Test: atest FrameworksCoreTests:CpuScalingPolicyReaderTest

Change-Id: I57abc971e7813a69e434917b5789181606f31ffe
parent 753acc68
Loading
Loading
Loading
Loading
+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;
        }
    }
}
+84 −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 static androidx.test.InstrumentationRegistry.getContext;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.os.FileUtils;

import androidx.test.runner.AndroidJUnit4;

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

import java.io.File;
import java.io.IOException;

@RunWith(AndroidJUnit4.class)
public class CpuScalingPolicyReaderTest {
    private CpuScalingPolicyReader mCpuScalingPolicyReader;

    @Before
    public void setup() throws IOException {
        File testDir = getContext().getDir("test", Context.MODE_PRIVATE);
        FileUtils.deleteContents(testDir);

        File policy0 = new File(testDir, "policy0");
        FileUtils.createDir(policy0);
        FileUtils.stringToFile(new File(policy0, "related_cpus"), "0 2 7");
        FileUtils.stringToFile(new File(policy0, "scaling_available_frequencies"), "1234 9876");

        File policy5 = new File(testDir, "policy5");
        FileUtils.createDir(policy5);
        FileUtils.stringToFile(new File(policy5, "related_cpus"), "3 6\n");
        FileUtils.stringToFile(new File(policy5, "scaling_available_frequencies"), "1234 5678\n");
        FileUtils.stringToFile(new File(policy5, "scaling_boost_frequencies"), "9998 9999\n");

        File policy7 = new File(testDir, "policy7");
        FileUtils.createDir(policy7);
        FileUtils.stringToFile(new File(policy7, "related_cpus"), "8\n");
        FileUtils.stringToFile(new File(policy7, "cpuinfo_cur_freq"), "1000000");

        File policy9 = new File(testDir, "policy9");
        FileUtils.createDir(policy9);
        FileUtils.stringToFile(new File(policy9, "related_cpus"), "42");

        File policy999 = new File(testDir, "policy999");
        FileUtils.createDir(policy999);

        mCpuScalingPolicyReader = new CpuScalingPolicyReader(testDir.getPath());
    }

    @Test
    public void readFromSysFs() {
        CpuScalingPolicies info = mCpuScalingPolicyReader.read();
        assertThat(info.getPolicies()).isEqualTo(new int[]{0, 5, 7, 9});
        assertThat(info.getRelatedCpus(0)).isEqualTo(new int[]{0, 2, 7});
        assertThat(info.getFrequencies(0)).isEqualTo(new int[]{1234, 9876});
        assertThat(info.getRelatedCpus(5)).isEqualTo(new int[]{3, 6});
        assertThat(info.getFrequencies(5)).isEqualTo(new int[]{1234, 5678, 9998, 9999});
        assertThat(info.getRelatedCpus(7)).isEqualTo(new int[]{8});
        assertThat(info.getFrequencies(7)).isEqualTo(new int[]{1000000});
        assertThat(info.getRelatedCpus(9)).isEqualTo(new int[]{42});
        assertThat(info.getFrequencies(9)).isEqualTo(new int[]{0});     // Unknown
        assertThat(info.getScalingStepCount()).isEqualTo(8);
    }
}