summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Lakshman Annadorai <lakshmana@google.com> 2023-02-01 11:38:57 -0800
committer Cherrypicker Worker <android-build-cherrypicker-worker@google.com> 2023-03-16 23:35:32 +0000
commitceab3a6e8aa8e9d0fb622692bcb35b8b67b1c20f (patch)
tree9718ef0aff48b84192c3579fb83d24c81cabcc2e
parentaabb81ecb58208e7c9151bea6ebc19f6060e7515 (diff)
Monitor CPU availability using CPU frequency stats.
- Implement the core CPU availability monitoring logic by reading the CPU infos with CpuInfoReader. - Start debug monitoring on user-debug and eng builds when no callbacks are registered. - Update CpuAvailabilityInfo to cache the uptime of the cached data. - Update CpuAvailabilityMonitoringConfig to return cpuset string. Test: atest CpuMonitorServiceTest Bug: 242722241 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1a18fd0962728468830cc2f2752c1ede3e0c1473) Merged-In: I6e12c11f243966914079f54df4430d69b797ec1f Change-Id: I6e12c11f243966914079f54df4430d69b797ec1f
-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/CpuMonitorService.java393
3 files changed, 417 insertions, 54 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/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 4eefe5c8cad5..fac8a2f7b168 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -18,15 +18,31 @@ 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.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.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 +50,52 @@ 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;
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // 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;
+ // TODO(b/242722241): Add a constant for normal monitoring interval when callbacks are
+ // registered.
+ /**
+ * 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 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 final SparseArray<CpusetInfo> mCpusetInfosByCpuset;
+ private final Runnable mMonitorCpuStats = this::monitorCpuStats;
+
@GuardedBy("mLock")
- private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
+ private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+ private Handler mHandler;
private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
@Override
@@ -64,17 +104,22 @@ public final class CpuMonitorService extends SystemService {
Objects.requireNonNull(callback, "Callback must be non-null");
Objects.requireNonNull(config, "Config must be non-null");
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.
+ // 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);
+ CpuAvailabilityCallbackInfo callbackInfo =
+ mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, callback);
+ if (callbackInfo != null) {
+ Slogf.i(TAG, "Overwriting the existing %s", callbackInfo);
+ }
}
- CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
+ CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(config,
executor);
- mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
+ mAvailabilityCallbackInfosByCallbacksByCpuset.add(config.cpuset, callback,
+ callbackInfo);
if (DEBUG) {
- Slogf.d(TAG, "Added a CPU availability callback: %s", info);
+ Slogf.d(TAG, "Added a CPU availability callback: %s", callbackInfo);
}
}
// TODO(b/242722241):
@@ -86,58 +131,188 @@ public final class CpuMonitorService extends SystemService {
@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);
+ }
+ 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, DEBUG_MONITORING_INTERVAL_MILLISECONDS,
+ LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+ }
+
+ @VisibleForTesting
+ CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
+ boolean shouldDebugMonitor, long debugMonitoringIntervalMillis,
+ long latestAvailabilityDurationMillis) {
super(context);
mContext = context;
+ mHandlerThread = handlerThread;
+ mShouldDebugMonitor = shouldDebugMonitor;
+ 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);
+ }
+ }
}
private void doDump(IndentingPrintWriter writer) {
writer.printf("*%s*\n", getClass().getSimpleName());
writer.increaseIndent();
+ mCpuInfoReader.dump(writer);
synchronized (mLock) {
- writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
- if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
+ writer.printf("Current CPU monitoring interval: %d ms\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();
+ 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);
+ // TODO(b/242722241): Check CPU availability against thresholds and notify clients.
+ }
+
+ // 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 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 final class CpuAvailabilityCallbackInfo {
public final CpuAvailabilityMonitoringConfig config;
+ @Nullable
public final Executor executor;
CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
- Executor executor) {
+ @Nullable Executor executor) {
this.config = config;
this.executor = executor;
}
@@ -170,4 +345,144 @@ 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);
+ }
+
+ 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);
+ }
+
+ 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 + '}';
+ }
+ }
+ }
}