• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.os;
18 
19 import android.annotation.NonNull;
20 import android.os.FileUtils;
21 import android.util.IntArray;
22 import android.util.Slog;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import libcore.util.EmptyArray;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.Arrays;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 
35 /**
36  * Captures a CPU scaling policies such as available scaling frequencies as well as
37  * CPUs (cores) for each policy.
38  *
39  * See <a
40  * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
41  * #policy-interface-in-sysfs">Policy Interface in sysfs</a>
42  */
43 @android.ravenwood.annotation.RavenwoodKeepWholeClass
44 public class CpuScalingPolicyReader {
45     private static final String TAG = "CpuScalingPolicyReader";
46     private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq";
47     private static final Pattern POLICY_PATTERN = Pattern.compile("policy(\\d+)");
48     private static final String FILE_NAME_RELATED_CPUS = "related_cpus";
49     private static final String FILE_NAME_SCALING_AVAILABLE_FREQUENCIES =
50             "scaling_available_frequencies";
51     private static final String FILE_NAME_SCALING_BOOST_FREQUENCIES = "scaling_boost_frequencies";
52     private static final String FILE_NAME_CPUINFO_CUR_FREQ = "cpuinfo_cur_freq";
53 
54     private final String mCpuFreqDir;
55 
CpuScalingPolicyReader()56     public CpuScalingPolicyReader() {
57         this(CPUFREQ_DIR);
58     }
59 
60     @VisibleForTesting
CpuScalingPolicyReader(String cpuFreqDir)61     public CpuScalingPolicyReader(String cpuFreqDir) {
62         mCpuFreqDir = cpuFreqDir;
63     }
64 
65     /**
66      * Reads scaling policy info from sysfs files in /sys/devices/system/cpu/cpufreq
67      */
68     @NonNull
read()69     public CpuScalingPolicies read() {
70         SparseArray<int[]> cpusByPolicy = new SparseArray<>();
71         SparseArray<int[]> freqsByPolicy = new SparseArray<>();
72 
73         File cpuFreqDir = new File(mCpuFreqDir);
74         File[] policyDirs = cpuFreqDir.listFiles();
75         if (policyDirs != null) {
76             for (File policyDir : policyDirs) {
77                 Matcher matcher = POLICY_PATTERN.matcher(policyDir.getName());
78                 if (matcher.matches()) {
79                     int[] relatedCpus = readIntsFromFile(
80                             new File(policyDir, FILE_NAME_RELATED_CPUS));
81                     if (relatedCpus.length == 0) {
82                         continue;
83                     }
84 
85                     int[] availableFreqs = readIntsFromFile(
86                             new File(policyDir, FILE_NAME_SCALING_AVAILABLE_FREQUENCIES));
87                     int[] boostFreqs = readIntsFromFile(
88                             new File(policyDir, FILE_NAME_SCALING_BOOST_FREQUENCIES));
89                     int[] freqs;
90                     if (boostFreqs.length == 0) {
91                         freqs = availableFreqs;
92                     } else {
93                         freqs = Arrays.copyOf(availableFreqs,
94                                 availableFreqs.length + boostFreqs.length);
95                         System.arraycopy(boostFreqs, 0, freqs, availableFreqs.length,
96                                 boostFreqs.length);
97                     }
98                     if (freqs.length == 0) {
99                         freqs = readIntsFromFile(new File(policyDir, FILE_NAME_CPUINFO_CUR_FREQ));
100                         if (freqs.length == 0) {
101                             freqs = new int[]{0};  // Unknown frequency
102                         }
103                     }
104                     int policy = Integer.parseInt(matcher.group(1));
105                     cpusByPolicy.put(policy, relatedCpus);
106                     freqsByPolicy.put(policy, freqs);
107                 }
108             }
109         }
110 
111         if (cpusByPolicy.size() == 0) {
112             // There just has to be at least one CPU - otherwise, what's executing this code?
113             cpusByPolicy.put(0, new int[]{0});
114             freqsByPolicy.put(0, new int[]{0});
115         }
116 
117         return new CpuScalingPolicies(cpusByPolicy, freqsByPolicy);
118     }
119 
120     @NonNull
readIntsFromFile(File file)121     private static int[] readIntsFromFile(File file) {
122         if (!file.exists()) {
123             return EmptyArray.INT;
124         }
125 
126         IntArray intArray = new IntArray(16);
127         try {
128             String contents = FileUtils.readTextFile(file, 0, null).trim();
129             String[] strings = contents.split(" ");
130             intArray.clear();
131             for (String s : strings) {
132                 if (s.isBlank()) {
133                     continue;
134                 }
135                 try {
136                     intArray.add(Integer.parseInt(s));
137                 } catch (NumberFormatException e) {
138                     Slog.e(TAG, "Unexpected file format " + file
139                             + ": " + contents, e);
140                 }
141             }
142             return intArray.toArray();
143         } catch (IOException e) {
144             Slog.e(TAG, "Cannot read " + file, e);
145             return EmptyArray.INT;
146         }
147     }
148 }
149