Merge changes from topic "CpuMonitorImpl"

* 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.
This commit is contained in:
TreeHugger Robot
2023-03-08 22:10:59 +00:00
committed by Android (Google) Code Review
4 changed files with 1210 additions and 110 deletions

View File

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

View File

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

View File

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

View File

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