Start/Stop CPU monitoring based on the client callback addition/removal.
- Start CPU monitoring when the first client callback is added. - Stop CPU monitoring when the last client callback is removed. - When on client callbacks are available, perform debug monitoring only user-debug and eng builds. - When a client callback is added, notify the client of the latest CPU availability and the current monitoring interval. Test: atest CpuMonitorServiceTest Bug: 242722241 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5182a35f25e43edd5e979432ad76bbde989c8a1d) Merged-In: I6d423ee165ad6e0623f6078e3c68cc4fd1871371 Change-Id: I6d423ee165ad6e0623f6078e3c68cc4fd1871371
This commit is contained in:
committed by
Cherrypicker Worker
parent
17a7c61a43
commit
b9e7f3dd87
@@ -41,6 +41,7 @@ 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;
|
||||
@@ -52,17 +53,18 @@ import java.io.PrintWriter;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/** 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);
|
||||
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 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.
|
||||
*/
|
||||
@@ -85,6 +87,7 @@ public final class CpuMonitorService extends SystemService {
|
||||
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();
|
||||
@@ -94,8 +97,6 @@ public final class CpuMonitorService extends SystemService {
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<CpusetInfo> mCpusetInfosByCpuset;
|
||||
private final Runnable mMonitorCpuStats = this::monitorCpuStats;
|
||||
private final NotifyCpuAvailabilityFunctor mNotifyCpuAvailabilityFunctor =
|
||||
new NotifyCpuAvailabilityFunctor();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
|
||||
@@ -107,29 +108,24 @@ 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) {
|
||||
// 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);
|
||||
callbackInfo = mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset,
|
||||
callback);
|
||||
if (callbackInfo != null) {
|
||||
Slogf.i(TAG, "Overwriting the existing %s", callbackInfo);
|
||||
}
|
||||
}
|
||||
CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(config,
|
||||
callback, executor);
|
||||
mAvailabilityCallbackInfosByCallbacksByCpuset.add(config.cpuset, callback,
|
||||
callbackInfo);
|
||||
if (DEBUG) {
|
||||
Slogf.d(TAG, "Added a CPU availability callback: %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
|
||||
@@ -143,6 +139,7 @@ public final class CpuMonitorService extends SystemService {
|
||||
if (DEBUG) {
|
||||
Slogf.d(TAG, "Successfully removed %s", callbackInfo);
|
||||
}
|
||||
checkAndStopMonitoringLocked();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -154,19 +151,20 @@ public final class CpuMonitorService extends SystemService {
|
||||
|
||||
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);
|
||||
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 debugMonitoringIntervalMillis,
|
||||
long latestAvailabilityDurationMillis) {
|
||||
boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
|
||||
long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mHandlerThread = handlerThread;
|
||||
mShouldDebugMonitor = shouldDebugMonitor;
|
||||
mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
|
||||
mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
|
||||
mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
|
||||
mCpuInfoReader = cpuInfoReader;
|
||||
@@ -202,12 +200,24 @@ public final class CpuMonitorService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@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("Current CPU monitoring interval: %d ms\n",
|
||||
writer.printf("mCurrentMonitoringIntervalMillis = %d\n",
|
||||
mCurrentMonitoringIntervalMillis);
|
||||
if (hasClientCallbacksLocked()) {
|
||||
writer.println("CPU availability change callbacks:");
|
||||
@@ -230,6 +240,11 @@ public final class CpuMonitorService extends SystemService {
|
||||
|
||||
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
|
||||
@@ -262,7 +277,7 @@ public final class CpuMonitorService extends SystemService {
|
||||
}
|
||||
|
||||
// TODO(b/267500110): Detect heavy CPU load. On detecting heavy CPU load, increase
|
||||
// the monitoring interval and notify the clients.
|
||||
// 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.
|
||||
@@ -280,34 +295,94 @@ public final class CpuMonitorService extends SystemService {
|
||||
private void checkClientThresholdsAndNotifyLocked(CpusetInfo cpusetInfo) {
|
||||
int prevAvailabilityPercent = cpusetInfo.getPrevCpuAvailabilityPercent();
|
||||
CpuAvailabilityInfo latestAvailabilityInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
|
||||
ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
|
||||
CpuAvailabilityCallbackInfo> callbackMap =
|
||||
mAvailabilityCallbackInfosByCallbacksByCpuset.get(cpusetInfo.cpuset);
|
||||
if (latestAvailabilityInfo == null || prevAvailabilityPercent < 0
|
||||
|| callbackMap.isEmpty()) {
|
||||
|| 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 < callbackMap.size(); i++) {
|
||||
CpuAvailabilityCallbackInfo callbackInfo = callbackMap.valueAt(i);
|
||||
if (didCrossAnyThreshold(prevAvailabilityPercent,
|
||||
latestAvailabilityInfo.latestAvgAvailabilityPercent,
|
||||
callbackInfo.config.getThresholds())) {
|
||||
asyncNotifyCpuAvailabilityToClient(latestAvailabilityInfo, callbackInfo);
|
||||
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(() -> mNotifyCpuAvailabilityFunctor.accept(callbackInfo.callback,
|
||||
availabilityInfo));
|
||||
mHandler.post(callbackInfo.notifyCpuAvailabilityChangeRunnable);
|
||||
} else {
|
||||
callbackInfo.executor.execute(() -> mNotifyCpuAvailabilityFunctor.accept(
|
||||
callbackInfo.callback, availabilityInfo));
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,13 +445,24 @@ public final class CpuMonitorService extends SystemService {
|
||||
}
|
||||
|
||||
private static final class CpuAvailabilityCallbackInfo {
|
||||
public final CpuMonitorService service;
|
||||
public final CpuAvailabilityMonitoringConfig config;
|
||||
public final CpuMonitorInternal.CpuAvailabilityCallback callback;
|
||||
@Nullable
|
||||
public final Executor executor;
|
||||
public final Runnable notifyMonitoringIntervalChangeRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onMonitoringIntervalChanged(service.getCurrentMonitoringIntervalMillis());
|
||||
}
|
||||
};
|
||||
public final NotifyCpuAvailabilityChangeRunnable notifyCpuAvailabilityChangeRunnable =
|
||||
new NotifyCpuAvailabilityChangeRunnable();
|
||||
|
||||
CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
|
||||
CpuAvailabilityCallbackInfo(CpuMonitorService service,
|
||||
CpuAvailabilityMonitoringConfig config,
|
||||
CpuMonitorInternal.CpuAvailabilityCallback callback, @Nullable Executor executor) {
|
||||
this.service = service;
|
||||
this.config = config;
|
||||
this.callback = callback;
|
||||
this.executor = executor;
|
||||
@@ -387,6 +473,25 @@ public final class CpuMonitorService extends SystemService {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CpuMonitorBinder extends Binder {
|
||||
@@ -563,13 +668,4 @@ public final class CpuMonitorService extends SystemService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NotifyCpuAvailabilityFunctor implements
|
||||
BiConsumer<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityInfo> {
|
||||
@Override
|
||||
public void accept(CpuMonitorInternal.CpuAvailabilityCallback callback,
|
||||
CpuAvailabilityInfo availabilityInfo) {
|
||||
callback.onAvailabilityChanged(availabilityInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,105 +17,659 @@
|
||||
package com.android.server.cpu;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
import static com.android.server.cpu.CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
|
||||
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
|
||||
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
|
||||
import static com.android.server.cpu.CpuInfoReader.CpuInfo.MISSING_FREQUENCY;
|
||||
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
|
||||
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
|
||||
import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerExecutor;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.ArraySet;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.server.ExtendedMockitoRule;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.Watchdog;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.OngoingStubbing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class CpuMonitorServiceTest {
|
||||
private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
|
||||
private static final String TAG = CpuMonitorServiceTest.class.getSimpleName();
|
||||
private static final String USER_BUILD_TAG = TAG + "UserBuild";
|
||||
private static final long ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS =
|
||||
TimeUnit.SECONDS.toMillis(1);
|
||||
private static final long HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS =
|
||||
TimeUnit.SECONDS.toMillis(5);
|
||||
private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100;
|
||||
private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150;
|
||||
private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300;
|
||||
private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET =
|
||||
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
|
||||
.addThreshold(30).addThreshold(70).build();
|
||||
|
||||
private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
|
||||
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
|
||||
.addThreshold(10).addThreshold(90).build();
|
||||
private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_BG_CPUSET =
|
||||
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_BACKGROUND)
|
||||
.addThreshold(50).addThreshold(90).build();
|
||||
private static final List<StaticCpuInfo> STATIC_CPU_INFOS = List.of(
|
||||
new StaticCpuInfo(/* cpuCore= */ 0,
|
||||
/* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
|
||||
/* maxCpuFreqKHz= */ 4000),
|
||||
new StaticCpuInfo(/* cpuCore= */ 1,
|
||||
/* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
|
||||
/* maxCpuFreqKHz= */ 3000),
|
||||
new StaticCpuInfo(/* cpuCore= */ 2, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
|
||||
| FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
|
||||
new StaticCpuInfo(/* cpuCore= */ 3, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
|
||||
| FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
|
||||
new StaticCpuInfo(/* cpuCore= */ 4, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
|
||||
| FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 2000));
|
||||
private static final ArraySet<Integer> NO_OFFLINE_CORES = new ArraySet<>();
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
private Context mMockContext;
|
||||
@Mock
|
||||
private CpuInfoReader mMockCpuInfoReader;
|
||||
@Captor
|
||||
private ArgumentCaptor<CpuAvailabilityInfo> mCpuAvailabilityInfoCaptor;
|
||||
private HandlerThread mServiceHandlerThread;
|
||||
private Handler mServiceHandler;
|
||||
private CpuMonitorService mService;
|
||||
private HandlerExecutor mHandlerExecutor;
|
||||
private CpuMonitorInternal mLocalService;
|
||||
|
||||
@Rule
|
||||
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
|
||||
.mockStatic(ServiceManager.class)
|
||||
.mockStatic(Watchdog.class)
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mService = new CpuMonitorService(mContext);
|
||||
mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
|
||||
public void setUp() throws Exception {
|
||||
mServiceHandlerThread = new HandlerThread(TAG);
|
||||
mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread,
|
||||
/* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
|
||||
TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
|
||||
|
||||
doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
|
||||
anyBoolean(), anyInt()));
|
||||
mService.onStart();
|
||||
mLocalService = LocalServices.getService(CpuMonitorInternal.class);
|
||||
doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
|
||||
when(mMockCpuInfoReader.init()).thenReturn(true);
|
||||
when(mMockCpuInfoReader.readCpuInfos()).thenReturn(new SparseArray<>());
|
||||
|
||||
startService();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// The CpuMonitorInternal.class service is added by the mService.onStart call.
|
||||
// Remove the service to ensure the setUp procedure can add this service again.
|
||||
public void tearDown() throws Exception {
|
||||
terminateService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
|
||||
CpuMonitorInternal.CpuAvailabilityCallback.class);
|
||||
|
||||
mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
|
||||
TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
|
||||
|
||||
assertWithMessage("Monitoring interval after adding a client callback")
|
||||
.that(mService.getCurrentMonitoringIntervalMillis())
|
||||
.isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
|
||||
|
||||
// Monitoring interval changed notification is sent asynchronously from the handler thread.
|
||||
// So, sync with this thread before verifying the client call.
|
||||
syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
|
||||
.onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
|
||||
|
||||
verify(mockCallback, never()).onAvailabilityChanged(any());
|
||||
|
||||
mLocalService.removeCpuAvailabilityCallback(mockCallback);
|
||||
|
||||
assertWithMessage("Monitoring interval after removing all client callbacks")
|
||||
.that(mService.getCurrentMonitoringIntervalMillis())
|
||||
.isEqualTo(TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveCpuAvailabilityCallbackOnUserBuild() throws Exception {
|
||||
// The default service instantiated during test setUp has the debug monitoring enabled.
|
||||
// But on a user build, debug monitoring is disabled. So, replace the default service with
|
||||
// an equivalent user build service.
|
||||
replaceServiceWithUserBuildService();
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
|
||||
CpuMonitorInternal.CpuAvailabilityCallback.class);
|
||||
|
||||
mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
|
||||
TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
|
||||
|
||||
assertWithMessage("Monitoring interval after adding a client callback")
|
||||
.that(mService.getCurrentMonitoringIntervalMillis())
|
||||
.isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
|
||||
|
||||
// Monitoring interval changed notification is sent asynchronously from the handler thread.
|
||||
// So, sync with this thread before verifying the client call.
|
||||
syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
|
||||
.onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
|
||||
|
||||
verify(mockCallback, never()).onAvailabilityChanged(any());
|
||||
|
||||
mLocalService.removeCpuAvailabilityCallback(mockCallback);
|
||||
|
||||
assertWithMessage("Monitoring interval after removing all client callbacks")
|
||||
.that(mService.getCurrentMonitoringIntervalMillis())
|
||||
.isEqualTo(DEFAULT_MONITORING_INTERVAL_MILLISECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveInvalidCpuAvailabilityCallback() throws Exception {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
|
||||
CpuMonitorInternal.CpuAvailabilityCallback.class);
|
||||
|
||||
mLocalService.removeCpuAvailabilityCallback(mockCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveCpuAvailabilityCallbackOnAddingFirstCallback() throws Exception {
|
||||
// Debug monitoring is in progress but the default {@link CpuInfoReader.CpuInfo} returned by
|
||||
// the {@link CpuInfoReader.readCpuInfos} is empty, so the client won't be notified when
|
||||
// adding a callback. Inject {@link CpuInfoReader.CpuInfo}, so the client callback is
|
||||
// notified on adding a callback.
|
||||
injectCpuInfosAndWait(List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
|
||||
NO_OFFLINE_CORES)));
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveCpuAvailabilityCallbackOnAddingMultipleCallbacks() throws Exception {
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
|
||||
|
||||
injectCpuInfosAndWait(List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
|
||||
NO_OFFLINE_CORES)));
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrossCpuAvailabilityThresholdsWithSingleCallback() throws Exception {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
injectCpuInfosAndWait(List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
|
||||
NO_OFFLINE_CORES)));
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 30,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 45,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 82,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 57,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrossCpuAvailabilityThresholdsWithMultipleCallbacks() throws Exception {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
|
||||
|
||||
injectCpuInfosAndWait(List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
|
||||
NO_OFFLINE_CORES)));
|
||||
|
||||
verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 75,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 55,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 15,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 60,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
|
||||
.isEqualTo(expected);
|
||||
|
||||
ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
|
||||
ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
|
||||
|
||||
verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
|
||||
.onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
|
||||
|
||||
actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
|
||||
expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 60,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 36,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 90,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 75,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 15,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 60,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrossCpuAvailabilityThresholdsWithOfflineCores() throws Exception {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
|
||||
|
||||
// Disable one top-app and one all cpuset core.
|
||||
ArraySet<Integer> offlineCoresA = new ArraySet<>();
|
||||
offlineCoresA.add(1);
|
||||
offlineCoresA.add(3);
|
||||
|
||||
// Disable two all cpuset cores.
|
||||
ArraySet<Integer> offlineCoresB = new ArraySet<>();
|
||||
offlineCoresB.add(2);
|
||||
offlineCoresB.add(4);
|
||||
|
||||
injectCpuInfosAndWait(List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f, offlineCoresA),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f, offlineCoresB),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, offlineCoresA),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, offlineCoresB),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f, offlineCoresA),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, offlineCoresB),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
|
||||
offlineCoresA)));
|
||||
|
||||
verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 75,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 55,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 15,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 61,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
|
||||
.isEqualTo(expected);
|
||||
|
||||
ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
|
||||
ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
|
||||
|
||||
verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
|
||||
.onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
|
||||
|
||||
actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
|
||||
expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 60,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 35,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 90,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 75,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 15,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 55,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveCpuAvailabilityCallbacksOnExecutorThread() throws Exception {
|
||||
Handler testHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
assertWithMessage("Test main handler").that(testHandler).isNotNull();
|
||||
|
||||
HandlerExecutor testExecutor = new HandlerExecutor(testHandler);
|
||||
|
||||
assertWithMessage("Test main executor").that(testExecutor).isNotNull();
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
|
||||
addCpuAvailabilityCallback(testHandler, testExecutor,
|
||||
TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
// CPU monitoring is started on the service handler thread. Sync with this thread before
|
||||
// proceeding. Otherwise, debug monitoring may consume the injected CPU infos and cause
|
||||
// the test to be flaky. Because the {@link addCpuAvailabilityCallback} syncs only with
|
||||
// the passed handler, the test must explicitly sync with the service handler.
|
||||
syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
|
||||
|
||||
injectCpuInfosAndWait(testHandler, List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
|
||||
NO_OFFLINE_CORES)));
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 30,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 45,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 82,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 57,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateAddCpuAvailabilityCallback() throws Exception {
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
|
||||
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
|
||||
addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
|
||||
|
||||
injectCpuInfosAndWait(List.of(
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 40.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 80.0f,
|
||||
NO_OFFLINE_CORES),
|
||||
generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 95.0f,
|
||||
NO_OFFLINE_CORES)));
|
||||
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(2))
|
||||
.onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
|
||||
|
||||
List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
|
||||
|
||||
// Verify that the callback is called for the last added monitoring config.
|
||||
List<CpuAvailabilityInfo> expected = List.of(
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 60, MISSING_CPU_AVAILABILITY_PERCENT,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
|
||||
new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
|
||||
/* latestAvgAvailabilityPercent= */ 95,
|
||||
/* pastNMillisAvgAvailabilityPercent= */ 78,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
|
||||
|
||||
assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeavyCpuLoadMonitoring() throws Exception {
|
||||
// TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest.
|
||||
}
|
||||
|
||||
private void startService() {
|
||||
mService.onStart();
|
||||
mServiceHandler = mServiceHandlerThread.getThreadHandler();
|
||||
|
||||
assertWithMessage("Service thread handler").that(mServiceHandler).isNotNull();
|
||||
|
||||
mLocalService = LocalServices.getService(CpuMonitorInternal.class);
|
||||
|
||||
assertWithMessage("CpuMonitorInternal local service").that(mLocalService).isNotNull();
|
||||
}
|
||||
|
||||
private void terminateService() {
|
||||
// The CpuMonitorInternal.class service is added by the {@link CpuMonitorService#onStart}
|
||||
// call. Remove the service to ensure this service can be added again during
|
||||
// the {@link CpuMonitorService#onStart} call.
|
||||
LocalServices.removeServiceForTest(CpuMonitorInternal.class);
|
||||
if (mServiceHandlerThread != null && mServiceHandlerThread.isAlive()) {
|
||||
mServiceHandlerThread.quitSafely();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveCpuAvailabilityCallback() {
|
||||
private void replaceServiceWithUserBuildService() {
|
||||
terminateService();
|
||||
mServiceHandlerThread = new HandlerThread(USER_BUILD_TAG);
|
||||
mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader,
|
||||
mServiceHandlerThread, /* shouldDebugMonitor= */ false,
|
||||
TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
|
||||
TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
|
||||
TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
|
||||
|
||||
startService();
|
||||
}
|
||||
|
||||
private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(
|
||||
CpuAvailabilityMonitoringConfig config) throws Exception {
|
||||
return addCpuAvailabilityCallback(mServiceHandler, /* executor= */ null, config);
|
||||
}
|
||||
|
||||
private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(Handler handler,
|
||||
HandlerExecutor executor, CpuAvailabilityMonitoringConfig config) throws Exception {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
|
||||
CpuMonitorInternal.CpuAvailabilityCallback.class);
|
||||
|
||||
mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
|
||||
TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
|
||||
mLocalService.addCpuAvailabilityCallback(executor, config, mockCallback);
|
||||
|
||||
// TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
|
||||
// {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
|
||||
// Monitoring interval changed notification is sent asynchronously from the given handler.
|
||||
// So, sync with this thread before verifying the client call.
|
||||
syncWithHandler(handler, /* delayMillis= */ 0);
|
||||
|
||||
mLocalService.removeCpuAvailabilityCallback(mockCallback);
|
||||
verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
|
||||
.onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
|
||||
|
||||
return mockCallback;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDuplicateAddCpuAvailabilityCallback() {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
|
||||
CpuMonitorInternal.CpuAvailabilityCallback.class);
|
||||
|
||||
mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
|
||||
TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
|
||||
|
||||
mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
|
||||
TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
|
||||
|
||||
// TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
|
||||
// thresholds cross the bounds specified in the
|
||||
// {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
|
||||
|
||||
mLocalService.removeCpuAvailabilityCallback(mockCallback);
|
||||
private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos)
|
||||
throws Exception {
|
||||
injectCpuInfosAndWait(mServiceHandler, cpuInfos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveInvalidCpuAvailabilityCallback() {
|
||||
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
|
||||
CpuMonitorInternal.CpuAvailabilityCallback.class);
|
||||
private void injectCpuInfosAndWait(Handler handler,
|
||||
List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception {
|
||||
assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty();
|
||||
|
||||
mLocalService.removeCpuAvailabilityCallback(mockCallback);
|
||||
OngoingStubbing<SparseArray<CpuInfoReader.CpuInfo>> ongoingStubbing =
|
||||
when(mMockCpuInfoReader.readCpuInfos());
|
||||
for (SparseArray<CpuInfoReader.CpuInfo> cpuInfo : cpuInfos) {
|
||||
ongoingStubbing = ongoingStubbing.thenReturn(cpuInfo);
|
||||
}
|
||||
|
||||
// CPU infos are read asynchronously on a separate handler thread. So, wait based on
|
||||
// the current monitoring interval and the number of CPU infos were injected.
|
||||
syncWithHandler(handler,
|
||||
/* delayMillis= */ mService.getCurrentMonitoringIntervalMillis() * cpuInfos.size());
|
||||
}
|
||||
|
||||
private void syncWithHandler(Handler handler, long delayMillis) throws Exception {
|
||||
AtomicBoolean didRun = new AtomicBoolean(false);
|
||||
handler.postDelayed(() -> {
|
||||
synchronized (didRun) {
|
||||
didRun.set(true);
|
||||
didRun.notifyAll();
|
||||
}
|
||||
}, delayMillis);
|
||||
synchronized (didRun) {
|
||||
while (!didRun.get()) {
|
||||
didRun.wait(HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SparseArray<CpuInfoReader.CpuInfo> generateCpuInfosForAvailability(
|
||||
double cpuAvailabilityPercent, ArraySet<Integer> offlineCores) {
|
||||
SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>(STATIC_CPU_INFOS.size());
|
||||
for (StaticCpuInfo staticCpuInfo : STATIC_CPU_INFOS) {
|
||||
boolean isOnline = !offlineCores.contains(staticCpuInfo.cpuCore);
|
||||
cpuInfos.append(staticCpuInfo.cpuCore, constructCpuInfo(staticCpuInfo.cpuCore,
|
||||
staticCpuInfo.cpusetCategories, isOnline, staticCpuInfo.maxCpuFreqKHz,
|
||||
cpuAvailabilityPercent));
|
||||
}
|
||||
return cpuInfos;
|
||||
}
|
||||
|
||||
private static CpuInfoReader.CpuInfo constructCpuInfo(int cpuCore,
|
||||
@CpuInfoReader.CpusetCategory int cpusetCategories, boolean isOnline,
|
||||
long maxCpuFreqKHz, double cpuAvailabilityPercent) {
|
||||
long availCpuFreqKHz = (long) (maxCpuFreqKHz * (cpuAvailabilityPercent / 100.0));
|
||||
long curCpuFreqKHz = maxCpuFreqKHz - availCpuFreqKHz;
|
||||
return new CpuInfoReader.CpuInfo(cpuCore, cpusetCategories, isOnline,
|
||||
isOnline ? curCpuFreqKHz : MISSING_FREQUENCY, maxCpuFreqKHz,
|
||||
/* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
|
||||
isOnline ? availCpuFreqKHz : MISSING_FREQUENCY,
|
||||
/* latestCpuUsageStats= */ null);
|
||||
}
|
||||
|
||||
private static final class StaticCpuInfo {
|
||||
public final int cpuCore;
|
||||
public final int cpusetCategories;
|
||||
public final int maxCpuFreqKHz;
|
||||
|
||||
StaticCpuInfo(int cpuCore, @CpuInfoReader.CpusetCategory int cpusetCategories,
|
||||
int maxCpuFreqKHz) {
|
||||
this.cpuCore = cpuCore;
|
||||
this.cpusetCategories = cpusetCategories;
|
||||
this.maxCpuFreqKHz = maxCpuFreqKHz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StaticCpuInfo{cpuCore=" + cpuCore + ", cpusetCategories=" + cpusetCategories
|
||||
+ ", maxCpuFreqKHz=" + maxCpuFreqKHz + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user