Merge changes from topic "cherrypicker-L08300000959200714:N79700001348406342" into udc-dev

* changes:
  Start/Stop CPU monitoring based on the client callback addition/removal.
  Verify CPU availability against client thresholds and notify the clients.
  Monitor CPU availability using CPU frequency stats.
  Update CpuInfoReader to compute normalized available CPU frequency.
This commit is contained in:
TreeHugger Robot
2023-03-17 01:23:28 +00:00
committed by Android (Google) Code Review
6 changed files with 1400 additions and 158 deletions

View File

@@ -21,6 +21,8 @@ import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACK
import com.android.internal.util.Preconditions;
import java.util.Objects;
/** CPU availability information. */
public final class CpuAvailabilityInfo {
/** Constant to indicate missing CPU availability percent. */
@@ -35,29 +37,64 @@ public final class CpuAvailabilityInfo {
@CpuAvailabilityMonitoringConfig.Cpuset
public final int cpuset;
/** Uptime (in milliseconds) when the data in this object was captured. */
public final long dataTimestampUptimeMillis;
/** The latest average CPU availability percent. */
public final int latestAvgAvailabilityPercent;
/** The past N-second average CPU availability percent. */
public final int pastNSecAvgAvailabilityPercent;
/**
* The past N-millisecond average CPU availability percent.
*
* <p>When there is not enough data to calculate the past N-millisecond average, this field will
* contain the value {@link MISSING_CPU_AVAILABILITY_PERCENT}.
*/
public final int pastNMillisAvgAvailabilityPercent;
/** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */
public final int avgAvailabilityDurationSec;
/** The duration over which the {@link pastNMillisAvgAvailabilityPercent} was calculated. */
public final long pastNMillisDuration;
@Override
public String toString() {
return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent="
+ latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent="
+ pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec="
+ avgAvailabilityDurationSec + '}';
return "CpuAvailabilityInfo{" + "cpuset = " + cpuset + ", dataTimestampUptimeMillis = "
+ dataTimestampUptimeMillis + ", latestAvgAvailabilityPercent = "
+ latestAvgAvailabilityPercent + ", pastNMillisAvgAvailabilityPercent = "
+ pastNMillisAvgAvailabilityPercent + ", pastNMillisDuration = "
+ pastNMillisDuration + '}';
}
CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent,
int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CpuAvailabilityInfo)) {
return false;
}
CpuAvailabilityInfo info = (CpuAvailabilityInfo) obj;
return cpuset == info.cpuset && dataTimestampUptimeMillis == info.dataTimestampUptimeMillis
&& latestAvgAvailabilityPercent == info.latestAvgAvailabilityPercent
&& pastNMillisAvgAvailabilityPercent == info.pastNMillisAvgAvailabilityPercent
&& pastNMillisDuration == info.pastNMillisDuration;
}
@Override
public int hashCode() {
return Objects.hash(cpuset, dataTimestampUptimeMillis, latestAvgAvailabilityPercent,
pastNMillisAvgAvailabilityPercent, pastNMillisDuration);
}
CpuAvailabilityInfo(int cpuset, long dataTimestampUptimeMillis,
int latestAvgAvailabilityPercent, int pastNMillisAvgAvailabilityPercent,
long pastNMillisDuration) {
this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND,
"cpuset");
this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent;
this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent;
this.avgAvailabilityDurationSec = avgAvailabilityDurationSec;
this.dataTimestampUptimeMillis =
Preconditions.checkArgumentNonnegative(dataTimestampUptimeMillis);
this.latestAvgAvailabilityPercent = Preconditions.checkArgumentNonnegative(
latestAvgAvailabilityPercent);
this.pastNMillisAvgAvailabilityPercent = pastNMillisAvgAvailabilityPercent;
this.pastNMillisDuration = Preconditions.checkArgumentNonnegative(
pastNMillisDuration);
}
}

View File

@@ -90,8 +90,19 @@ public final class CpuAvailabilityMonitoringConfig {
@Override
public String toString() {
return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds
+ ')';
return "CpuAvailabilityMonitoringConfig{cpuset=" + toCpusetString(cpuset) + ", mThresholds="
+ mThresholds + ')';
}
/** Returns the string equivalent of the provided cpuset. */
public static String toCpusetString(int cpuset) {
switch (cpuset) {
case CPUSET_ALL:
return "CPUSET_ALL";
case CPUSET_BACKGROUND:
return "CPUSET_BACKGROUND";
}
return "Invalid cpuset: " + cpuset;
}
private CpuAvailabilityMonitoringConfig(Builder builder) {

View File

@@ -21,8 +21,10 @@ 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;
@@ -50,6 +52,9 @@ public final class CpuInfoReader {
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";
// TODO(b/263154344): Avoid reading from cpuinfo_cur_freq because non-root users don't have
// read permission for this file. The file permissions are set by the Kernel. Instead, read
// the current frequency only from scaling_cur_freq.
private static final String CUR_CPUFREQ_FILE = "cpuinfo_cur_freq";
private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq";
private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq";
@@ -70,16 +75,18 @@ public final class CpuInfoReader {
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
})
private @interface CpusetCategory{}
/** 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<>();
@@ -90,16 +97,20 @@ public final class CpuInfoReader {
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));
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) {
CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis) {
mCpusetDir = cpusetDir;
mCpuFreqDir = cpuFreqDir;
mProcStatFile = procStatFile;
mMinReadIntervalMillis = minReadIntervalMillis;
}
/**
@@ -167,6 +178,16 @@ public final class CpuInfoReader {
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");
@@ -202,6 +223,12 @@ public final class CpuInfoReader {
+ " policy ID %d", policyId);
continue;
}
if (curFreqKHz > maxFreqKHz) {
Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
+ " (%d) for policy ID (%d). Skipping CPU frequency policy", curFreqKHz,
maxFreqKHz, policyId);
continue;
}
for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
int relatedCpuCore = staticPolicyInfo.relatedCpuCores.get(coreIdx);
CpuInfo prevCpuInfo = cpuInfoByCpus.get(relatedCpuCore);
@@ -241,9 +268,73 @@ public final class CpuInfoReader {
}
}
}
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.
*
@@ -496,6 +587,9 @@ public final class CpuInfoReader {
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;
@@ -624,16 +718,29 @@ public final class CpuInfoReader {
@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 maxCpuFreqKHz;
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;
@@ -641,6 +748,11 @@ public final class CpuInfoReader {
this.maxCpuFreqKHz = maxCpuFreqKHz;
this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
this.latestCpuUsageStats = latestCpuUsageStats;
this.mNormalizedAvailableCpuFreqKHz = normalizedAvailableCpuFreqKHz;
}
public long getNormalizedAvailableCpuFreqKHz() {
return mNormalizedAvailableCpuFreqKHz;
}
@Override
@@ -657,6 +769,8 @@ public final class CpuInfoReader {
.append(avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY ? "missing"
: avgTimeInStateCpuFreqKHz)
.append(", latestCpuUsageStats = ").append(latestCpuUsageStats)
.append(", mNormalizedAvailableCpuFreqKHz = ")
.append(mNormalizedAvailableCpuFreqKHz)
.append(" }").toString();
}
@@ -673,13 +787,32 @@ public final class CpuInfoReader {
&& isOnline == other.isOnline && curCpuFreqKHz == other.curCpuFreqKHz
&& maxCpuFreqKHz == other.maxCpuFreqKHz
&& avgTimeInStateCpuFreqKHz == other.avgTimeInStateCpuFreqKHz
&& latestCpuUsageStats.equals(other.latestCpuUsageStats);
&& latestCpuUsageStats.equals(other.latestCpuUsageStats)
&& mNormalizedAvailableCpuFreqKHz == other.mNormalizedAvailableCpuFreqKHz;
}
@Override
public int hashCode() {
return Objects.hash(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
avgTimeInStateCpuFreqKHz, latestCpuUsageStats);
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);
}
}
@@ -712,7 +845,7 @@ public final class CpuInfoReader {
this.guestNiceTimeMillis = guestNiceTimeMillis;
}
public long getTotalTime() {
public long getTotalTimeMillis() {
return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis
+ iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis
+ guestTimeMillis + guestNiceTimeMillis;
@@ -796,8 +929,8 @@ public final class CpuInfoReader {
@Override
public String toString() {
return "FrequencyPair{cpuFreqKHz=" + cpuFreqKHz + ", scalingFreqKHz=" + scalingFreqKHz
+ '}';
return "FrequencyPair{cpuFreqKHz = " + cpuFreqKHz + ", scalingFreqKHz = "
+ scalingFreqKHz + '}';
}
}
@@ -812,7 +945,7 @@ public final class CpuInfoReader {
@Override
public String toString() {
return "StaticPolicyInfo{maxCpuFreqPair=" + maxCpuFreqPair + ", relatedCpuCores="
return "StaticPolicyInfo{maxCpuFreqPair = " + maxCpuFreqPair + ", relatedCpuCores = "
+ relatedCpuCores + '}';
}
}
@@ -831,9 +964,9 @@ public final class CpuInfoReader {
@Override
public String toString() {
return "DynamicPolicyInfo{curCpuFreqPair=" + curCpuFreqPair
+ ", avgTimeInStateCpuFreqKHz=" + avgTimeInStateCpuFreqKHz
+ ", affectedCpuCores=" + affectedCpuCores + '}';
return "DynamicPolicyInfo{curCpuFreqPair = " + curCpuFreqPair
+ ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
+ ", affectedCpuCores = " + affectedCpuCores + '}';
}
}
}

View File

@@ -18,15 +18,33 @@ package com.android.server.cpu;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.util.ArrayMap;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.utils.PriorityDump;
import com.android.server.utils.Slogf;
@@ -34,28 +52,55 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/** Service to monitor CPU availability and usage. */
public final class CpuMonitorService extends SystemService {
static final String TAG = CpuMonitorService.class.getSimpleName();
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// TODO(b/242722241): Make this a resource overlay property.
// Maintain 3 monitoring intervals:
// * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
// CPU availability is above a threshold (such as at least 10% of CPU is available).
// * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available
// and CPU availability is below a threshold (such as less than 10% of CPU is available).
// * One to poll very less frequently when no callbacks are available and the build is either
// user-debug or eng. This will be useful for debugging in development environment.
static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000;
// TODO(b/267500110): Make these constants resource overlay properties.
/** Default monitoring interval when no monitoring is in progress. */
static final long DEFAULT_MONITORING_INTERVAL_MILLISECONDS = -1;
/** Monitoring interval when callbacks are registered and the CPU load is normal. */
private static final long NORMAL_MONITORING_INTERVAL_MILLISECONDS =
TimeUnit.SECONDS.toMillis(5);
/**
* Monitoring interval when no registered callbacks and the build is either user-debug or eng.
*/
private static final long DEBUG_MONITORING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
/**
* Size of the in-memory cache relative to the current uptime.
*
* On user-debug or eng builds, continuously cache stats with a bigger cache size for debugging
* purposes.
*/
private static final long CACHE_DURATION_MILLISECONDS = Build.IS_USERDEBUG || Build.IS_ENG
? TimeUnit.MINUTES.toMillis(30) : TimeUnit.MINUTES.toMillis(10);
// TODO(b/267500110): Investigate whether this duration should change when the monitoring
// interval is updated. When the CPU is under heavy load, the monitoring will happen less
// frequently. Should this duration be increased as well when this happens?
private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS =
TimeUnit.SECONDS.toMillis(30);
private final Context mContext;
private final HandlerThread mHandlerThread;
private final CpuInfoReader mCpuInfoReader;
private final boolean mShouldDebugMonitor;
private final long mNormalMonitoringIntervalMillis;
private final long mDebugMonitoringIntervalMillis;
private final long mLatestAvailabilityDurationMillis;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo>
mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>();
private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
CpuAvailabilityCallbackInfo> mAvailabilityCallbackInfosByCallbacksByCpuset;
@GuardedBy("mLock")
private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
private final SparseArray<CpusetInfo> mCpusetInfosByCpuset;
private final Runnable mMonitorCpuStats = this::monitorCpuStats;
@GuardedBy("mLock")
private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
private Handler mHandler;
private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
@Override
@@ -63,89 +108,389 @@ public final class CpuMonitorService extends SystemService {
CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
Objects.requireNonNull(callback, "Callback must be non-null");
Objects.requireNonNull(config, "Config must be non-null");
CpuAvailabilityCallbackInfo callbackInfo;
synchronized (mLock) {
if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s",
mCpuAvailabilityCallbackInfoByCallbacks.get(callback));
// TODO(b/242722241): Overwrite any internal cache (will be added in future CLs)
// that maps callbacks based on the CPU availability thresholds.
}
CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
executor);
mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
if (DEBUG) {
Slogf.d(TAG, "Added a CPU availability callback: %s", info);
// Verify all CPUSET entries before adding the callback because this will help
// delete any previously added callback for a different CPUSET.
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
callbackInfo = mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset,
callback);
if (callbackInfo != null) {
Slogf.i(TAG, "Overwriting the existing %s", callbackInfo);
}
}
callbackInfo = newCallbackInfoLocked(config, callback, executor);
}
asyncNotifyMonitoringIntervalChangeToClient(callbackInfo);
if (DEBUG) {
Slogf.d(TAG, "Successfully added %s", callbackInfo);
}
// TODO(b/242722241):
// * On the executor or on the handler thread, call the callback with the latest CPU
// availability info and monitoring interval.
// * Monitor the CPU stats more frequently when the first callback is added.
}
@Override
public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
synchronized (mLock) {
if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
Slogf.i(TAG, "CpuAvailabilityCallback was not previously added."
+ " Ignoring the remove request");
return;
}
CpuAvailabilityCallbackInfo info =
mCpuAvailabilityCallbackInfoByCallbacks.remove(callback);
if (DEBUG) {
Slogf.d(TAG, "Removed a CPU availability callback: %s", info);
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
CpuAvailabilityCallbackInfo callbackInfo =
mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, callback);
if (callbackInfo != null) {
if (DEBUG) {
Slogf.d(TAG, "Successfully removed %s", callbackInfo);
}
checkAndStopMonitoringLocked();
return;
}
}
Slogf.w(TAG, "CpuAvailabilityCallback was not previously added. Ignoring the remove"
+ " request");
}
// TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed.
}
};
public CpuMonitorService(Context context) {
this(context, new CpuInfoReader(), new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true),
Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS,
DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS);
}
@VisibleForTesting
CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
super(context);
mContext = context;
mHandlerThread = handlerThread;
mShouldDebugMonitor = shouldDebugMonitor;
mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
mCpuInfoReader = cpuInfoReader;
mCpusetInfosByCpuset = new SparseArray<>(2);
mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL));
mCpusetInfosByCpuset.append(CPUSET_BACKGROUND, new CpusetInfo(CPUSET_BACKGROUND));
mAvailabilityCallbackInfosByCallbacksByCpuset = new SparseArrayMap<>();
}
@Override
public void onStart() {
// Initialize CPU info reader and perform the first read to make sure the CPU stats are
// readable without any issues.
if (!mCpuInfoReader.init() || mCpuInfoReader.readCpuInfos() == null) {
Slogf.wtf(TAG, "Failed to initialize CPU info reader. This happens when the CPU "
+ "frequency stats are not available or the sysfs interface has changed in "
+ "the Kernel. Cannot monitor CPU without these stats. Terminating CPU monitor "
+ "service");
return;
}
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
publishLocalService(CpuMonitorInternal.class, mLocalService);
publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL);
Watchdog.getInstance().addThread(mHandler);
synchronized (mLock) {
if (mShouldDebugMonitor && !mHandler.hasCallbacks(mMonitorCpuStats)) {
mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
Slogf.i(TAG, "Starting debug monitoring");
mHandler.post(mMonitorCpuStats);
}
}
}
@VisibleForTesting
long getCurrentMonitoringIntervalMillis() {
synchronized (mLock) {
return mCurrentMonitoringIntervalMillis;
}
}
private void doDump(IndentingPrintWriter writer) {
writer.printf("*%s*\n", getClass().getSimpleName());
writer.increaseIndent();
mCpuInfoReader.dump(writer);
writer.printf("mShouldDebugMonitor = %s\n", mShouldDebugMonitor ? "Yes" : "No");
writer.printf("mNormalMonitoringIntervalMillis = %d\n", mNormalMonitoringIntervalMillis);
writer.printf("mDebugMonitoringIntervalMillis = %d\n", mDebugMonitoringIntervalMillis);
writer.printf("mLatestAvailabilityDurationMillis = %d\n",
mLatestAvailabilityDurationMillis);
synchronized (mLock) {
writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
writer.printf("mCurrentMonitoringIntervalMillis = %d\n",
mCurrentMonitoringIntervalMillis);
if (hasClientCallbacksLocked()) {
writer.println("CPU availability change callbacks:");
writer.increaseIndent();
for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) {
writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i),
mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i));
mAvailabilityCallbackInfosByCallbacksByCpuset.forEach(
(callbackInfo) -> writer.printf("%s\n", callbackInfo));
writer.decreaseIndent();
}
if (mCpusetInfosByCpuset.size() > 0) {
writer.println("Cpuset infos:");
writer.increaseIndent();
for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
writer.printf("%s\n", mCpusetInfosByCpuset.valueAt(i));
}
writer.decreaseIndent();
}
}
// TODO(b/242722241): Print the recent past CPU stats.
writer.decreaseIndent();
}
private static final class CpuAvailabilityCallbackInfo {
public final CpuAvailabilityMonitoringConfig config;
public final Executor executor;
private void monitorCpuStats() {
long uptimeMillis = SystemClock.uptimeMillis();
// Remove duplicate callbacks caused by switching form debug to normal monitoring.
// The removal of the duplicate callback done in the {@link newCallbackInfoLocked} method
// may result in a no-op when a duplicate execution of this callback has already started
// on the handler thread.
mHandler.removeCallbacks(mMonitorCpuStats);
SparseArray<CpuInfoReader.CpuInfo> cpuInfosByCoreId = mCpuInfoReader.readCpuInfos();
if (cpuInfosByCoreId == null) {
// This shouldn't happen because the CPU infos are read & verified during
// the {@link onStart} call.
Slogf.wtf(TAG, "Failed to read CPU info from device");
synchronized (mLock) {
stopMonitoringCpuStatsLocked();
}
// Monitoring is stopped but no client callback is removed.
// TODO(b/267500110): Identify whether the clients should be notified about this state.
return;
}
CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
Executor executor) {
synchronized (mLock) {
// 1. Populate the {@link mCpusetInfosByCpuset} with the latest cpuInfo.
for (int i = 0; i < cpuInfosByCoreId.size(); i++) {
CpuInfoReader.CpuInfo cpuInfo = cpuInfosByCoreId.valueAt(i);
for (int j = 0; j < mCpusetInfosByCpuset.size(); j++) {
mCpusetInfosByCpuset.valueAt(j).appendCpuInfo(uptimeMillis, cpuInfo);
}
}
// 2. Verify whether any monitoring thresholds are crossed and notify the corresponding
// clients.
for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
CpusetInfo cpusetInfo = mCpusetInfosByCpuset.valueAt(i);
cpusetInfo.populateLatestCpuAvailabilityInfo(uptimeMillis,
mLatestAvailabilityDurationMillis);
checkClientThresholdsAndNotifyLocked(cpusetInfo);
}
// TODO(b/267500110): Detect heavy CPU load. On detecting heavy CPU load, increase
// the monitoring interval and notify the clients.
// 3. Continue monitoring only when either there is at least one registered client
// callback or debug monitoring is enabled.
if (mCurrentMonitoringIntervalMillis > 0
&& (hasClientCallbacksLocked() || mShouldDebugMonitor)) {
mHandler.postAtTime(mMonitorCpuStats,
uptimeMillis + mCurrentMonitoringIntervalMillis);
} else {
stopMonitoringCpuStatsLocked();
}
}
}
@GuardedBy("mLock")
private void checkClientThresholdsAndNotifyLocked(CpusetInfo cpusetInfo) {
int prevAvailabilityPercent = cpusetInfo.getPrevCpuAvailabilityPercent();
CpuAvailabilityInfo latestAvailabilityInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
if (latestAvailabilityInfo == null || prevAvailabilityPercent < 0
|| mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKey(
cpusetInfo.cpuset) == 0) {
// When either the current or the previous CPU availability percents are
// missing, skip the current cpuset as there is not enough data to verify
// whether the CPU availability has crossed any monitoring threshold.
return;
}
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
for (int j = 0; j < mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(
i); j++) {
CpuAvailabilityCallbackInfo callbackInfo =
mAvailabilityCallbackInfosByCallbacksByCpuset.valueAt(i, j);
if (callbackInfo.config.cpuset != cpusetInfo.cpuset) {
continue;
}
if (didCrossAnyThreshold(prevAvailabilityPercent,
latestAvailabilityInfo.latestAvgAvailabilityPercent,
callbackInfo.config.getThresholds())) {
asyncNotifyCpuAvailabilityToClient(latestAvailabilityInfo, callbackInfo);
}
}
}
}
private void asyncNotifyMonitoringIntervalChangeToClient(
CpuAvailabilityCallbackInfo callbackInfo) {
if (callbackInfo.executor == null) {
mHandler.post(callbackInfo.notifyMonitoringIntervalChangeRunnable);
} else {
callbackInfo.executor.execute(callbackInfo.notifyMonitoringIntervalChangeRunnable);
}
}
private void asyncNotifyCpuAvailabilityToClient(CpuAvailabilityInfo availabilityInfo,
CpuAvailabilityCallbackInfo callbackInfo) {
callbackInfo.notifyCpuAvailabilityChangeRunnable.prepare(availabilityInfo);
if (callbackInfo.executor == null) {
mHandler.post(callbackInfo.notifyCpuAvailabilityChangeRunnable);
} else {
callbackInfo.executor.execute(callbackInfo.notifyCpuAvailabilityChangeRunnable);
}
}
@GuardedBy("mLock")
private CpuAvailabilityCallbackInfo newCallbackInfoLocked(
CpuAvailabilityMonitoringConfig config,
CpuMonitorInternal.CpuAvailabilityCallback callback, Executor executor) {
CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(this, config,
callback, executor);
String cpusetStr = CpuAvailabilityMonitoringConfig.toCpusetString(
callbackInfo.config.cpuset);
CpusetInfo cpusetInfo = mCpusetInfosByCpuset.get(callbackInfo.config.cpuset);
Preconditions.checkState(cpusetInfo != null, "Missing cpuset info for cpuset %s",
cpusetStr);
boolean hasExistingClientCallbacks = hasClientCallbacksLocked();
mAvailabilityCallbackInfosByCallbacksByCpuset.add(callbackInfo.config.cpuset,
callbackInfo.callback, callbackInfo);
if (DEBUG) {
Slogf.d(TAG, "Added a CPU availability callback: %s", callbackInfo);
}
CpuAvailabilityInfo latestInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
if (latestInfo != null) {
asyncNotifyCpuAvailabilityToClient(latestInfo, callbackInfo);
}
if (hasExistingClientCallbacks && mHandler.hasCallbacks(mMonitorCpuStats)) {
return callbackInfo;
}
// Remove existing callbacks to ensure any debug monitoring (if started) is stopped before
// starting normal monitoring.
mHandler.removeCallbacks(mMonitorCpuStats);
mCurrentMonitoringIntervalMillis = mNormalMonitoringIntervalMillis;
mHandler.post(mMonitorCpuStats);
return callbackInfo;
}
@GuardedBy("mLock")
private void checkAndStopMonitoringLocked() {
if (hasClientCallbacksLocked()) {
return;
}
if (mShouldDebugMonitor) {
if (DEBUG) {
Slogf.e(TAG, "Switching to debug monitoring");
}
mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
} else {
stopMonitoringCpuStatsLocked();
}
}
@GuardedBy("mLock")
private boolean hasClientCallbacksLocked() {
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
if (mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(i) > 0) {
return true;
}
}
return false;
}
@GuardedBy("mLock")
private void stopMonitoringCpuStatsLocked() {
mHandler.removeCallbacks(mMonitorCpuStats);
mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
// When the monitoring is stopped, the latest CPU availability info and the snapshots in
// {@code mCpusetInfosByCpuset} will become obsolete soon. So, remove them.
for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
mCpusetInfosByCpuset.valueAt(i).clear();
}
}
private static boolean containsCpuset(@CpuInfoReader.CpusetCategory int cpusetCategories,
@CpuAvailabilityMonitoringConfig.Cpuset int expectedCpuset) {
switch (expectedCpuset) {
case CPUSET_ALL:
return (cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0;
case CPUSET_BACKGROUND:
return (cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0;
default:
Slogf.wtf(TAG, "Provided invalid expectedCpuset %d", expectedCpuset);
}
return false;
}
private static boolean didCrossAnyThreshold(int prevAvailabilityPercent,
int curAvailabilityPercent, IntArray thresholds) {
if (prevAvailabilityPercent == curAvailabilityPercent) {
return false;
}
for (int i = 0; i < thresholds.size(); i++) {
int threshold = thresholds.get(i);
// TODO(b/267500110): Identify whether or not the clients need to be notified when
// the CPU availability jumps too frequently around the provided thresholds.
// A. Should the client be notified twice - once when the availability reaches
// the threshold and once when it moves away (increase/decrease) from the threshold
// immediately?
// B. Should there be some sort of rate-limiting to avoid notifying the client too
// frequently? Should the client be able to config the rate-limit?
if (prevAvailabilityPercent < threshold && curAvailabilityPercent >= threshold) {
return true;
}
if (prevAvailabilityPercent >= threshold && curAvailabilityPercent < threshold) {
return true;
}
}
return false;
}
private static final class CpuAvailabilityCallbackInfo {
public final CpuMonitorService service;
public final CpuAvailabilityMonitoringConfig config;
public final CpuMonitorInternal.CpuAvailabilityCallback callback;
@Nullable
public final Executor executor;
public final Runnable notifyMonitoringIntervalChangeRunnable = new Runnable() {
@Override
public void run() {
callback.onMonitoringIntervalChanged(service.getCurrentMonitoringIntervalMillis());
}
};
public final NotifyCpuAvailabilityChangeRunnable notifyCpuAvailabilityChangeRunnable =
new NotifyCpuAvailabilityChangeRunnable();
CpuAvailabilityCallbackInfo(CpuMonitorService service,
CpuAvailabilityMonitoringConfig config,
CpuMonitorInternal.CpuAvailabilityCallback callback, @Nullable Executor executor) {
this.service = service;
this.config = config;
this.callback = callback;
this.executor = executor;
}
@Override
public String toString() {
return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor
+ '}';
return "CpuAvailabilityCallbackInfo{config = " + config + ", callback = " + callback
+ ", mExecutor = " + executor + '}';
}
private final class NotifyCpuAvailabilityChangeRunnable implements Runnable {
private final Object mLock = new Object();
@GuardedBy("mLock")
private CpuAvailabilityInfo mCpuAvailabilityInfo;
public void prepare(CpuAvailabilityInfo cpuAvailabilityInfo) {
synchronized (mLock) {
mCpuAvailabilityInfo = cpuAvailabilityInfo;
}
}
@Override
public void run() {
synchronized (mLock) {
callback.onAvailabilityChanged(mCpuAvailabilityInfo);
}
}
}
}
@@ -170,4 +515,157 @@ public final class CpuMonitorService extends SystemService {
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
}
private static final class CpusetInfo {
@CpuAvailabilityMonitoringConfig.Cpuset
public final int cpuset;
private final LongSparseArray<Snapshot> mSnapshotsByUptime;
@Nullable
private CpuAvailabilityInfo mLatestCpuAvailabilityInfo;
CpusetInfo(int cpuset) {
this.cpuset = cpuset;
mSnapshotsByUptime = new LongSparseArray<>();
}
public void appendCpuInfo(long uptimeMillis, CpuInfoReader.CpuInfo cpuInfo) {
if (!containsCpuset(cpuInfo.cpusetCategories, cpuset)) {
return;
}
Snapshot currentSnapshot = mSnapshotsByUptime.get(uptimeMillis);
if (currentSnapshot == null) {
currentSnapshot = new Snapshot(uptimeMillis);
mSnapshotsByUptime.append(uptimeMillis, currentSnapshot);
if (mSnapshotsByUptime.size() > 0
&& (uptimeMillis - mSnapshotsByUptime.valueAt(0).uptimeMillis)
> CACHE_DURATION_MILLISECONDS) {
mSnapshotsByUptime.removeAt(0);
}
}
currentSnapshot.appendCpuInfo(cpuInfo);
}
@Nullable
public CpuAvailabilityInfo getLatestCpuAvailabilityInfo() {
return mLatestCpuAvailabilityInfo;
}
public void populateLatestCpuAvailabilityInfo(long currentUptimeMillis,
long latestAvailabilityDurationMillis) {
int numSnapshots = mSnapshotsByUptime.size();
if (numSnapshots == 0) {
mLatestCpuAvailabilityInfo = null;
return;
}
Snapshot latestSnapshot = mSnapshotsByUptime.valueAt(numSnapshots - 1);
if (latestSnapshot.uptimeMillis != currentUptimeMillis) {
// When the cpuset has no stats available for the current polling, the uptime will
// mismatch. When this happens, return {@code null} to avoid returning stale
// information.
if (DEBUG) {
Slogf.d(TAG, "Skipping stale CPU availability information for cpuset %s",
CpuAvailabilityMonitoringConfig.toCpusetString(cpuset));
}
mLatestCpuAvailabilityInfo = null;
return;
}
// Avoid constructing {@link mLatestCpuAvailabilityInfo} if the uptime hasn't changed.
if (mLatestCpuAvailabilityInfo != null
&& mLatestCpuAvailabilityInfo.dataTimestampUptimeMillis
== latestSnapshot.uptimeMillis) {
return;
}
long earliestUptimeMillis = currentUptimeMillis - latestAvailabilityDurationMillis;
mLatestCpuAvailabilityInfo = new CpuAvailabilityInfo(cpuset,
latestSnapshot.uptimeMillis, latestSnapshot.getAverageAvailableCpuFreqPercent(),
getCumulativeAvgAvailabilityPercent(earliestUptimeMillis),
latestAvailabilityDurationMillis);
}
public int getPrevCpuAvailabilityPercent() {
int numSnapshots = mSnapshotsByUptime.size();
if (numSnapshots < 2) {
return -1;
}
return mSnapshotsByUptime.valueAt(numSnapshots - 2).getAverageAvailableCpuFreqPercent();
}
private int getCumulativeAvgAvailabilityPercent(long earliestUptimeMillis) {
long totalAvailableCpuFreqKHz = 0;
long totalOnlineMaxCpuFreqKHz = 0;
int totalAccountedSnapshots = 0;
long earliestSeenUptimeMillis = Long.MAX_VALUE;
for (int i = mSnapshotsByUptime.size() - 1; i >= 0; i--) {
Snapshot snapshot = mSnapshotsByUptime.valueAt(i);
earliestSeenUptimeMillis = snapshot.uptimeMillis;
if (snapshot.uptimeMillis <= earliestUptimeMillis) {
break;
}
totalAccountedSnapshots++;
totalAvailableCpuFreqKHz += snapshot.totalNormalizedAvailableCpuFreqKHz;
totalOnlineMaxCpuFreqKHz += snapshot.totalOnlineMaxCpuFreqKHz;
}
// The cache must have at least 2 snapshots within the given duration and
// the {@link earliestSeenUptimeMillis} must be earlier than (i,e., less than) the given
// {@link earliestUptimeMillis}. Otherwise, the cache doesn't have enough data to
// calculate the cumulative average for the given duration.
// TODO(b/267500110): Investigate whether the cumulative average duration should be
// shrunk when not enough data points are available.
if (earliestSeenUptimeMillis > earliestUptimeMillis || totalAccountedSnapshots < 2) {
return CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
}
return (int) ((totalAvailableCpuFreqKHz * 100.0) / totalOnlineMaxCpuFreqKHz);
}
public void clear() {
mLatestCpuAvailabilityInfo = null;
mSnapshotsByUptime.clear();
}
@Override
public String toString() {
return "CpusetInfo{cpuset = " + CpuAvailabilityMonitoringConfig.toCpusetString(cpuset)
+ ", mSnapshotsByUptime = " + mSnapshotsByUptime
+ ", mLatestCpuAvailabilityInfo = " + mLatestCpuAvailabilityInfo + '}';
}
private static final class Snapshot {
public final long uptimeMillis;
public int totalOnlineCpus;
public int totalOfflineCpus;
public long totalNormalizedAvailableCpuFreqKHz;
public long totalOnlineMaxCpuFreqKHz;
public long totalOfflineMaxCpuFreqKHz;
Snapshot(long uptimeMillis) {
this.uptimeMillis = uptimeMillis;
}
public void appendCpuInfo(CpuInfoReader.CpuInfo cpuInfo) {
if (!cpuInfo.isOnline) {
totalOfflineCpus++;
totalOfflineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
return;
}
++totalOnlineCpus;
totalNormalizedAvailableCpuFreqKHz += cpuInfo.getNormalizedAvailableCpuFreqKHz();
totalOnlineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
}
public int getAverageAvailableCpuFreqPercent() {
return (int) ((totalNormalizedAvailableCpuFreqKHz * 100.0)
/ totalOnlineMaxCpuFreqKHz);
}
@Override
public String toString() {
return "Snapshot{uptimeMillis = " + uptimeMillis + ", totalOnlineCpus = "
+ totalOnlineCpus + ", totalOfflineCpus = " + totalOfflineCpus
+ ", totalNormalizedAvailableCpuFreqKHz = "
+ totalNormalizedAvailableCpuFreqKHz
+ ", totalOnlineMaxCpuFreqKHz = " + totalOnlineMaxCpuFreqKHz
+ ", totalOfflineMaxCpuFreqKHz = " + totalOfflineMaxCpuFreqKHz + '}';
}
}
}
}

View File

@@ -92,6 +92,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
/* normalizedAvailableCpuFreqKHz= */ 2_402_267,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
/* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
/* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -101,6 +102,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
/* normalizedAvailableCpuFreqKHz= */ 2_693_525,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -111,6 +113,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
/* normalizedAvailableCpuFreqKHz= */ 1_901_608,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
/* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
/* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
@@ -121,6 +124,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
/* normalizedAvailableCpuFreqKHz= */ 1_907_125,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
/* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
/* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
@@ -139,6 +143,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
/* normalizedAvailableCpuFreqKHz= */ 2_425_919,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -148,6 +153,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
/* normalizedAvailableCpuFreqKHz= */ 2_403_009,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -158,6 +164,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
/* normalizedAvailableCpuFreqKHz= */ 1_688_209,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
/* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -168,6 +175,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
/* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
@@ -189,6 +197,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 2_253_713,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
/* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
/* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -198,6 +207,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 2_492_687,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -208,6 +218,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 1_788_079,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
/* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
/* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
@@ -218,6 +229,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 1_799_962,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
/* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
/* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
@@ -237,6 +249,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 2323347,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -246,6 +259,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 209111,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -256,6 +270,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 453514,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
/* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -266,6 +281,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
/* normalizedAvailableCpuFreqKHz= */ 37728,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
/* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
@@ -323,38 +339,8 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 3_000_000,
/* maxCpuFreqKHz= */ 1_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
/* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
/* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
/* guestNiceTimeMillis= */ 0)));
expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2,
/* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
/* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
/* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
/* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
/* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
/* guestNiceTimeMillis= */ 0)));
expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2,
/* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
/* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
/* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
/* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
/* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
/* guestNiceTimeMillis= */ 0)));
compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos,
actualCpuInfos);
compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos, actualCpuInfos);
}
@Test
@@ -368,6 +354,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
/* normalizedAvailableCpuFreqKHz= */ 2_402_267,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
/* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
/* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -377,6 +364,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
/* normalizedAvailableCpuFreqKHz= */ 2_693_525,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -393,7 +381,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue();
CpuInfoReader cpuInfoReader = new CpuInfoReader(emptyDir, getCacheFile(
VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR),
getCacheFile(VALID_PROC_STAT));
getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0);
assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse();
@@ -406,7 +394,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
File emptyDir = getCacheFile(EMPTY_DIR);
assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue();
CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR), emptyDir,
getCacheFile(VALID_PROC_STAT));
getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0);
assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse();
@@ -420,12 +408,32 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile())
.isTrue();
CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE));
getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE),
/* minReadIntervalMillis= */0);
assertWithMessage("Cpu infos with empty proc stat").that(cpuInfoReader.readCpuInfos())
.isNull();
}
@Test
public void testReadingTooFrequentlyReturnsLastReadCpuInfos() throws Exception {
CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT),
/* minReadIntervalMillis= */ 60_000);
assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue();
SparseArray<CpuInfoReader.CpuInfo> firstCpuInfos = cpuInfoReader.readCpuInfos();
assertWithMessage("CPU infos first snapshot").that(firstCpuInfos).isNotNull();
assertWithMessage("CPU infos first snapshot size").that(firstCpuInfos.size())
.isGreaterThan(0);
SparseArray<CpuInfoReader.CpuInfo> secondCpuInfos = cpuInfoReader.readCpuInfos();
compareCpuInfos("CPU infos second snapshot", firstCpuInfos, secondCpuInfos);
SparseArray<CpuInfoReader.CpuInfo> thirdCpuInfos = cpuInfoReader.readCpuInfos();
compareCpuInfos("CPU infos third snapshot", firstCpuInfos, thirdCpuInfos);
}
private void compareCpuInfos(String message,
SparseArray<CpuInfoReader.CpuInfo> expected,
SparseArray<CpuInfoReader.CpuInfo> actual) {
@@ -462,7 +470,8 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
private static CpuInfoReader newCpuInfoReader(File cpusetDir, File cpuFreqDir,
File procStatFile) {
CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile);
CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile,
/* minReadIntervalMillis= */ 0);
assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue();
return cpuInfoReader;
}

View File

@@ -17,105 +17,659 @@
package com.android.server.cpu;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.cpu.CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
import static com.android.server.cpu.CpuInfoReader.CpuInfo.MISSING_FREQUENCY;
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ServiceManager;
import android.util.ArraySet;
import android.util.SparseArray;
import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.stubbing.OngoingStubbing;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public final class CpuMonitorServiceTest {
private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
private static final String TAG = CpuMonitorServiceTest.class.getSimpleName();
private static final String USER_BUILD_TAG = TAG + "UserBuild";
private static final long ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS =
TimeUnit.SECONDS.toMillis(1);
private static final long HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS =
TimeUnit.SECONDS.toMillis(5);
private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100;
private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150;
private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300;
private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET =
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
.addThreshold(30).addThreshold(70).build();
private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
.addThreshold(10).addThreshold(90).build();
private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_BG_CPUSET =
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_BACKGROUND)
.addThreshold(50).addThreshold(90).build();
private static final List<StaticCpuInfo> STATIC_CPU_INFOS = List.of(
new StaticCpuInfo(/* cpuCore= */ 0,
/* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
/* maxCpuFreqKHz= */ 4000),
new StaticCpuInfo(/* cpuCore= */ 1,
/* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
/* maxCpuFreqKHz= */ 3000),
new StaticCpuInfo(/* cpuCore= */ 2, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
| FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
new StaticCpuInfo(/* cpuCore= */ 3, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
| FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
new StaticCpuInfo(/* cpuCore= */ 4, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
| FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 2000));
private static final ArraySet<Integer> NO_OFFLINE_CORES = new ArraySet<>();
@Mock
private Context mContext;
private Context mMockContext;
@Mock
private CpuInfoReader mMockCpuInfoReader;
@Captor
private ArgumentCaptor<CpuAvailabilityInfo> mCpuAvailabilityInfoCaptor;
private HandlerThread mServiceHandlerThread;
private Handler mServiceHandler;
private CpuMonitorService mService;
private HandlerExecutor mHandlerExecutor;
private CpuMonitorInternal mLocalService;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.mockStatic(ServiceManager.class)
.mockStatic(Watchdog.class)
.build();
@Before
public void setUp() {
mService = new CpuMonitorService(mContext);
mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
public void setUp() throws Exception {
mServiceHandlerThread = new HandlerThread(TAG);
mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread,
/* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
anyBoolean(), anyInt()));
mService.onStart();
mLocalService = LocalServices.getService(CpuMonitorInternal.class);
doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
when(mMockCpuInfoReader.init()).thenReturn(true);
when(mMockCpuInfoReader.readCpuInfos()).thenReturn(new SparseArray<>());
startService();
}
@After
public void tearDown() {
// The CpuMonitorInternal.class service is added by the mService.onStart call.
// Remove the service to ensure the setUp procedure can add this service again.
public void tearDown() throws Exception {
terminateService();
}
@Test
public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
assertWithMessage("Monitoring interval after adding a client callback")
.that(mService.getCurrentMonitoringIntervalMillis())
.isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
// Monitoring interval changed notification is sent asynchronously from the handler thread.
// So, sync with this thread before verifying the client call.
syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
.onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
verify(mockCallback, never()).onAvailabilityChanged(any());
mLocalService.removeCpuAvailabilityCallback(mockCallback);
assertWithMessage("Monitoring interval after removing all client callbacks")
.that(mService.getCurrentMonitoringIntervalMillis())
.isEqualTo(TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS);
}
@Test
public void testAddRemoveCpuAvailabilityCallbackOnUserBuild() throws Exception {
// The default service instantiated during test setUp has the debug monitoring enabled.
// But on a user build, debug monitoring is disabled. So, replace the default service with
// an equivalent user build service.
replaceServiceWithUserBuildService();
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
assertWithMessage("Monitoring interval after adding a client callback")
.that(mService.getCurrentMonitoringIntervalMillis())
.isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
// Monitoring interval changed notification is sent asynchronously from the handler thread.
// So, sync with this thread before verifying the client call.
syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
.onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
verify(mockCallback, never()).onAvailabilityChanged(any());
mLocalService.removeCpuAvailabilityCallback(mockCallback);
assertWithMessage("Monitoring interval after removing all client callbacks")
.that(mService.getCurrentMonitoringIntervalMillis())
.isEqualTo(DEFAULT_MONITORING_INTERVAL_MILLISECONDS);
}
@Test
public void testRemoveInvalidCpuAvailabilityCallback() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
mLocalService.removeCpuAvailabilityCallback(mockCallback);
}
@Test
public void testReceiveCpuAvailabilityCallbackOnAddingFirstCallback() throws Exception {
// Debug monitoring is in progress but the default {@link CpuInfoReader.CpuInfo} returned by
// the {@link CpuInfoReader.readCpuInfos} is empty, so the client won't be notified when
// adding a callback. Inject {@link CpuInfoReader.CpuInfo}, so the client callback is
// notified on adding a callback.
injectCpuInfosAndWait(List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
NO_OFFLINE_CORES)));
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
}
@Test
public void testReceiveCpuAvailabilityCallbackOnAddingMultipleCallbacks() throws Exception {
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
injectCpuInfosAndWait(List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
NO_OFFLINE_CORES)));
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
}
@Test
public void testCrossCpuAvailabilityThresholdsWithSingleCallback() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
injectCpuInfosAndWait(List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
NO_OFFLINE_CORES)));
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 30,
/* pastNMillisAvgAvailabilityPercent= */ 45,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 82,
/* pastNMillisAvgAvailabilityPercent= */ 57,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
}
@Test
public void testCrossCpuAvailabilityThresholdsWithMultipleCallbacks() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
injectCpuInfosAndWait(List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
NO_OFFLINE_CORES)));
verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 75,
/* pastNMillisAvgAvailabilityPercent= */ 55,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 15,
/* pastNMillisAvgAvailabilityPercent= */ 60,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
.isEqualTo(expected);
ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
.onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
expected = List.of(
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 60,
/* pastNMillisAvgAvailabilityPercent= */ 36,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 90,
/* pastNMillisAvgAvailabilityPercent= */ 75,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 15,
/* pastNMillisAvgAvailabilityPercent= */ 60,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
.isEqualTo(expected);
}
@Test
public void testCrossCpuAvailabilityThresholdsWithOfflineCores() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
// Disable one top-app and one all cpuset core.
ArraySet<Integer> offlineCoresA = new ArraySet<>();
offlineCoresA.add(1);
offlineCoresA.add(3);
// Disable two all cpuset cores.
ArraySet<Integer> offlineCoresB = new ArraySet<>();
offlineCoresB.add(2);
offlineCoresB.add(4);
injectCpuInfosAndWait(List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f, offlineCoresA),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f, offlineCoresB),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, offlineCoresA),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, offlineCoresB),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f, offlineCoresA),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, offlineCoresB),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
offlineCoresA)));
verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 75,
/* pastNMillisAvgAvailabilityPercent= */ 55,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 15,
/* pastNMillisAvgAvailabilityPercent= */ 61,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
.isEqualTo(expected);
ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
.onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
expected = List.of(
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 60,
/* pastNMillisAvgAvailabilityPercent= */ 35,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 90,
/* pastNMillisAvgAvailabilityPercent= */ 75,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 15,
/* pastNMillisAvgAvailabilityPercent= */ 55,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
.isEqualTo(expected);
}
@Test
public void testReceiveCpuAvailabilityCallbacksOnExecutorThread() throws Exception {
Handler testHandler = new Handler(Looper.getMainLooper());
assertWithMessage("Test main handler").that(testHandler).isNotNull();
HandlerExecutor testExecutor = new HandlerExecutor(testHandler);
assertWithMessage("Test main executor").that(testExecutor).isNotNull();
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
addCpuAvailabilityCallback(testHandler, testExecutor,
TEST_MONITORING_CONFIG_ALL_CPUSET);
// CPU monitoring is started on the service handler thread. Sync with this thread before
// proceeding. Otherwise, debug monitoring may consume the injected CPU infos and cause
// the test to be flaky. Because the {@link addCpuAvailabilityCallback} syncs only with
// the passed handler, the test must explicitly sync with the service handler.
syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
injectCpuInfosAndWait(testHandler, List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
NO_OFFLINE_CORES)));
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 30,
/* pastNMillisAvgAvailabilityPercent= */ 45,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 82,
/* pastNMillisAvgAvailabilityPercent= */ 57,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
}
@Test
public void testDuplicateAddCpuAvailabilityCallback() throws Exception {
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
injectCpuInfosAndWait(List.of(
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 40.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 80.0f,
NO_OFFLINE_CORES),
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 95.0f,
NO_OFFLINE_CORES)));
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(2))
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
// Verify that the callback is called for the last added monitoring config.
List<CpuAvailabilityInfo> expected = List.of(
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 60, MISSING_CPU_AVAILABILITY_PERCENT,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
/* latestAvgAvailabilityPercent= */ 95,
/* pastNMillisAvgAvailabilityPercent= */ 78,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
}
@Test
public void testHeavyCpuLoadMonitoring() throws Exception {
// TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest.
}
private void startService() {
mService.onStart();
mServiceHandler = mServiceHandlerThread.getThreadHandler();
assertWithMessage("Service thread handler").that(mServiceHandler).isNotNull();
mLocalService = LocalServices.getService(CpuMonitorInternal.class);
assertWithMessage("CpuMonitorInternal local service").that(mLocalService).isNotNull();
}
private void terminateService() {
// The CpuMonitorInternal.class service is added by the {@link CpuMonitorService#onStart}
// call. Remove the service to ensure this service can be added again during
// the {@link CpuMonitorService#onStart} call.
LocalServices.removeServiceForTest(CpuMonitorInternal.class);
if (mServiceHandlerThread != null && mServiceHandlerThread.isAlive()) {
mServiceHandlerThread.quitSafely();
}
}
@Test
public void testAddRemoveCpuAvailabilityCallback() {
private void replaceServiceWithUserBuildService() {
terminateService();
mServiceHandlerThread = new HandlerThread(USER_BUILD_TAG);
mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader,
mServiceHandlerThread, /* shouldDebugMonitor= */ false,
TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
startService();
}
private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(
CpuAvailabilityMonitoringConfig config) throws Exception {
return addCpuAvailabilityCallback(mServiceHandler, /* executor= */ null, config);
}
private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(Handler handler,
HandlerExecutor executor, CpuAvailabilityMonitoringConfig config) throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
mLocalService.addCpuAvailabilityCallback(executor, config, mockCallback);
// TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
// {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
// Monitoring interval changed notification is sent asynchronously from the given handler.
// So, sync with this thread before verifying the client call.
syncWithHandler(handler, /* delayMillis= */ 0);
mLocalService.removeCpuAvailabilityCallback(mockCallback);
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
.onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
return mockCallback;
}
@Test
public void testDuplicateAddCpuAvailabilityCallback() {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
// TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
// thresholds cross the bounds specified in the
// {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
mLocalService.removeCpuAvailabilityCallback(mockCallback);
private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos)
throws Exception {
injectCpuInfosAndWait(mServiceHandler, cpuInfos);
}
@Test
public void testRemoveInvalidCpuAvailabilityCallback() {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
private void injectCpuInfosAndWait(Handler handler,
List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception {
assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty();
mLocalService.removeCpuAvailabilityCallback(mockCallback);
OngoingStubbing<SparseArray<CpuInfoReader.CpuInfo>> ongoingStubbing =
when(mMockCpuInfoReader.readCpuInfos());
for (SparseArray<CpuInfoReader.CpuInfo> cpuInfo : cpuInfos) {
ongoingStubbing = ongoingStubbing.thenReturn(cpuInfo);
}
// CPU infos are read asynchronously on a separate handler thread. So, wait based on
// the current monitoring interval and the number of CPU infos were injected.
syncWithHandler(handler,
/* delayMillis= */ mService.getCurrentMonitoringIntervalMillis() * cpuInfos.size());
}
private void syncWithHandler(Handler handler, long delayMillis) throws Exception {
AtomicBoolean didRun = new AtomicBoolean(false);
handler.postDelayed(() -> {
synchronized (didRun) {
didRun.set(true);
didRun.notifyAll();
}
}, delayMillis);
synchronized (didRun) {
while (!didRun.get()) {
didRun.wait(HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS);
}
}
}
private static SparseArray<CpuInfoReader.CpuInfo> generateCpuInfosForAvailability(
double cpuAvailabilityPercent, ArraySet<Integer> offlineCores) {
SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>(STATIC_CPU_INFOS.size());
for (StaticCpuInfo staticCpuInfo : STATIC_CPU_INFOS) {
boolean isOnline = !offlineCores.contains(staticCpuInfo.cpuCore);
cpuInfos.append(staticCpuInfo.cpuCore, constructCpuInfo(staticCpuInfo.cpuCore,
staticCpuInfo.cpusetCategories, isOnline, staticCpuInfo.maxCpuFreqKHz,
cpuAvailabilityPercent));
}
return cpuInfos;
}
private static CpuInfoReader.CpuInfo constructCpuInfo(int cpuCore,
@CpuInfoReader.CpusetCategory int cpusetCategories, boolean isOnline,
long maxCpuFreqKHz, double cpuAvailabilityPercent) {
long availCpuFreqKHz = (long) (maxCpuFreqKHz * (cpuAvailabilityPercent / 100.0));
long curCpuFreqKHz = maxCpuFreqKHz - availCpuFreqKHz;
return new CpuInfoReader.CpuInfo(cpuCore, cpusetCategories, isOnline,
isOnline ? curCpuFreqKHz : MISSING_FREQUENCY, maxCpuFreqKHz,
/* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
isOnline ? availCpuFreqKHz : MISSING_FREQUENCY,
/* latestCpuUsageStats= */ null);
}
private static final class StaticCpuInfo {
public final int cpuCore;
public final int cpusetCategories;
public final int maxCpuFreqKHz;
StaticCpuInfo(int cpuCore, @CpuInfoReader.CpusetCategory int cpusetCategories,
int maxCpuFreqKHz) {
this.cpuCore = cpuCore;
this.cpusetCategories = cpusetCategories;
this.maxCpuFreqKHz = maxCpuFreqKHz;
}
@Override
public String toString() {
return "StaticCpuInfo{cpuCore=" + cpuCore + ", cpusetCategories=" + cpusetCategories
+ ", maxCpuFreqKHz=" + maxCpuFreqKHz + '}';
}
}
}