summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2023-03-17 01:23:28 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-03-17 01:23:28 +0000
commit2a523ecc0939dd1dda92d676c3a5127f856f9980 (patch)
tree70160736c8d00173de25e5201a7e158b4858b5ad
parent2e196a41e3d04001d99bc66bb40856053f2197cd (diff)
parentb9e7f3dd8798e45e9da08d6f78801ac03fcd8e3d (diff)
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.
-rw-r--r--services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java63
-rw-r--r--services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java15
-rw-r--r--services/core/java/com/android/server/cpu/CpuInfoReader.java159
-rw-r--r--services/core/java/com/android/server/cpu/CpuMonitorService.java594
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java79
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java616
6 files changed, 1384 insertions, 142 deletions
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
index 06b45bf0fb4b..97507be96d84 100644
--- a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
@@ -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 + '}';
+ }
+
+ @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, int latestAvgAvailabilityPercent,
- int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
+ 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);
}
}
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
index a3c4c9e828b4..cbe02fc09d84 100644
--- a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
@@ -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) {
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index ca97a9847b39..ce68edbb9fa1 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -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 + '}';
}
}
}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 4eefe5c8cad5..df8cfad4ab03 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -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 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;
+ }
+
+ 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(CpuAvailabilityMonitoringConfig config,
- Executor executor) {
+ 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 + '}';
+ }
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index b21478753301..04f6f8b2e9f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -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;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 49a2cc696744..5a5f5256e37d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -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_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<>();
- private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
- new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
- .addThreshold(10).addThreshold(90).build();
-
@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.
- LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+ public void tearDown() throws Exception {
+ terminateService();
}
@Test
- public void testAddRemoveCpuAvailabilityCallback() {
+ public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
- mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
- TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+ 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);
- // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
- // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
+ 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 testDuplicateAddCpuAvailabilityCallback() {
+ 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(mHandlerExecutor,
- TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+ 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);
- mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
- TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
+ // 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);
- // 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.
+ 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() {
+ 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();
+ }
+ }
+
+ 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(executor, config, mockCallback);
+
+ // 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);
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+ .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+ return mockCallback;
+ }
+
+ private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos)
+ throws Exception {
+ injectCpuInfosAndWait(mServiceHandler, cpuInfos);
+ }
+
+ private void injectCpuInfosAndWait(Handler handler,
+ List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception {
+ assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty();
+
+ 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 + '}';
+ }
+ }
}