blob: 984ad1dd728821c77014ff3078e6b03ab57d3f26 [file] [log] [blame]
/*
* 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
|| dynamicPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for"
+ " policy ID %d", policyId);
continue;
}
if (dynamicPolicyInfo.curCpuFreqKHz > dynamicPolicyInfo.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, dynamicPolicyInfo.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,
dynamicPolicyInfo.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, dynamicPolicyInfo.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);
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(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;
}
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;
}
DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz,
maxCpuFreqKHz, 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).trim();
if (line.isEmpty()) {
continue;
}
String[] pairs = line.contains(",") ? line.split(",")
: line.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 (NumberFormatException e) {
Slogf.e(TAG, e, "Failed to read CPU cores from %s due to incorrect file format",
file.getAbsolutePath());
} 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 IntArray relatedCpuCores;
StaticPolicyInfo(IntArray relatedCpuCores) {
this.relatedCpuCores = relatedCpuCores;
}
@Override
public String toString() {
return "StaticPolicyInfo{relatedCpuCores = " + relatedCpuCores + '}';
}
}
private static final class DynamicPolicyInfo {
public final long curCpuFreqKHz;
public final long maxCpuFreqKHz;
public final long avgTimeInStateCpuFreqKHz;
public final IntArray affectedCpuCores;
DynamicPolicyInfo(long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
IntArray affectedCpuCores) {
this.curCpuFreqKHz = curCpuFreqKHz;
this.maxCpuFreqKHz = maxCpuFreqKHz;
this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
this.affectedCpuCores = affectedCpuCores;
}
@Override
public String toString() {
return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz
+ ", maxCpuFreqKHz = " + maxCpuFreqKHz
+ ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
+ ", affectedCpuCores = " + affectedCpuCores + '}';
}
}
}