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:
Lakshman Annadorai
2023-02-01 13:00:05 -08:00
committed by Cherrypicker Worker
parent 17a7c61a43
commit b9e7f3dd87
2 changed files with 744 additions and 94 deletions

View File

@@ -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);
}
}
}

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 + '}';
}
}
}