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