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:
committed by
Android (Google) Code Review
commit
2a523ecc09
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user