diff options
| author | 2023-02-01 11:38:57 -0800 | |
|---|---|---|
| committer | 2023-03-16 23:35:32 +0000 | |
| commit | ceab3a6e8aa8e9d0fb622692bcb35b8b67b1c20f (patch) | |
| tree | 9718ef0aff48b84192c3579fb83d24c81cabcc2e | |
| parent | aabb81ecb58208e7c9151bea6ebc19f6060e7515 (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
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 + '}'; + } + } + } } |