| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.cpu; |
| |
| import static com.android.server.cpu.CpuMonitorService.DEBUG; |
| import static com.android.server.cpu.CpuMonitorService.TAG; |
| |
| import android.annotation.IntDef; |
| import android.annotation.Nullable; |
| import android.os.SystemClock; |
| import android.system.Os; |
| import android.system.OsConstants; |
| import android.util.IndentingPrintWriter; |
| import android.util.IntArray; |
| import android.util.LongSparseLongArray; |
| import android.util.SparseArray; |
| import android.util.SparseIntArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.utils.Slogf; |
| |
| import java.io.File; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.nio.file.Files; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** Reader to read CPU information from proc and sys fs files exposed by the Kernel. */ |
| public final class CpuInfoReader { |
| static final int FLAG_CPUSET_CATEGORY_TOP_APP = 1 << 0; |
| static final int FLAG_CPUSET_CATEGORY_BACKGROUND = 1 << 1; |
| |
| private static final String CPUFREQ_DIR_PATH = "/sys/devices/system/cpu/cpufreq"; |
| private static final String POLICY_DIR_PREFIX = "policy"; |
| private static final String RELATED_CPUS_FILE = "related_cpus"; |
| private static final String AFFECTED_CPUS_FILE = "affected_cpus"; |
| private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq"; |
| private static final String MAX_SCALING_FREQ_FILE = "scaling_max_freq"; |
| private static final String TIME_IN_STATE_FILE = "stats/time_in_state"; |
| private static final String CPUSET_DIR_PATH = "/dev/cpuset"; |
| private static final String CPUSET_TOP_APP_DIR = "top-app"; |
| private static final String CPUSET_BACKGROUND_DIR = "background"; |
| private static final String CPUS_FILE = "cpus"; |
| private static final String PROC_STAT_FILE_PATH = "/proc/stat"; |
| private static final Pattern PROC_STAT_PATTERN = |
| Pattern.compile("cpu(?<core>[0-9]+)\\s(?<userClockTicks>[0-9]+)\\s" |
| + "(?<niceClockTicks>[0-9]+)\\s(?<sysClockTicks>[0-9]+)\\s" |
| + "(?<idleClockTicks>[0-9]+)\\s(?<iowaitClockTicks>[0-9]+)\\s" |
| + "(?<irqClockTicks>[0-9]+)\\s(?<softirqClockTicks>[0-9]+)\\s" |
| + "(?<stealClockTicks>[0-9]+)\\s(?<guestClockTicks>[0-9]+)\\s" |
| + "(?<guestNiceClockTicks>[0-9]+)"); |
| private static final Pattern TIME_IN_STATE_PATTERN = |
| Pattern.compile("(?<freqKHz>[0-9]+)\\s(?<time>[0-9]+)"); |
| private static final long MILLIS_PER_CLOCK_TICK = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK); |
| private static final long MIN_READ_INTERVAL_MILLISECONDS = 500; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = { |
| FLAG_CPUSET_CATEGORY_TOP_APP, |
| FLAG_CPUSET_CATEGORY_BACKGROUND |
| }) |
| /** package **/ @interface CpusetCategory{} |
| |
| // TODO(b/242722241): Protect updatable variables with a local lock. |
| private final File mCpusetDir; |
| private final long mMinReadIntervalMillis; |
| private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray(); |
| private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>(); |
| private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>(); |
| private final SparseArray<LongSparseLongArray> mTimeInStateByPolicyId = new SparseArray<>(); |
| |
| private File mCpuFreqDir; |
| private File mProcStatFile; |
| private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>(); |
| private boolean mIsEnabled; |
| private boolean mHasTimeInStateFile; |
| private long mLastReadUptimeMillis; |
| private SparseArray<CpuInfo> mLastReadCpuInfos; |
| |
| public CpuInfoReader() { |
| this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH), |
| MIN_READ_INTERVAL_MILLISECONDS); |
| } |
| |
| @VisibleForTesting |
| CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis) { |
| mCpusetDir = cpusetDir; |
| mCpuFreqDir = cpuFreqDir; |
| mProcStatFile = procStatFile; |
| mMinReadIntervalMillis = minReadIntervalMillis; |
| } |
| |
| /** |
| * Initializes CpuInfoReader and returns a boolean to indicate whether the reader is enabled. |
| */ |
| public boolean init() { |
| if (mCpuFreqPolicyDirsById.size() > 0) { |
| Slogf.w(TAG, "Ignoring duplicate CpuInfoReader init request"); |
| return mIsEnabled; |
| } |
| File[] policyDirs = mCpuFreqDir.listFiles( |
| file -> file.isDirectory() && file.getName().startsWith(POLICY_DIR_PREFIX)); |
| if (policyDirs == null || policyDirs.length == 0) { |
| Slogf.w(TAG, "Missing CPU frequency policy directories at %s", |
| mCpuFreqDir.getAbsolutePath()); |
| return false; |
| } |
| populateCpuFreqPolicyDirsById(policyDirs); |
| if (mCpuFreqPolicyDirsById.size() == 0) { |
| Slogf.e(TAG, "Failed to parse CPU frequency policy directory paths: %s", |
| Arrays.toString(policyDirs)); |
| return false; |
| } |
| readStaticPolicyInfo(); |
| if (mStaticPolicyInfoById.size() == 0) { |
| Slogf.e(TAG, "Failed to read static CPU frequency policy info from policy dirs: %s", |
| Arrays.toString(policyDirs)); |
| return false; |
| } |
| if (!mProcStatFile.exists()) { |
| Slogf.e(TAG, "Missing proc stat file at %s", mProcStatFile.getAbsolutePath()); |
| return false; |
| } |
| readCpusetCategories(); |
| if (mCpusetCategoriesByCpus.size() == 0) { |
| Slogf.e(TAG, "Failed to read cpuset information from %s", mCpusetDir.getAbsolutePath()); |
| return false; |
| } |
| // Certain CPU performance scaling drivers, such as intel_pstate, perform their own CPU |
| // frequency transitions and do not supply this information to the Kernel's cpufreq node. |
| // Thus, the `time_in_state` file won't be available on devices running such scaling |
| // drivers. Check the presence of this file only once during init and do not throw error |
| // when this file is missing. The implementation must accommodate such use cases. |
| for (int i = 0; i < mCpuFreqPolicyDirsById.size() && !mHasTimeInStateFile; ++i) { |
| // If all the CPU cores on a policy are offline, this file might be missing for the |
| // policy. Make sure this file is not available on all policies before marking it as |
| // missing. |
| mHasTimeInStateFile |= new File(mCpuFreqPolicyDirsById.valueAt(i), TIME_IN_STATE_FILE) |
| .exists(); |
| } |
| if (!mHasTimeInStateFile) { |
| Slogf.e(TAG, "Time in state file not available for any cpufreq policy"); |
| } |
| mIsEnabled = true; |
| return true; |
| } |
| |
| /** |
| * Reads CPU information from proc and sys fs files exposed by the Kernel. |
| * |
| * @return SparseArray keyed by CPU core ID; {@code null} on error or when disabled. |
| */ |
| @Nullable |
| public SparseArray<CpuInfo> readCpuInfos() { |
| if (!mIsEnabled) { |
| return null; |
| } |
| long uptimeMillis = SystemClock.uptimeMillis(); |
| if (mLastReadUptimeMillis > 0 |
| && uptimeMillis - mLastReadUptimeMillis < mMinReadIntervalMillis) { |
| Slogf.w(TAG, "Skipping reading from device and returning the last read CpuInfos. " |
| + "Last read was %d ms ago, min read interval is %d ms", |
| uptimeMillis - mLastReadUptimeMillis, mMinReadIntervalMillis); |
| return mLastReadCpuInfos; |
| } |
| mLastReadUptimeMillis = uptimeMillis; |
| mLastReadCpuInfos = null; |
| SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats(); |
| if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) { |
| Slogf.e(TAG, "Failed to read latest CPU usage stats"); |
| return null; |
| } |
| SparseArray<DynamicPolicyInfo> dynamicPolicyInfoById = readDynamicPolicyInfo(); |
| if (dynamicPolicyInfoById.size() == 0) { |
| Slogf.e(TAG, "Failed to read dynamic policy infos"); |
| return null; |
| } |
| SparseArray<CpuInfo> cpuInfoByCpus = new SparseArray<>(); |
| for (int i = 0; i < mStaticPolicyInfoById.size(); i++) { |
| int policyId = mStaticPolicyInfoById.keyAt(i); |
| StaticPolicyInfo staticPolicyInfo = mStaticPolicyInfoById.valueAt(i); |
| DynamicPolicyInfo dynamicPolicyInfo = dynamicPolicyInfoById.get(policyId); |
| if (dynamicPolicyInfo == null) { |
| Slogf.w(TAG, "Missing dynamic policy info for policy ID %d", policyId); |
| continue; |
| } |
| if (dynamicPolicyInfo.curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY |
| || staticPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) { |
| Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for" |
| + " policy ID %d", policyId); |
| continue; |
| } |
| if (dynamicPolicyInfo.curCpuFreqKHz > staticPolicyInfo.maxCpuFreqKHz) { |
| Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency" |
| + " (%d) for policy ID (%d). Skipping CPU frequency policy", |
| dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz, policyId); |
| continue; |
| } |
| for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) { |
| int relatedCpuCore = staticPolicyInfo.relatedCpuCores.get(coreIdx); |
| CpuInfo prevCpuInfo = cpuInfoByCpus.get(relatedCpuCore); |
| if (prevCpuInfo != null) { |
| Slogf.wtf(TAG, "CPU info already available for the CPU core %d", |
| relatedCpuCore); |
| if (prevCpuInfo.isOnline) { |
| continue; |
| } |
| } |
| int cpusetCategories = mCpusetCategoriesByCpus.get(relatedCpuCore, -1); |
| if (cpusetCategories < 0) { |
| Slogf.w(TAG, "Missing cpuset information for the CPU core %d", |
| relatedCpuCore); |
| continue; |
| } |
| CpuUsageStats usageStats = cpuUsageStatsByCpus.get(relatedCpuCore); |
| if (dynamicPolicyInfo.affectedCpuCores.indexOf(relatedCpuCore) < 0) { |
| cpuInfoByCpus.append(relatedCpuCore, new CpuInfo(relatedCpuCore, |
| cpusetCategories, /* isOnline= */false, CpuInfo.MISSING_FREQUENCY, |
| staticPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats)); |
| continue; |
| } |
| // If a CPU core is online, it must have the usage stats. When the usage stats is |
| // missing, drop the core's CPU info. |
| if (usageStats == null) { |
| Slogf.w(TAG, "Missing CPU usage information for online CPU core %d", |
| relatedCpuCore); |
| continue; |
| } |
| CpuInfo cpuInfo = new CpuInfo(relatedCpuCore, cpusetCategories, /* isOnline= */true, |
| dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz, |
| dynamicPolicyInfo.avgTimeInStateCpuFreqKHz, usageStats); |
| cpuInfoByCpus.append(relatedCpuCore, cpuInfo); |
| if (DEBUG) { |
| Slogf.d(TAG, "Added %s for CPU core %d", cpuInfo, relatedCpuCore); |
| } |
| } |
| } |
| mLastReadCpuInfos = cpuInfoByCpus; |
| return cpuInfoByCpus; |
| } |
| |
| /** Dumps the current state. */ |
| public void dump(IndentingPrintWriter writer) { |
| writer.printf("*%s*\n", getClass().getSimpleName()); |
| writer.increaseIndent(); // Add intend for the outermost block. |
| |
| writer.printf("mCpusetDir = %s\n", mCpusetDir.getAbsolutePath()); |
| writer.printf("mCpuFreqDir = %s\n", mCpuFreqDir.getAbsolutePath()); |
| writer.printf("mProcStatFile = %s\n", mProcStatFile.getAbsolutePath()); |
| writer.printf("mIsEnabled = %s\n", mIsEnabled); |
| writer.printf("mHasTimeInStateFile = %s\n", mHasTimeInStateFile); |
| writer.printf("mLastReadUptimeMillis = %d\n", mLastReadUptimeMillis); |
| writer.printf("mMinReadIntervalMillis = %d\n", mMinReadIntervalMillis); |
| |
| writer.printf("Cpuset categories by CPU core:\n"); |
| writer.increaseIndent(); |
| for (int i = 0; i < mCpusetCategoriesByCpus.size(); i++) { |
| writer.printf("CPU core id = %d, %s\n", mCpusetCategoriesByCpus.keyAt(i), |
| toCpusetCategoriesStr(mCpusetCategoriesByCpus.valueAt(i))); |
| } |
| writer.decreaseIndent(); |
| |
| writer.println("Cpu frequency policy directories by policy id:"); |
| writer.increaseIndent(); |
| for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) { |
| writer.printf("Policy id = %d, Dir = %s\n", mCpuFreqPolicyDirsById.keyAt(i), |
| mCpuFreqPolicyDirsById.valueAt(i)); |
| } |
| writer.decreaseIndent(); |
| |
| writer.println("Static cpu frequency policy infos by policy id:"); |
| writer.increaseIndent(); |
| for (int i = 0; i < mStaticPolicyInfoById.size(); i++) { |
| writer.printf("Policy id = %d, %s\n", mStaticPolicyInfoById.keyAt(i), |
| mStaticPolicyInfoById.valueAt(i)); |
| } |
| writer.decreaseIndent(); |
| |
| writer.println("Cpu time in frequency state by policy id:"); |
| writer.increaseIndent(); |
| for (int i = 0; i < mTimeInStateByPolicyId.size(); i++) { |
| writer.printf("Policy id = %d, Time(millis) in state by CPU frequency(KHz) = %s\n", |
| mTimeInStateByPolicyId.keyAt(i), mTimeInStateByPolicyId.valueAt(i)); |
| } |
| writer.decreaseIndent(); |
| |
| writer.println("Last read CPU infos:"); |
| writer.increaseIndent(); |
| for (int i = 0; i < mLastReadCpuInfos.size(); i++) { |
| writer.printf("%s\n", mLastReadCpuInfos.valueAt(i)); |
| } |
| writer.decreaseIndent(); |
| |
| writer.println("Latest cumulative CPU usage stats by CPU core:"); |
| writer.increaseIndent(); |
| for (int i = 0; i < mCumulativeCpuUsageStats.size(); i++) { |
| writer.printf("CPU core id = %d, %s\n", mCumulativeCpuUsageStats.keyAt(i), |
| mCumulativeCpuUsageStats.valueAt(i)); |
| } |
| writer.decreaseIndent(); |
| |
| writer.decreaseIndent(); // Remove intend for the outermost block. |
| } |
| |
| /** |
| * Sets the CPU frequency for testing. |
| * |
| * <p>Return {@code true} on success. Otherwise, returns {@code false}. |
| */ |
| @VisibleForTesting |
| boolean setCpuFreqDir(File cpuFreqDir) { |
| File[] cpuFreqPolicyDirs = cpuFreqDir.listFiles( |
| file -> file.isDirectory() && file.getName().startsWith(POLICY_DIR_PREFIX)); |
| if (cpuFreqPolicyDirs == null || cpuFreqPolicyDirs.length == 0) { |
| Slogf.w(TAG, "Failed to set CPU frequency directory. Missing policy directories at %s", |
| cpuFreqDir.getAbsolutePath()); |
| return false; |
| } |
| populateCpuFreqPolicyDirsById(cpuFreqPolicyDirs); |
| int numCpuFreqPolicyDirs = mCpuFreqPolicyDirsById.size(); |
| int numStaticPolicyInfos = mStaticPolicyInfoById.size(); |
| if (numCpuFreqPolicyDirs == 0 || numCpuFreqPolicyDirs != numStaticPolicyInfos) { |
| Slogf.e(TAG, "Failed to set CPU frequency directory to %s. Total CPU frequency " |
| + "policies (%d) under new path is either 0 or not equal to initial " |
| + "total CPU frequency policies. Clearing CPU frequency policy " |
| + "directories", cpuFreqDir.getAbsolutePath(), numCpuFreqPolicyDirs, |
| numStaticPolicyInfos); |
| mCpuFreqPolicyDirsById.clear(); |
| return false; |
| } |
| mCpuFreqDir = cpuFreqDir; |
| return true; |
| } |
| |
| /** |
| * Sets the proc stat file for testing. |
| * |
| * <p>Return true on success. Otherwise, returns false. |
| */ |
| @VisibleForTesting |
| boolean setProcStatFile(File procStatFile) { |
| if (!procStatFile.exists()) { |
| Slogf.e(TAG, "Missing proc stat file at %s", procStatFile.getAbsolutePath()); |
| return false; |
| } |
| mProcStatFile = procStatFile; |
| return true; |
| } |
| |
| private void populateCpuFreqPolicyDirsById(File[] policyDirs) { |
| mCpuFreqPolicyDirsById.clear(); |
| for (int i = 0; i < policyDirs.length; i++) { |
| File policyDir = policyDirs[i]; |
| String policyIdStr = policyDir.getName().substring(POLICY_DIR_PREFIX.length()); |
| if (policyIdStr.isEmpty()) { |
| continue; |
| } |
| mCpuFreqPolicyDirsById.append(Integer.parseInt(policyIdStr), policyDir); |
| if (DEBUG) { |
| Slogf.d(TAG, "Cached policy directory %s for policy id %s", policyDir, policyIdStr); |
| } |
| } |
| } |
| |
| private void readCpusetCategories() { |
| File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory); |
| if (cpusetDirs == null) { |
| Slogf.e(TAG, "Missing cpuset directories at %s", mCpusetDir.getAbsolutePath()); |
| return; |
| } |
| for (int i = 0; i < cpusetDirs.length; i++) { |
| File dir = cpusetDirs[i]; |
| @CpusetCategory int cpusetCategory; |
| switch (dir.getName()) { |
| case CPUSET_TOP_APP_DIR: |
| cpusetCategory = FLAG_CPUSET_CATEGORY_TOP_APP; |
| break; |
| case CPUSET_BACKGROUND_DIR: |
| cpusetCategory = FLAG_CPUSET_CATEGORY_BACKGROUND; |
| break; |
| default: |
| // Ignore other cpuset categories because the implementation doesn't support |
| // monitoring CPU availability for other cpusets. |
| continue; |
| } |
| File cpuCoresFile = new File(dir.getPath(), CPUS_FILE); |
| IntArray cpuCores = readCpuCores(cpuCoresFile); |
| if (cpuCores == null || cpuCores.size() == 0) { |
| Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath()); |
| continue; |
| } |
| for (int j = 0; j < cpuCores.size(); j++) { |
| int categories = mCpusetCategoriesByCpus.get(cpuCores.get(j)); |
| categories |= cpusetCategory; |
| mCpusetCategoriesByCpus.append(cpuCores.get(j), categories); |
| if (DEBUG) { |
| Slogf.d(TAG, "Mapping CPU core id %d with cpuset categories [%s]", |
| cpuCores.get(j), toCpusetCategoriesStr(categories)); |
| } |
| } |
| } |
| } |
| |
| private void readStaticPolicyInfo() { |
| for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) { |
| int policyId = mCpuFreqPolicyDirsById.keyAt(i); |
| File policyDir = mCpuFreqPolicyDirsById.valueAt(i); |
| long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE)); |
| if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) { |
| Slogf.w(TAG, "Missing max CPU frequency information at %s", |
| policyDir.getAbsolutePath()); |
| continue; |
| } |
| File cpuCoresFile = new File(policyDir, RELATED_CPUS_FILE); |
| IntArray relatedCpuCores = readCpuCores(cpuCoresFile); |
| if (relatedCpuCores == null || relatedCpuCores.size() == 0) { |
| Slogf.e(TAG, "Failed to read related CPU cores from %s", |
| cpuCoresFile.getAbsolutePath()); |
| continue; |
| } |
| StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqKHz, |
| relatedCpuCores); |
| mStaticPolicyInfoById.append(policyId, staticPolicyInfo); |
| if (DEBUG) { |
| Slogf.d(TAG, "Added static policy info %s for policy id %d", staticPolicyInfo, |
| policyId); |
| } |
| } |
| } |
| |
| private SparseArray<DynamicPolicyInfo> readDynamicPolicyInfo() { |
| SparseArray<DynamicPolicyInfo> dynamicPolicyInfoById = new SparseArray<>(); |
| for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) { |
| int policyId = mCpuFreqPolicyDirsById.keyAt(i); |
| File policyDir = mCpuFreqPolicyDirsById.valueAt(i); |
| long curCpuFreqKHz = readCpuFreqKHz(new File(policyDir, CUR_SCALING_FREQ_FILE)); |
| if (curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) { |
| Slogf.w(TAG, "Missing current frequency information at %s", |
| policyDir.getAbsolutePath()); |
| continue; |
| } |
| long avgTimeInStateCpuFreqKHz = readAvgTimeInStateCpuFrequency(policyId, policyDir); |
| File cpuCoresFile = new File(policyDir, AFFECTED_CPUS_FILE); |
| IntArray affectedCpuCores = readCpuCores(cpuCoresFile); |
| if (affectedCpuCores == null || affectedCpuCores.size() == 0) { |
| Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath()); |
| continue; |
| } |
| DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz, |
| avgTimeInStateCpuFreqKHz, affectedCpuCores); |
| dynamicPolicyInfoById.append(policyId, dynamicPolicyInfo); |
| if (DEBUG) { |
| Slogf.d(TAG, "Read dynamic policy info %s for policy id %d", dynamicPolicyInfo, |
| policyId); |
| } |
| } |
| return dynamicPolicyInfoById; |
| } |
| |
| private long readAvgTimeInStateCpuFrequency(int policyId, File policyDir) { |
| LongSparseLongArray latestTimeInState = readTimeInState(policyDir); |
| if (latestTimeInState == null || latestTimeInState.size() == 0) { |
| return CpuInfo.MISSING_FREQUENCY; |
| } |
| LongSparseLongArray prevTimeInState = mTimeInStateByPolicyId.get(policyId); |
| if (prevTimeInState == null) { |
| mTimeInStateByPolicyId.put(policyId, latestTimeInState); |
| if (DEBUG) { |
| Slogf.d(TAG, "Added aggregated time in state info for policy id %d", policyId); |
| } |
| return calculateAvgCpuFreq(latestTimeInState); |
| } |
| LongSparseLongArray deltaTimeInState = calculateDeltaTimeInState(prevTimeInState, |
| latestTimeInState); |
| mTimeInStateByPolicyId.put(policyId, latestTimeInState); |
| if (DEBUG) { |
| Slogf.d(TAG, "Added latest delta time in state info for policy id %d", policyId); |
| } |
| return calculateAvgCpuFreq(deltaTimeInState); |
| } |
| |
| @Nullable |
| private LongSparseLongArray readTimeInState(File policyDir) { |
| if (!mHasTimeInStateFile) { |
| return null; |
| } |
| File timeInStateFile = new File(policyDir, TIME_IN_STATE_FILE); |
| try { |
| List<String> lines = Files.readAllLines(timeInStateFile.toPath()); |
| if (lines.isEmpty()) { |
| Slogf.w(TAG, "Empty time in state file at %s", timeInStateFile.getAbsolutePath()); |
| return null; |
| } |
| LongSparseLongArray cpuTimeByFrequencies = new LongSparseLongArray(); |
| for (int i = 0; i < lines.size(); i++) { |
| Matcher m = TIME_IN_STATE_PATTERN.matcher(lines.get(i).trim()); |
| if (!m.find()) { |
| continue; |
| } |
| cpuTimeByFrequencies.put(Long.parseLong(m.group("freqKHz")), |
| clockTickStrToMillis(m.group("time"))); |
| } |
| return cpuTimeByFrequencies; |
| } catch (Exception e) { |
| Slogf.e(TAG, e, "Failed to read CPU time in state from file: %s", |
| timeInStateFile.getAbsolutePath()); |
| } |
| return null; |
| } |
| |
| private static long readCpuFreqKHz(File file) { |
| if (!file.exists()) { |
| Slogf.e(TAG, "CPU frequency file %s doesn't exist", file.getAbsolutePath()); |
| return CpuInfo.MISSING_FREQUENCY; |
| } |
| try { |
| List<String> lines = Files.readAllLines(file.toPath()); |
| if (!lines.isEmpty()) { |
| long frequency = Long.parseLong(lines.get(0).trim()); |
| return frequency > 0 ? frequency : CpuInfo.MISSING_FREQUENCY; |
| } |
| } catch (Exception e) { |
| Slogf.e(TAG, e, "Failed to read integer content from file: %s", file.getAbsolutePath()); |
| } |
| return CpuInfo.MISSING_FREQUENCY; |
| } |
| |
| private static LongSparseLongArray calculateDeltaTimeInState( |
| LongSparseLongArray prevTimeInState, LongSparseLongArray latestTimeInState) { |
| int numTimeInStateEntries = latestTimeInState.size(); |
| LongSparseLongArray deltaTimeInState = new LongSparseLongArray(numTimeInStateEntries); |
| for (int i = 0; i < numTimeInStateEntries; i++) { |
| long freq = latestTimeInState.keyAt(i); |
| long durationMillis = latestTimeInState.valueAt(i); |
| long prevDurationMillis = prevTimeInState.get(freq); |
| deltaTimeInState.put(freq, durationMillis > prevDurationMillis |
| ? (durationMillis - prevDurationMillis) : durationMillis); |
| } |
| return deltaTimeInState; |
| } |
| |
| private static long calculateAvgCpuFreq(LongSparseLongArray timeInState) { |
| double totalTimeInState = 0; |
| for (int i = 0; i < timeInState.size(); i++) { |
| totalTimeInState += timeInState.valueAt(i); |
| } |
| if (totalTimeInState == 0) { |
| return CpuInfo.MISSING_FREQUENCY; |
| } |
| double avgFreqKHz = 0; |
| for (int i = 0; i < timeInState.size(); i++) { |
| avgFreqKHz += (timeInState.keyAt(i) * timeInState.valueAt(i)) / totalTimeInState; |
| } |
| return (long) avgFreqKHz; |
| } |
| |
| /** |
| * Reads the list of CPU cores from the given file. |
| * |
| * <p>Reads CPU cores represented in one of the below formats. |
| * <ul> |
| * <li> Single core id. Eg: 1 |
| * <li> Core id range. Eg: 1-4 |
| * <li> Comma separated values. Eg: 1, 3-5, 7 |
| * </ul> |
| */ |
| @Nullable |
| private static IntArray readCpuCores(File file) { |
| if (!file.exists()) { |
| Slogf.e(TAG, "Failed to read CPU cores as the file '%s' doesn't exist", |
| file.getAbsolutePath()); |
| return null; |
| } |
| try { |
| List<String> lines = Files.readAllLines(file.toPath()); |
| IntArray cpuCores = new IntArray(0); |
| for (int i = 0; i < lines.size(); i++) { |
| String line = lines.get(i); |
| String[] pairs = line.contains(",") ? line.trim().split(",") |
| : line.trim().split(" "); |
| for (int j = 0; j < pairs.length; j++) { |
| String[] minMaxPairs = pairs[j].split("-"); |
| if (minMaxPairs.length >= 2) { |
| int min = Integer.parseInt(minMaxPairs[0]); |
| int max = Integer.parseInt(minMaxPairs[1]); |
| if (min > max) { |
| continue; |
| } |
| for (int id = min; id <= max; id++) { |
| cpuCores.add(id); |
| } |
| } else if (minMaxPairs.length == 1) { |
| cpuCores.add(Integer.parseInt(minMaxPairs[0])); |
| } else { |
| Slogf.w(TAG, "Invalid CPU core range format %s", pairs[j]); |
| } |
| } |
| } |
| return cpuCores; |
| } catch (Exception e) { |
| Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath()); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private SparseArray<CpuUsageStats> readLatestCpuUsageStats() { |
| SparseArray<CpuUsageStats> cumulativeCpuUsageStats = readCumulativeCpuUsageStats(); |
| if (cumulativeCpuUsageStats.size() == 0) { |
| Slogf.e(TAG, "Failed to read cumulative CPU usage stats"); |
| return null; |
| } |
| SparseArray<CpuUsageStats> deltaCpuUsageStats = new SparseArray(); |
| for (int i = 0; i < cumulativeCpuUsageStats.size(); i++) { |
| int cpu = cumulativeCpuUsageStats.keyAt(i); |
| CpuUsageStats newStats = cumulativeCpuUsageStats.valueAt(i); |
| CpuUsageStats oldStats = mCumulativeCpuUsageStats.get(cpu); |
| deltaCpuUsageStats.append(cpu, oldStats == null ? newStats : newStats.delta(oldStats)); |
| } |
| mCumulativeCpuUsageStats = cumulativeCpuUsageStats; |
| return deltaCpuUsageStats; |
| } |
| |
| private SparseArray<CpuUsageStats> readCumulativeCpuUsageStats() { |
| SparseArray<CpuUsageStats> cpuUsageStats = new SparseArray<>(); |
| try { |
| List<String> lines = Files.readAllLines(mProcStatFile.toPath()); |
| for (int i = 0; i < lines.size(); i++) { |
| Matcher m = PROC_STAT_PATTERN.matcher(lines.get(i).trim()); |
| if (!m.find()) { |
| continue; |
| } |
| cpuUsageStats.append(Integer.parseInt(m.group("core")), |
| new CpuUsageStats(clockTickStrToMillis(m.group("userClockTicks")), |
| clockTickStrToMillis(m.group("niceClockTicks")), |
| clockTickStrToMillis(m.group("sysClockTicks")), |
| clockTickStrToMillis(m.group("idleClockTicks")), |
| clockTickStrToMillis(m.group("iowaitClockTicks")), |
| clockTickStrToMillis(m.group("irqClockTicks")), |
| clockTickStrToMillis(m.group("softirqClockTicks")), |
| clockTickStrToMillis(m.group("stealClockTicks")), |
| clockTickStrToMillis(m.group("guestClockTicks")), |
| clockTickStrToMillis(m.group("guestNiceClockTicks")))); |
| } |
| } catch (Exception e) { |
| Slogf.e(TAG, e, "Failed to read cpu usage stats from %s", |
| mProcStatFile.getAbsolutePath()); |
| } |
| return cpuUsageStats; |
| } |
| |
| private static long clockTickStrToMillis(String jiffyStr) { |
| return Long.parseLong(jiffyStr) * MILLIS_PER_CLOCK_TICK; |
| } |
| |
| private static String toCpusetCategoriesStr(int cpusetCategories) { |
| StringBuilder builder = new StringBuilder(); |
| if ((cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0) { |
| builder.append("FLAG_CPUSET_CATEGORY_TOP_APP"); |
| } |
| if ((cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0) { |
| if (builder.length() > 0) { |
| builder.append('|'); |
| } |
| builder.append("FLAG_CPUSET_CATEGORY_BACKGROUND"); |
| } |
| return builder.toString(); |
| } |
| |
| /** Contains information for each CPU core on the system. */ |
| public static final class CpuInfo { |
| public static final long MISSING_FREQUENCY = 0; |
| |
| public final int cpuCore; |
| @CpusetCategory |
| public final int cpusetCategories; |
| public final boolean isOnline; |
| public final long maxCpuFreqKHz; |
| // Values in the below fields may be missing when a CPU core is offline. |
| public final long curCpuFreqKHz; |
| public final long avgTimeInStateCpuFreqKHz; |
| @Nullable |
| public final CpuUsageStats latestCpuUsageStats; |
| |
| private long mNormalizedAvailableCpuFreqKHz; |
| |
| CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline, |
| long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, |
| CpuUsageStats latestCpuUsageStats) { |
| this(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz, |
| avgTimeInStateCpuFreqKHz, /* normalizedAvailableCpuFreqKHz= */ 0, |
| latestCpuUsageStats); |
| this.mNormalizedAvailableCpuFreqKHz = computeNormalizedAvailableCpuFreqKHz(); |
| } |
| |
| // Should be used only for testing. |
| @VisibleForTesting |
| CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline, |
| long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, |
| long normalizedAvailableCpuFreqKHz, CpuUsageStats latestCpuUsageStats) { |
| this.cpuCore = cpuCore; |
| this.cpusetCategories = cpusetCategories; |
| this.isOnline = isOnline; |
| this.curCpuFreqKHz = curCpuFreqKHz; |
| this.maxCpuFreqKHz = maxCpuFreqKHz; |
| this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz; |
| this.latestCpuUsageStats = latestCpuUsageStats; |
| this.mNormalizedAvailableCpuFreqKHz = normalizedAvailableCpuFreqKHz; |
| } |
| |
| public long getNormalizedAvailableCpuFreqKHz() { |
| return mNormalizedAvailableCpuFreqKHz; |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder("CpuInfo{ cpuCore = ").append(cpuCore) |
| .append(", cpusetCategories = [") |
| .append(toCpusetCategoriesStr(cpusetCategories)) |
| .append("], isOnline = ").append(isOnline ? "Yes" : "No") |
| .append(", curCpuFreqKHz = ") |
| .append(curCpuFreqKHz == MISSING_FREQUENCY ? "missing" : curCpuFreqKHz) |
| .append(", maxCpuFreqKHz = ") |
| .append(maxCpuFreqKHz == MISSING_FREQUENCY ? "missing" : maxCpuFreqKHz) |
| .append(", avgTimeInStateCpuFreqKHz = ") |
| .append(avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY ? "missing" |
| : avgTimeInStateCpuFreqKHz) |
| .append(", latestCpuUsageStats = ").append(latestCpuUsageStats) |
| .append(", mNormalizedAvailableCpuFreqKHz = ") |
| .append(mNormalizedAvailableCpuFreqKHz) |
| .append(" }").toString(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof CpuInfo)) { |
| return false; |
| } |
| CpuInfo other = (CpuInfo) obj; |
| return cpuCore == other.cpuCore && cpusetCategories == other.cpusetCategories |
| && isOnline == other.isOnline && curCpuFreqKHz == other.curCpuFreqKHz |
| && maxCpuFreqKHz == other.maxCpuFreqKHz |
| && avgTimeInStateCpuFreqKHz == other.avgTimeInStateCpuFreqKHz |
| && latestCpuUsageStats.equals(other.latestCpuUsageStats) |
| && mNormalizedAvailableCpuFreqKHz == other.mNormalizedAvailableCpuFreqKHz; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz, |
| avgTimeInStateCpuFreqKHz, latestCpuUsageStats, mNormalizedAvailableCpuFreqKHz); |
| } |
| |
| private long computeNormalizedAvailableCpuFreqKHz() { |
| if (!isOnline) { |
| return MISSING_FREQUENCY; |
| } |
| long totalTimeMillis = latestCpuUsageStats.getTotalTimeMillis(); |
| if (totalTimeMillis == 0) { |
| Slogf.wtf(TAG, "Total CPU time millis is 0. This shouldn't happen unless stats are" |
| + " polled too frequently"); |
| return MISSING_FREQUENCY; |
| } |
| double nonIdlePercent = 100.0 * (totalTimeMillis |
| - (double) latestCpuUsageStats.idleTimeMillis) / totalTimeMillis; |
| long curFreqKHz = avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY |
| ? curCpuFreqKHz : avgTimeInStateCpuFreqKHz; |
| double availablePercent = 100.0 - (nonIdlePercent * curFreqKHz / maxCpuFreqKHz); |
| return (long) ((availablePercent * maxCpuFreqKHz) / 100.0); |
| } |
| } |
| |
| /** CPU time spent in different modes. */ |
| public static final class CpuUsageStats { |
| public final long userTimeMillis; |
| public final long niceTimeMillis; |
| public final long systemTimeMillis; |
| public final long idleTimeMillis; |
| public final long iowaitTimeMillis; |
| public final long irqTimeMillis; |
| public final long softirqTimeMillis; |
| public final long stealTimeMillis; |
| public final long guestTimeMillis; |
| public final long guestNiceTimeMillis; |
| |
| public CpuUsageStats(long userTimeMillis, long niceTimeMillis, long systemTimeMillis, |
| long idleTimeMillis, long iowaitTimeMillis, long irqTimeMillis, |
| long softirqTimeMillis, long stealTimeMillis, long guestTimeMillis, |
| long guestNiceTimeMillis) { |
| this.userTimeMillis = userTimeMillis; |
| this.niceTimeMillis = niceTimeMillis; |
| this.systemTimeMillis = systemTimeMillis; |
| this.idleTimeMillis = idleTimeMillis; |
| this.iowaitTimeMillis = iowaitTimeMillis; |
| this.irqTimeMillis = irqTimeMillis; |
| this.softirqTimeMillis = softirqTimeMillis; |
| this.stealTimeMillis = stealTimeMillis; |
| this.guestTimeMillis = guestTimeMillis; |
| this.guestNiceTimeMillis = guestNiceTimeMillis; |
| } |
| |
| public long getTotalTimeMillis() { |
| return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis |
| + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis |
| + guestTimeMillis + guestNiceTimeMillis; |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder("CpuUsageStats{ userTimeMillis = ") |
| .append(userTimeMillis) |
| .append(", niceTimeMillis = ").append(niceTimeMillis) |
| .append(", systemTimeMillis = ").append(systemTimeMillis) |
| .append(", idleTimeMillis = ").append(idleTimeMillis) |
| .append(", iowaitTimeMillis = ").append(iowaitTimeMillis) |
| .append(", irqTimeMillis = ").append(irqTimeMillis) |
| .append(", softirqTimeMillis = ").append(softirqTimeMillis) |
| .append(", stealTimeMillis = ").append(stealTimeMillis) |
| .append(", guestTimeMillis = ").append(guestTimeMillis) |
| .append(", guestNiceTimeMillis = ").append(guestNiceTimeMillis) |
| .append(" }").toString(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof CpuUsageStats)) { |
| return false; |
| } |
| CpuUsageStats other = (CpuUsageStats) obj; |
| return userTimeMillis == other.userTimeMillis && niceTimeMillis == other.niceTimeMillis |
| && systemTimeMillis == other.systemTimeMillis |
| && idleTimeMillis == other.idleTimeMillis |
| && iowaitTimeMillis == other.iowaitTimeMillis |
| && irqTimeMillis == other.irqTimeMillis |
| && softirqTimeMillis == other.softirqTimeMillis |
| && stealTimeMillis == other.stealTimeMillis |
| && guestTimeMillis == other.guestTimeMillis |
| && guestNiceTimeMillis == other.guestNiceTimeMillis; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(userTimeMillis, niceTimeMillis, systemTimeMillis, idleTimeMillis, |
| iowaitTimeMillis, irqTimeMillis, softirqTimeMillis, stealTimeMillis, |
| guestTimeMillis, |
| guestNiceTimeMillis); |
| } |
| |
| CpuUsageStats delta(CpuUsageStats rhs) { |
| return new CpuUsageStats(diff(userTimeMillis, rhs.userTimeMillis), |
| diff(niceTimeMillis, rhs.niceTimeMillis), |
| diff(systemTimeMillis, rhs.systemTimeMillis), |
| diff(idleTimeMillis, rhs.idleTimeMillis), |
| diff(iowaitTimeMillis, rhs.iowaitTimeMillis), |
| diff(irqTimeMillis, rhs.irqTimeMillis), |
| diff(softirqTimeMillis, rhs.softirqTimeMillis), |
| diff(stealTimeMillis, rhs.stealTimeMillis), |
| diff(guestTimeMillis, rhs.guestTimeMillis), |
| diff(guestNiceTimeMillis, rhs.guestNiceTimeMillis)); |
| } |
| |
| private static long diff(long lhs, long rhs) { |
| return lhs > rhs ? lhs - rhs : 0; |
| } |
| } |
| |
| private static final class StaticPolicyInfo { |
| public final long maxCpuFreqKHz; |
| public final IntArray relatedCpuCores; |
| |
| StaticPolicyInfo(long maxCpuFreqKHz, IntArray relatedCpuCores) { |
| this.maxCpuFreqKHz = maxCpuFreqKHz; |
| this.relatedCpuCores = relatedCpuCores; |
| } |
| |
| @Override |
| public String toString() { |
| return "StaticPolicyInfo{maxCpuFreqKHz = " + maxCpuFreqKHz + ", relatedCpuCores = " |
| + relatedCpuCores + '}'; |
| } |
| } |
| |
| private static final class DynamicPolicyInfo { |
| public final long curCpuFreqKHz; |
| public final long avgTimeInStateCpuFreqKHz; |
| public final IntArray affectedCpuCores; |
| |
| DynamicPolicyInfo(long curCpuFreqKHz, long avgTimeInStateCpuFreqKHz, |
| IntArray affectedCpuCores) { |
| this.curCpuFreqKHz = curCpuFreqKHz; |
| this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz; |
| this.affectedCpuCores = affectedCpuCores; |
| } |
| |
| @Override |
| public String toString() { |
| return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz |
| + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz |
| + ", affectedCpuCores = " + affectedCpuCores + '}'; |
| } |
| } |
| } |