1 /*
2  * Copyright (C) 2022 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.server.cpu;
18 
19 import static com.android.server.cpu.CpuMonitorService.DEBUG;
20 import static com.android.server.cpu.CpuMonitorService.TAG;
21 
22 import android.annotation.IntDef;
23 import android.annotation.Nullable;
24 import android.os.SystemClock;
25 import android.system.Os;
26 import android.system.OsConstants;
27 import android.util.IndentingPrintWriter;
28 import android.util.IntArray;
29 import android.util.LongSparseLongArray;
30 import android.util.SparseArray;
31 import android.util.SparseIntArray;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.server.utils.Slogf;
35 
36 import java.io.File;
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.nio.file.Files;
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.Objects;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45 
46 /** Reader to read CPU information from proc and sys fs files exposed by the Kernel. */
47 public final class CpuInfoReader {
48     static final int FLAG_CPUSET_CATEGORY_TOP_APP = 1 << 0;
49     static final int FLAG_CPUSET_CATEGORY_BACKGROUND = 1 << 1;
50 
51     private static final String CPUFREQ_DIR_PATH = "/sys/devices/system/cpu/cpufreq";
52     private static final String POLICY_DIR_PREFIX = "policy";
53     private static final String RELATED_CPUS_FILE = "related_cpus";
54     private static final String AFFECTED_CPUS_FILE = "affected_cpus";
55     private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq";
56     private static final String MAX_SCALING_FREQ_FILE = "scaling_max_freq";
57     private static final String TIME_IN_STATE_FILE = "stats/time_in_state";
58     private static final String CPUSET_DIR_PATH = "/dev/cpuset";
59     private static final String CPUSET_TOP_APP_DIR = "top-app";
60     private static final String CPUSET_BACKGROUND_DIR = "background";
61     private static final String CPUS_FILE = "cpus";
62     private static final String PROC_STAT_FILE_PATH = "/proc/stat";
63     private static final Pattern PROC_STAT_PATTERN =
64             Pattern.compile("cpu(?<core>[0-9]+)\\s(?<userClockTicks>[0-9]+)\\s"
65                     + "(?<niceClockTicks>[0-9]+)\\s(?<sysClockTicks>[0-9]+)\\s"
66                     + "(?<idleClockTicks>[0-9]+)\\s(?<iowaitClockTicks>[0-9]+)\\s"
67                     + "(?<irqClockTicks>[0-9]+)\\s(?<softirqClockTicks>[0-9]+)\\s"
68                     + "(?<stealClockTicks>[0-9]+)\\s(?<guestClockTicks>[0-9]+)\\s"
69                     + "(?<guestNiceClockTicks>[0-9]+)");
70     private static final Pattern TIME_IN_STATE_PATTERN =
71             Pattern.compile("(?<freqKHz>[0-9]+)\\s(?<time>[0-9]+)");
72     private static final long MILLIS_PER_CLOCK_TICK = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK);
73     private static final long MIN_READ_INTERVAL_MILLISECONDS = 500;
74 
75     @Retention(RetentionPolicy.SOURCE)
76     @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = {
77             FLAG_CPUSET_CATEGORY_TOP_APP,
78             FLAG_CPUSET_CATEGORY_BACKGROUND
79     })
80     /** package **/ @interface CpusetCategory{}
81 
82     // TODO(b/242722241): Protect updatable variables with a local lock.
83     private final File mCpusetDir;
84     private final long mMinReadIntervalMillis;
85     private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray();
86     private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>();
87     private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>();
88     private final SparseArray<LongSparseLongArray> mTimeInStateByPolicyId = new SparseArray<>();
89 
90     private File mCpuFreqDir;
91     private File mProcStatFile;
92     private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
93     private boolean mIsEnabled;
94     private boolean mHasTimeInStateFile;
95     private long mLastReadUptimeMillis;
96     private SparseArray<CpuInfo> mLastReadCpuInfos;
97 
CpuInfoReader()98     public CpuInfoReader() {
99         this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH),
100                 MIN_READ_INTERVAL_MILLISECONDS);
101     }
102 
103     @VisibleForTesting
CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis)104     CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis) {
105         mCpusetDir = cpusetDir;
106         mCpuFreqDir = cpuFreqDir;
107         mProcStatFile = procStatFile;
108         mMinReadIntervalMillis = minReadIntervalMillis;
109     }
110 
111     /**
112      * Initializes CpuInfoReader and returns a boolean to indicate whether the reader is enabled.
113      */
init()114     public boolean init() {
115         if (mCpuFreqPolicyDirsById.size() > 0) {
116             Slogf.w(TAG, "Ignoring duplicate CpuInfoReader init request");
117             return mIsEnabled;
118         }
119         File[] policyDirs = mCpuFreqDir.listFiles(
120                 file -> file.isDirectory() && file.getName().startsWith(POLICY_DIR_PREFIX));
121         if (policyDirs == null || policyDirs.length == 0) {
122             Slogf.w(TAG, "Missing CPU frequency policy directories at %s",
123                     mCpuFreqDir.getAbsolutePath());
124             return false;
125         }
126         populateCpuFreqPolicyDirsById(policyDirs);
127         if (mCpuFreqPolicyDirsById.size() == 0) {
128             Slogf.e(TAG, "Failed to parse CPU frequency policy directory paths: %s",
129                     Arrays.toString(policyDirs));
130             return false;
131         }
132         readStaticPolicyInfo();
133         if (mStaticPolicyInfoById.size() == 0) {
134             Slogf.e(TAG, "Failed to read static CPU frequency policy info from policy dirs: %s",
135                     Arrays.toString(policyDirs));
136             return false;
137         }
138         if (!mProcStatFile.exists()) {
139             Slogf.e(TAG, "Missing proc stat file at %s", mProcStatFile.getAbsolutePath());
140             return false;
141         }
142         readCpusetCategories();
143         if (mCpusetCategoriesByCpus.size() == 0) {
144             Slogf.e(TAG, "Failed to read cpuset information from %s", mCpusetDir.getAbsolutePath());
145             return false;
146         }
147         // Certain CPU performance scaling drivers, such as intel_pstate, perform their own CPU
148         // frequency transitions and do not supply this information to the Kernel's cpufreq node.
149         // Thus, the `time_in_state` file won't be available on devices running such scaling
150         // drivers. Check the presence of this file only once during init and do not throw error
151         // when this file is missing. The implementation must accommodate such use cases.
152         for (int i = 0; i < mCpuFreqPolicyDirsById.size() && !mHasTimeInStateFile; ++i) {
153             // If all the CPU cores on a policy are offline, this file might be missing for the
154             // policy. Make sure this file is not available on all policies before marking it as
155             // missing.
156             mHasTimeInStateFile |= new File(mCpuFreqPolicyDirsById.valueAt(i), TIME_IN_STATE_FILE)
157                     .exists();
158         }
159         if (!mHasTimeInStateFile) {
160             Slogf.e(TAG, "Time in state file not available for any cpufreq policy");
161         }
162         mIsEnabled = true;
163         return true;
164     }
165 
166     /**
167      * Reads CPU information from proc and sys fs files exposed by the Kernel.
168      *
169      * @return SparseArray keyed by CPU core ID; {@code null} on error or when disabled.
170      */
171     @Nullable
readCpuInfos()172     public SparseArray<CpuInfo> readCpuInfos() {
173         if (!mIsEnabled) {
174             return null;
175         }
176         long uptimeMillis = SystemClock.uptimeMillis();
177         if (mLastReadUptimeMillis > 0
178                 && uptimeMillis - mLastReadUptimeMillis < mMinReadIntervalMillis) {
179             Slogf.w(TAG, "Skipping reading from device and returning the last read CpuInfos. "
180                     + "Last read was %d ms ago, min read interval is %d ms",
181                     uptimeMillis - mLastReadUptimeMillis, mMinReadIntervalMillis);
182             return mLastReadCpuInfos;
183         }
184         mLastReadUptimeMillis = uptimeMillis;
185         mLastReadCpuInfos = null;
186         SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats();
187         if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) {
188             Slogf.e(TAG, "Failed to read latest CPU usage stats");
189             return null;
190         }
191         SparseArray<DynamicPolicyInfo> dynamicPolicyInfoById = readDynamicPolicyInfo();
192         if (dynamicPolicyInfoById.size() == 0) {
193             Slogf.e(TAG, "Failed to read dynamic policy infos");
194             return null;
195         }
196         SparseArray<CpuInfo> cpuInfoByCpus = new SparseArray<>();
197         for (int i = 0; i < mStaticPolicyInfoById.size(); i++) {
198             int policyId = mStaticPolicyInfoById.keyAt(i);
199             StaticPolicyInfo staticPolicyInfo = mStaticPolicyInfoById.valueAt(i);
200             DynamicPolicyInfo dynamicPolicyInfo = dynamicPolicyInfoById.get(policyId);
201             if (dynamicPolicyInfo == null) {
202                 Slogf.w(TAG, "Missing dynamic policy info for policy ID %d", policyId);
203                 continue;
204             }
205             if (dynamicPolicyInfo.curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY
206                     || dynamicPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
207                 Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for"
208                         + " policy ID %d", policyId);
209                 continue;
210             }
211             if (dynamicPolicyInfo.curCpuFreqKHz > dynamicPolicyInfo.maxCpuFreqKHz) {
212                 Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
213                         + " (%d) for policy ID (%d). Skipping CPU frequency policy",
214                         dynamicPolicyInfo.curCpuFreqKHz, dynamicPolicyInfo.maxCpuFreqKHz, policyId);
215                 continue;
216             }
217             for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
218                 int relatedCpuCore = staticPolicyInfo.relatedCpuCores.get(coreIdx);
219                 CpuInfo prevCpuInfo = cpuInfoByCpus.get(relatedCpuCore);
220                 if (prevCpuInfo != null) {
221                     Slogf.wtf(TAG, "CPU info already available for the CPU core %d",
222                             relatedCpuCore);
223                     if (prevCpuInfo.isOnline) {
224                         continue;
225                     }
226                 }
227                 int cpusetCategories = mCpusetCategoriesByCpus.get(relatedCpuCore, -1);
228                 if (cpusetCategories < 0) {
229                     Slogf.w(TAG, "Missing cpuset information for the CPU core %d",
230                             relatedCpuCore);
231                     continue;
232                 }
233                 CpuUsageStats usageStats = cpuUsageStatsByCpus.get(relatedCpuCore);
234                 if (dynamicPolicyInfo.affectedCpuCores.indexOf(relatedCpuCore) < 0) {
235                     cpuInfoByCpus.append(relatedCpuCore, new CpuInfo(relatedCpuCore,
236                             cpusetCategories, /* isOnline= */false, CpuInfo.MISSING_FREQUENCY,
237                             dynamicPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY,
238                             usageStats));
239                     continue;
240                 }
241                 // If a CPU core is online, it must have the usage stats. When the usage stats is
242                 // missing, drop the core's CPU info.
243                 if (usageStats == null) {
244                     Slogf.w(TAG, "Missing CPU usage information for online CPU core %d",
245                             relatedCpuCore);
246                     continue;
247                 }
248                 CpuInfo cpuInfo = new CpuInfo(relatedCpuCore, cpusetCategories, /* isOnline= */true,
249                         dynamicPolicyInfo.curCpuFreqKHz, dynamicPolicyInfo.maxCpuFreqKHz,
250                         dynamicPolicyInfo.avgTimeInStateCpuFreqKHz, usageStats);
251                 cpuInfoByCpus.append(relatedCpuCore, cpuInfo);
252                 if (DEBUG) {
253                     Slogf.d(TAG, "Added %s for CPU core %d", cpuInfo, relatedCpuCore);
254                 }
255             }
256         }
257         mLastReadCpuInfos = cpuInfoByCpus;
258         return cpuInfoByCpus;
259     }
260 
261     /** Dumps the current state. */
dump(IndentingPrintWriter writer)262     public void dump(IndentingPrintWriter writer) {
263         writer.printf("*%s*\n", getClass().getSimpleName());
264         writer.increaseIndent();    // Add intend for the outermost block.
265 
266         writer.printf("mCpusetDir = %s\n", mCpusetDir.getAbsolutePath());
267         writer.printf("mCpuFreqDir = %s\n", mCpuFreqDir.getAbsolutePath());
268         writer.printf("mProcStatFile = %s\n", mProcStatFile.getAbsolutePath());
269         writer.printf("mIsEnabled = %s\n", mIsEnabled);
270         writer.printf("mHasTimeInStateFile = %s\n", mHasTimeInStateFile);
271         writer.printf("mLastReadUptimeMillis = %d\n", mLastReadUptimeMillis);
272         writer.printf("mMinReadIntervalMillis = %d\n", mMinReadIntervalMillis);
273 
274         writer.printf("Cpuset categories by CPU core:\n");
275         writer.increaseIndent();
276         for (int i = 0; i < mCpusetCategoriesByCpus.size(); i++) {
277             writer.printf("CPU core id = %d, %s\n", mCpusetCategoriesByCpus.keyAt(i),
278                     toCpusetCategoriesStr(mCpusetCategoriesByCpus.valueAt(i)));
279         }
280         writer.decreaseIndent();
281 
282         writer.println("Cpu frequency policy directories by policy id:");
283         writer.increaseIndent();
284         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
285             writer.printf("Policy id = %d, Dir = %s\n", mCpuFreqPolicyDirsById.keyAt(i),
286                     mCpuFreqPolicyDirsById.valueAt(i));
287         }
288         writer.decreaseIndent();
289 
290         writer.println("Static cpu frequency policy infos by policy id:");
291         writer.increaseIndent();
292         for (int i = 0; i < mStaticPolicyInfoById.size(); i++) {
293             writer.printf("Policy id = %d, %s\n", mStaticPolicyInfoById.keyAt(i),
294                     mStaticPolicyInfoById.valueAt(i));
295         }
296         writer.decreaseIndent();
297 
298         writer.println("Cpu time in frequency state by policy id:");
299         writer.increaseIndent();
300         for (int i = 0; i < mTimeInStateByPolicyId.size(); i++) {
301             writer.printf("Policy id = %d, Time(millis) in state by CPU frequency(KHz) = %s\n",
302                     mTimeInStateByPolicyId.keyAt(i), mTimeInStateByPolicyId.valueAt(i));
303         }
304         writer.decreaseIndent();
305 
306         writer.println("Last read CPU infos:");
307         writer.increaseIndent();
308         for (int i = 0; i < mLastReadCpuInfos.size(); i++) {
309             writer.printf("%s\n", mLastReadCpuInfos.valueAt(i));
310         }
311         writer.decreaseIndent();
312 
313         writer.println("Latest cumulative CPU usage stats by CPU core:");
314         writer.increaseIndent();
315         for (int i = 0; i < mCumulativeCpuUsageStats.size(); i++) {
316             writer.printf("CPU core id = %d, %s\n", mCumulativeCpuUsageStats.keyAt(i),
317                     mCumulativeCpuUsageStats.valueAt(i));
318         }
319         writer.decreaseIndent();
320 
321         writer.decreaseIndent();    // Remove intend for the outermost block.
322     }
323 
324     /**
325      * Sets the CPU frequency for testing.
326      *
327      * <p>Return {@code true} on success. Otherwise, returns {@code false}.
328      */
329     @VisibleForTesting
setCpuFreqDir(File cpuFreqDir)330     boolean setCpuFreqDir(File cpuFreqDir) {
331         File[] cpuFreqPolicyDirs = cpuFreqDir.listFiles(
332                 file -> file.isDirectory() && file.getName().startsWith(POLICY_DIR_PREFIX));
333         if (cpuFreqPolicyDirs == null || cpuFreqPolicyDirs.length == 0) {
334             Slogf.w(TAG, "Failed to set CPU frequency directory. Missing policy directories at %s",
335                     cpuFreqDir.getAbsolutePath());
336             return false;
337         }
338         populateCpuFreqPolicyDirsById(cpuFreqPolicyDirs);
339         int numCpuFreqPolicyDirs = mCpuFreqPolicyDirsById.size();
340         int numStaticPolicyInfos = mStaticPolicyInfoById.size();
341         if (numCpuFreqPolicyDirs == 0 || numCpuFreqPolicyDirs != numStaticPolicyInfos) {
342             Slogf.e(TAG, "Failed to set CPU frequency directory to %s. Total CPU frequency "
343                             + "policies (%d) under new path is either 0 or not equal to initial "
344                             + "total CPU frequency policies. Clearing CPU frequency policy "
345                             + "directories", cpuFreqDir.getAbsolutePath(), numCpuFreqPolicyDirs,
346                     numStaticPolicyInfos);
347             mCpuFreqPolicyDirsById.clear();
348             return false;
349         }
350         mCpuFreqDir = cpuFreqDir;
351         return true;
352     }
353 
354     /**
355      * Sets the proc stat file for testing.
356      *
357      * <p>Return true on success. Otherwise, returns false.
358      */
359     @VisibleForTesting
setProcStatFile(File procStatFile)360     boolean setProcStatFile(File procStatFile) {
361         if (!procStatFile.exists()) {
362             Slogf.e(TAG, "Missing proc stat file at %s", procStatFile.getAbsolutePath());
363             return false;
364         }
365         mProcStatFile = procStatFile;
366         return true;
367     }
368 
populateCpuFreqPolicyDirsById(File[] policyDirs)369     private void populateCpuFreqPolicyDirsById(File[] policyDirs) {
370         mCpuFreqPolicyDirsById.clear();
371         for (int i = 0; i < policyDirs.length; i++) {
372             File policyDir = policyDirs[i];
373             String policyIdStr = policyDir.getName().substring(POLICY_DIR_PREFIX.length());
374             if (policyIdStr.isEmpty()) {
375                 continue;
376             }
377             mCpuFreqPolicyDirsById.append(Integer.parseInt(policyIdStr), policyDir);
378             if (DEBUG) {
379                 Slogf.d(TAG, "Cached policy directory %s for policy id %s", policyDir, policyIdStr);
380             }
381         }
382     }
383 
readCpusetCategories()384     private void readCpusetCategories() {
385         File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory);
386         if (cpusetDirs == null) {
387             Slogf.e(TAG, "Missing cpuset directories at %s", mCpusetDir.getAbsolutePath());
388             return;
389         }
390         for (int i = 0; i < cpusetDirs.length; i++) {
391             File dir = cpusetDirs[i];
392             @CpusetCategory int cpusetCategory;
393             switch (dir.getName()) {
394                 case CPUSET_TOP_APP_DIR:
395                     cpusetCategory = FLAG_CPUSET_CATEGORY_TOP_APP;
396                     break;
397                 case CPUSET_BACKGROUND_DIR:
398                     cpusetCategory = FLAG_CPUSET_CATEGORY_BACKGROUND;
399                     break;
400                 default:
401                     // Ignore other cpuset categories because the implementation doesn't support
402                     // monitoring CPU availability for other cpusets.
403                     continue;
404             }
405             File cpuCoresFile = new File(dir.getPath(), CPUS_FILE);
406             IntArray cpuCores = readCpuCores(cpuCoresFile);
407             if (cpuCores == null || cpuCores.size() == 0) {
408                 Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
409                 continue;
410             }
411             for (int j = 0; j < cpuCores.size(); j++) {
412                 int categories = mCpusetCategoriesByCpus.get(cpuCores.get(j));
413                 categories |= cpusetCategory;
414                 mCpusetCategoriesByCpus.append(cpuCores.get(j), categories);
415                 if (DEBUG) {
416                     Slogf.d(TAG, "Mapping CPU core id %d with cpuset categories [%s]",
417                             cpuCores.get(j), toCpusetCategoriesStr(categories));
418                 }
419             }
420         }
421     }
422 
readStaticPolicyInfo()423     private void readStaticPolicyInfo() {
424         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
425             int policyId = mCpuFreqPolicyDirsById.keyAt(i);
426             File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
427             File cpuCoresFile = new File(policyDir, RELATED_CPUS_FILE);
428             IntArray relatedCpuCores = readCpuCores(cpuCoresFile);
429             if (relatedCpuCores == null || relatedCpuCores.size() == 0) {
430                 Slogf.e(TAG, "Failed to read related CPU cores from %s",
431                         cpuCoresFile.getAbsolutePath());
432                 continue;
433             }
434             StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(relatedCpuCores);
435             mStaticPolicyInfoById.append(policyId, staticPolicyInfo);
436             if (DEBUG) {
437                 Slogf.d(TAG, "Added static policy info %s for policy id %d", staticPolicyInfo,
438                         policyId);
439             }
440         }
441     }
442 
readDynamicPolicyInfo()443     private SparseArray<DynamicPolicyInfo> readDynamicPolicyInfo() {
444         SparseArray<DynamicPolicyInfo> dynamicPolicyInfoById = new SparseArray<>();
445         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
446             int policyId = mCpuFreqPolicyDirsById.keyAt(i);
447             File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
448             long curCpuFreqKHz = readCpuFreqKHz(new File(policyDir, CUR_SCALING_FREQ_FILE));
449             if (curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
450                 Slogf.w(TAG, "Missing current frequency information at %s",
451                         policyDir.getAbsolutePath());
452                 continue;
453             }
454             long avgTimeInStateCpuFreqKHz = readAvgTimeInStateCpuFrequency(policyId, policyDir);
455             File cpuCoresFile = new File(policyDir, AFFECTED_CPUS_FILE);
456             IntArray affectedCpuCores = readCpuCores(cpuCoresFile);
457             if (affectedCpuCores == null || affectedCpuCores.size() == 0) {
458                 Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
459                 continue;
460             }
461             long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
462             if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
463                 Slogf.w(TAG, "Missing max CPU frequency information at %s",
464                         policyDir.getAbsolutePath());
465                 continue;
466             }
467             DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz,
468                     maxCpuFreqKHz, avgTimeInStateCpuFreqKHz, affectedCpuCores);
469             dynamicPolicyInfoById.append(policyId, dynamicPolicyInfo);
470             if (DEBUG) {
471                 Slogf.d(TAG, "Read dynamic policy info %s for policy id %d", dynamicPolicyInfo,
472                         policyId);
473             }
474         }
475         return dynamicPolicyInfoById;
476     }
477 
readAvgTimeInStateCpuFrequency(int policyId, File policyDir)478     private long readAvgTimeInStateCpuFrequency(int policyId, File policyDir) {
479         LongSparseLongArray latestTimeInState = readTimeInState(policyDir);
480         if (latestTimeInState == null || latestTimeInState.size() == 0) {
481             return CpuInfo.MISSING_FREQUENCY;
482         }
483         LongSparseLongArray prevTimeInState = mTimeInStateByPolicyId.get(policyId);
484         if (prevTimeInState == null) {
485             mTimeInStateByPolicyId.put(policyId, latestTimeInState);
486             if (DEBUG) {
487                 Slogf.d(TAG, "Added aggregated time in state info for policy id %d", policyId);
488             }
489             return calculateAvgCpuFreq(latestTimeInState);
490         }
491         LongSparseLongArray deltaTimeInState = calculateDeltaTimeInState(prevTimeInState,
492                 latestTimeInState);
493         mTimeInStateByPolicyId.put(policyId, latestTimeInState);
494         if (DEBUG) {
495             Slogf.d(TAG, "Added latest delta time in state info for policy id %d", policyId);
496         }
497         return calculateAvgCpuFreq(deltaTimeInState);
498     }
499 
500     @Nullable
readTimeInState(File policyDir)501     private LongSparseLongArray readTimeInState(File policyDir) {
502         if (!mHasTimeInStateFile) {
503             return null;
504         }
505         File timeInStateFile = new File(policyDir, TIME_IN_STATE_FILE);
506         try {
507             List<String> lines = Files.readAllLines(timeInStateFile.toPath());
508             if (lines.isEmpty()) {
509                 Slogf.w(TAG, "Empty time in state file at %s", timeInStateFile.getAbsolutePath());
510                 return null;
511             }
512             LongSparseLongArray cpuTimeByFrequencies = new LongSparseLongArray();
513             for (int i = 0; i < lines.size(); i++) {
514                 Matcher m = TIME_IN_STATE_PATTERN.matcher(lines.get(i).trim());
515                 if (!m.find()) {
516                     continue;
517                 }
518                 cpuTimeByFrequencies.put(Long.parseLong(m.group("freqKHz")),
519                         clockTickStrToMillis(m.group("time")));
520             }
521             return cpuTimeByFrequencies;
522         } catch (Exception e) {
523             Slogf.e(TAG, e, "Failed to read CPU time in state from file: %s",
524                     timeInStateFile.getAbsolutePath());
525         }
526         return null;
527     }
528 
readCpuFreqKHz(File file)529     private static long readCpuFreqKHz(File file) {
530         if (!file.exists()) {
531             Slogf.e(TAG, "CPU frequency file %s doesn't exist", file.getAbsolutePath());
532             return CpuInfo.MISSING_FREQUENCY;
533         }
534         try {
535             List<String> lines = Files.readAllLines(file.toPath());
536             if (!lines.isEmpty()) {
537                 long frequency = Long.parseLong(lines.get(0).trim());
538                 return frequency > 0 ? frequency : CpuInfo.MISSING_FREQUENCY;
539             }
540         } catch (Exception e) {
541             Slogf.e(TAG, e, "Failed to read integer content from file: %s", file.getAbsolutePath());
542         }
543         return CpuInfo.MISSING_FREQUENCY;
544     }
545 
calculateDeltaTimeInState( LongSparseLongArray prevTimeInState, LongSparseLongArray latestTimeInState)546     private static LongSparseLongArray calculateDeltaTimeInState(
547             LongSparseLongArray prevTimeInState, LongSparseLongArray latestTimeInState) {
548         int numTimeInStateEntries = latestTimeInState.size();
549         LongSparseLongArray deltaTimeInState = new LongSparseLongArray(numTimeInStateEntries);
550         for (int i = 0; i < numTimeInStateEntries; i++) {
551             long freq = latestTimeInState.keyAt(i);
552             long durationMillis = latestTimeInState.valueAt(i);
553             long prevDurationMillis = prevTimeInState.get(freq);
554             deltaTimeInState.put(freq, durationMillis > prevDurationMillis
555                     ? (durationMillis - prevDurationMillis) : durationMillis);
556         }
557         return deltaTimeInState;
558     }
559 
calculateAvgCpuFreq(LongSparseLongArray timeInState)560     private static long calculateAvgCpuFreq(LongSparseLongArray timeInState) {
561         double totalTimeInState = 0;
562         for (int i = 0; i < timeInState.size(); i++) {
563             totalTimeInState += timeInState.valueAt(i);
564         }
565         if (totalTimeInState == 0) {
566             return CpuInfo.MISSING_FREQUENCY;
567         }
568         double avgFreqKHz = 0;
569         for (int i = 0; i < timeInState.size(); i++) {
570             avgFreqKHz += (timeInState.keyAt(i) * timeInState.valueAt(i)) / totalTimeInState;
571         }
572         return (long) avgFreqKHz;
573     }
574 
575     /**
576      * Reads the list of CPU cores from the given file.
577      *
578      * <p>Reads CPU cores represented in one of the below formats.
579      * <ul>
580      * <li> Single core id. Eg: 1
581      * <li> Core id range. Eg: 1-4
582      * <li> Comma separated values. Eg: 1, 3-5, 7
583      * </ul>
584      */
585     @Nullable
readCpuCores(File file)586     private static IntArray readCpuCores(File file) {
587         if (!file.exists()) {
588             Slogf.e(TAG, "Failed to read CPU cores as the file '%s' doesn't exist",
589                     file.getAbsolutePath());
590             return null;
591         }
592         try {
593             List<String> lines = Files.readAllLines(file.toPath());
594             IntArray cpuCores = new IntArray(0);
595             for (int i = 0; i < lines.size(); i++) {
596                 String line = lines.get(i).trim();
597                 if (line.isEmpty()) {
598                     continue;
599                 }
600                 String[] pairs = line.contains(",") ? line.split(",")
601                         : line.split(" ");
602                 for (int j = 0; j < pairs.length; j++) {
603                     String[] minMaxPairs = pairs[j].split("-");
604                     if (minMaxPairs.length >= 2) {
605                         int min = Integer.parseInt(minMaxPairs[0]);
606                         int max = Integer.parseInt(minMaxPairs[1]);
607                         if (min > max) {
608                             continue;
609                         }
610                         for (int id = min; id <= max; id++) {
611                             cpuCores.add(id);
612                         }
613                     } else if (minMaxPairs.length == 1) {
614                         cpuCores.add(Integer.parseInt(minMaxPairs[0]));
615                     } else {
616                         Slogf.w(TAG, "Invalid CPU core range format %s", pairs[j]);
617                     }
618                 }
619             }
620             return cpuCores;
621         } catch (NumberFormatException e) {
622             Slogf.e(TAG, e, "Failed to read CPU cores from %s due to incorrect file format",
623                     file.getAbsolutePath());
624         } catch (Exception e) {
625             Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath());
626         }
627         return null;
628     }
629 
630     @Nullable
readLatestCpuUsageStats()631     private SparseArray<CpuUsageStats> readLatestCpuUsageStats() {
632         SparseArray<CpuUsageStats> cumulativeCpuUsageStats = readCumulativeCpuUsageStats();
633         if (cumulativeCpuUsageStats.size() == 0) {
634             Slogf.e(TAG, "Failed to read cumulative CPU usage stats");
635             return null;
636         }
637         SparseArray<CpuUsageStats> deltaCpuUsageStats = new SparseArray();
638         for (int i = 0; i < cumulativeCpuUsageStats.size(); i++) {
639             int cpu = cumulativeCpuUsageStats.keyAt(i);
640             CpuUsageStats newStats = cumulativeCpuUsageStats.valueAt(i);
641             CpuUsageStats oldStats = mCumulativeCpuUsageStats.get(cpu);
642             deltaCpuUsageStats.append(cpu, oldStats == null ? newStats : newStats.delta(oldStats));
643         }
644         mCumulativeCpuUsageStats = cumulativeCpuUsageStats;
645         return deltaCpuUsageStats;
646     }
647 
readCumulativeCpuUsageStats()648     private SparseArray<CpuUsageStats> readCumulativeCpuUsageStats() {
649         SparseArray<CpuUsageStats> cpuUsageStats = new SparseArray<>();
650         try {
651             List<String> lines = Files.readAllLines(mProcStatFile.toPath());
652             for (int i = 0; i < lines.size(); i++) {
653                 Matcher m = PROC_STAT_PATTERN.matcher(lines.get(i).trim());
654                 if (!m.find()) {
655                     continue;
656                 }
657                 cpuUsageStats.append(Integer.parseInt(m.group("core")),
658                         new CpuUsageStats(clockTickStrToMillis(m.group("userClockTicks")),
659                                 clockTickStrToMillis(m.group("niceClockTicks")),
660                                 clockTickStrToMillis(m.group("sysClockTicks")),
661                                 clockTickStrToMillis(m.group("idleClockTicks")),
662                                 clockTickStrToMillis(m.group("iowaitClockTicks")),
663                                 clockTickStrToMillis(m.group("irqClockTicks")),
664                                 clockTickStrToMillis(m.group("softirqClockTicks")),
665                                 clockTickStrToMillis(m.group("stealClockTicks")),
666                                 clockTickStrToMillis(m.group("guestClockTicks")),
667                                 clockTickStrToMillis(m.group("guestNiceClockTicks"))));
668             }
669         } catch (Exception e) {
670             Slogf.e(TAG, e, "Failed to read cpu usage stats from %s",
671                     mProcStatFile.getAbsolutePath());
672         }
673         return cpuUsageStats;
674     }
675 
clockTickStrToMillis(String jiffyStr)676     private static long clockTickStrToMillis(String jiffyStr) {
677         return Long.parseLong(jiffyStr) * MILLIS_PER_CLOCK_TICK;
678     }
679 
toCpusetCategoriesStr(int cpusetCategories)680     private static String toCpusetCategoriesStr(int cpusetCategories) {
681         StringBuilder builder = new StringBuilder();
682         if ((cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0) {
683             builder.append("FLAG_CPUSET_CATEGORY_TOP_APP");
684         }
685         if ((cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0) {
686             if (builder.length() > 0) {
687                 builder.append('|');
688             }
689             builder.append("FLAG_CPUSET_CATEGORY_BACKGROUND");
690         }
691         return builder.toString();
692     }
693 
694     /** Contains information for each CPU core on the system. */
695     public static final class CpuInfo {
696         public static final long MISSING_FREQUENCY = 0;
697 
698         public final int cpuCore;
699         @CpusetCategory
700         public final int cpusetCategories;
701         public final boolean isOnline;
702         public final long maxCpuFreqKHz;
703         // Values in the below fields may be missing when a CPU core is offline.
704         public final long curCpuFreqKHz;
705         public final long avgTimeInStateCpuFreqKHz;
706         @Nullable
707         public final CpuUsageStats latestCpuUsageStats;
708 
709         private long mNormalizedAvailableCpuFreqKHz;
710 
CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline, long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, CpuUsageStats latestCpuUsageStats)711         CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline,
712                 long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
713                 CpuUsageStats latestCpuUsageStats) {
714             this(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
715                     avgTimeInStateCpuFreqKHz, /* normalizedAvailableCpuFreqKHz= */ 0,
716                     latestCpuUsageStats);
717             this.mNormalizedAvailableCpuFreqKHz = computeNormalizedAvailableCpuFreqKHz();
718         }
719 
720         // Should be used only for testing.
721         @VisibleForTesting
CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline, long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, long normalizedAvailableCpuFreqKHz, CpuUsageStats latestCpuUsageStats)722         CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline,
723                 long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
724                 long normalizedAvailableCpuFreqKHz, CpuUsageStats latestCpuUsageStats) {
725             this.cpuCore = cpuCore;
726             this.cpusetCategories = cpusetCategories;
727             this.isOnline = isOnline;
728             this.curCpuFreqKHz = curCpuFreqKHz;
729             this.maxCpuFreqKHz = maxCpuFreqKHz;
730             this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
731             this.latestCpuUsageStats = latestCpuUsageStats;
732             this.mNormalizedAvailableCpuFreqKHz = normalizedAvailableCpuFreqKHz;
733         }
734 
getNormalizedAvailableCpuFreqKHz()735         public long getNormalizedAvailableCpuFreqKHz() {
736             return mNormalizedAvailableCpuFreqKHz;
737         }
738 
739         @Override
toString()740         public String toString() {
741             return new StringBuilder("CpuInfo{ cpuCore = ").append(cpuCore)
742                     .append(", cpusetCategories = [")
743                     .append(toCpusetCategoriesStr(cpusetCategories))
744                     .append("], isOnline = ").append(isOnline ? "Yes" : "No")
745                     .append(", curCpuFreqKHz = ")
746                     .append(curCpuFreqKHz == MISSING_FREQUENCY ? "missing" : curCpuFreqKHz)
747                     .append(", maxCpuFreqKHz = ")
748                     .append(maxCpuFreqKHz == MISSING_FREQUENCY ? "missing" : maxCpuFreqKHz)
749                     .append(", avgTimeInStateCpuFreqKHz = ")
750                     .append(avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY ? "missing"
751                             : avgTimeInStateCpuFreqKHz)
752                     .append(", latestCpuUsageStats = ").append(latestCpuUsageStats)
753                     .append(", mNormalizedAvailableCpuFreqKHz = ")
754                     .append(mNormalizedAvailableCpuFreqKHz)
755                     .append(" }").toString();
756         }
757 
758         @Override
equals(Object obj)759         public boolean equals(Object obj) {
760             if (this == obj) {
761                 return true;
762             }
763             if (!(obj instanceof CpuInfo)) {
764                 return false;
765             }
766             CpuInfo other = (CpuInfo) obj;
767             return cpuCore == other.cpuCore && cpusetCategories == other.cpusetCategories
768                     && isOnline == other.isOnline  && curCpuFreqKHz == other.curCpuFreqKHz
769                     && maxCpuFreqKHz == other.maxCpuFreqKHz
770                     && avgTimeInStateCpuFreqKHz == other.avgTimeInStateCpuFreqKHz
771                     && latestCpuUsageStats.equals(other.latestCpuUsageStats)
772                     && mNormalizedAvailableCpuFreqKHz == other.mNormalizedAvailableCpuFreqKHz;
773         }
774 
775         @Override
hashCode()776         public int hashCode() {
777             return Objects.hash(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
778                     avgTimeInStateCpuFreqKHz, latestCpuUsageStats, mNormalizedAvailableCpuFreqKHz);
779         }
780 
computeNormalizedAvailableCpuFreqKHz()781         private long computeNormalizedAvailableCpuFreqKHz() {
782             if (!isOnline) {
783                 return MISSING_FREQUENCY;
784             }
785             long totalTimeMillis = latestCpuUsageStats.getTotalTimeMillis();
786             if (totalTimeMillis == 0) {
787                 Slogf.wtf(TAG, "Total CPU time millis is 0. This shouldn't happen unless stats are"
788                         + " polled too frequently");
789                 return MISSING_FREQUENCY;
790             }
791             double nonIdlePercent = 100.0 * (totalTimeMillis
792                     - (double) latestCpuUsageStats.idleTimeMillis) / totalTimeMillis;
793             long curFreqKHz = avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY
794                     ? curCpuFreqKHz : avgTimeInStateCpuFreqKHz;
795             double availablePercent = 100.0 - (nonIdlePercent * curFreqKHz / maxCpuFreqKHz);
796             return (long) ((availablePercent * maxCpuFreqKHz) / 100.0);
797         }
798     }
799 
800     /** CPU time spent in different modes. */
801     public static final class CpuUsageStats {
802         public final long userTimeMillis;
803         public final long niceTimeMillis;
804         public final long systemTimeMillis;
805         public final long idleTimeMillis;
806         public final long iowaitTimeMillis;
807         public final long irqTimeMillis;
808         public final long softirqTimeMillis;
809         public final long stealTimeMillis;
810         public final long guestTimeMillis;
811         public final long guestNiceTimeMillis;
812 
CpuUsageStats(long userTimeMillis, long niceTimeMillis, long systemTimeMillis, long idleTimeMillis, long iowaitTimeMillis, long irqTimeMillis, long softirqTimeMillis, long stealTimeMillis, long guestTimeMillis, long guestNiceTimeMillis)813         public CpuUsageStats(long userTimeMillis, long niceTimeMillis, long systemTimeMillis,
814                 long idleTimeMillis, long iowaitTimeMillis, long irqTimeMillis,
815                 long softirqTimeMillis, long stealTimeMillis, long guestTimeMillis,
816                 long guestNiceTimeMillis) {
817             this.userTimeMillis = userTimeMillis;
818             this.niceTimeMillis = niceTimeMillis;
819             this.systemTimeMillis = systemTimeMillis;
820             this.idleTimeMillis = idleTimeMillis;
821             this.iowaitTimeMillis = iowaitTimeMillis;
822             this.irqTimeMillis = irqTimeMillis;
823             this.softirqTimeMillis = softirqTimeMillis;
824             this.stealTimeMillis = stealTimeMillis;
825             this.guestTimeMillis = guestTimeMillis;
826             this.guestNiceTimeMillis = guestNiceTimeMillis;
827         }
828 
getTotalTimeMillis()829         public long getTotalTimeMillis() {
830             return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis
831                     + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis
832                     + guestTimeMillis + guestNiceTimeMillis;
833         }
834 
835         @Override
toString()836         public String toString() {
837             return new StringBuilder("CpuUsageStats{ userTimeMillis = ")
838                     .append(userTimeMillis)
839                     .append(", niceTimeMillis = ").append(niceTimeMillis)
840                     .append(", systemTimeMillis = ").append(systemTimeMillis)
841                     .append(", idleTimeMillis = ").append(idleTimeMillis)
842                     .append(", iowaitTimeMillis = ").append(iowaitTimeMillis)
843                     .append(", irqTimeMillis = ").append(irqTimeMillis)
844                     .append(", softirqTimeMillis = ").append(softirqTimeMillis)
845                     .append(", stealTimeMillis = ").append(stealTimeMillis)
846                     .append(", guestTimeMillis = ").append(guestTimeMillis)
847                     .append(", guestNiceTimeMillis = ").append(guestNiceTimeMillis)
848                     .append(" }").toString();
849         }
850 
851         @Override
equals(Object obj)852         public boolean equals(Object obj) {
853             if (this == obj) {
854                 return true;
855             }
856             if (!(obj instanceof CpuUsageStats)) {
857                 return false;
858             }
859             CpuUsageStats other = (CpuUsageStats) obj;
860             return userTimeMillis == other.userTimeMillis && niceTimeMillis == other.niceTimeMillis
861                     && systemTimeMillis == other.systemTimeMillis
862                     && idleTimeMillis == other.idleTimeMillis
863                     && iowaitTimeMillis == other.iowaitTimeMillis
864                     && irqTimeMillis == other.irqTimeMillis
865                     && softirqTimeMillis == other.softirqTimeMillis
866                     && stealTimeMillis == other.stealTimeMillis
867                     && guestTimeMillis == other.guestTimeMillis
868                     && guestNiceTimeMillis == other.guestNiceTimeMillis;
869         }
870 
871         @Override
hashCode()872         public int hashCode() {
873             return Objects.hash(userTimeMillis, niceTimeMillis, systemTimeMillis, idleTimeMillis,
874                     iowaitTimeMillis, irqTimeMillis, softirqTimeMillis, stealTimeMillis,
875                     guestTimeMillis,
876                     guestNiceTimeMillis);
877         }
878 
delta(CpuUsageStats rhs)879         CpuUsageStats delta(CpuUsageStats rhs) {
880             return new CpuUsageStats(diff(userTimeMillis, rhs.userTimeMillis),
881                     diff(niceTimeMillis, rhs.niceTimeMillis),
882                     diff(systemTimeMillis, rhs.systemTimeMillis),
883                     diff(idleTimeMillis, rhs.idleTimeMillis),
884                     diff(iowaitTimeMillis, rhs.iowaitTimeMillis),
885                     diff(irqTimeMillis, rhs.irqTimeMillis),
886                     diff(softirqTimeMillis, rhs.softirqTimeMillis),
887                     diff(stealTimeMillis, rhs.stealTimeMillis),
888                     diff(guestTimeMillis, rhs.guestTimeMillis),
889                     diff(guestNiceTimeMillis, rhs.guestNiceTimeMillis));
890         }
891 
diff(long lhs, long rhs)892         private static long diff(long lhs, long rhs) {
893             return lhs > rhs ? lhs - rhs : 0;
894         }
895     }
896 
897     private static final class StaticPolicyInfo {
898         public final IntArray relatedCpuCores;
899 
StaticPolicyInfo(IntArray relatedCpuCores)900         StaticPolicyInfo(IntArray relatedCpuCores) {
901             this.relatedCpuCores = relatedCpuCores;
902         }
903 
904         @Override
toString()905         public String toString() {
906             return "StaticPolicyInfo{relatedCpuCores = " + relatedCpuCores + '}';
907         }
908     }
909 
910     private static final class DynamicPolicyInfo {
911         public final long curCpuFreqKHz;
912         public final long maxCpuFreqKHz;
913         public final long avgTimeInStateCpuFreqKHz;
914         public final IntArray affectedCpuCores;
915 
DynamicPolicyInfo(long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, IntArray affectedCpuCores)916         DynamicPolicyInfo(long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
917                 IntArray affectedCpuCores) {
918             this.curCpuFreqKHz = curCpuFreqKHz;
919             this.maxCpuFreqKHz = maxCpuFreqKHz;
920             this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
921             this.affectedCpuCores = affectedCpuCores;
922         }
923 
924         @Override
toString()925         public String toString() {
926             return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz
927                     + ", maxCpuFreqKHz = " + maxCpuFreqKHz
928                     + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
929                     + ", affectedCpuCores = " + affectedCpuCores + '}';
930         }
931     }
932 }
933