diff options
| -rw-r--r-- | services/core/java/com/android/server/cpu/CpuMonitorService.java | 196 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java | 616 |
2 files changed, 731 insertions, 81 deletions
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java index 7e930b4a1ef5..df8cfad4ab03 100644 --- a/services/core/java/com/android/server/cpu/CpuMonitorService.java +++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java @@ -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); - } - } } diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java index 49a2cc696744..5a5f5256e37d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java @@ -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_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<>(); - private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 = - new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) - .addThreshold(10).addThreshold(90).build(); - @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. - LocalServices.removeServiceForTest(CpuMonitorInternal.class); + public void tearDown() throws Exception { + terminateService(); } @Test - public void testAddRemoveCpuAvailabilityCallback() { + public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); - mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, - TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); + 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); - // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and - // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added. + 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 testDuplicateAddCpuAvailabilityCallback() { + 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(mHandlerExecutor, - TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); + 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); - mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, - TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback); + // 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); - // 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. + 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() { + 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(); + } + } + + 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(executor, config, mockCallback); + + // 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); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS)) + .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS); + + return mockCallback; + } + + private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) + throws Exception { + injectCpuInfosAndWait(mServiceHandler, cpuInfos); + } + + private void injectCpuInfosAndWait(Handler handler, + List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception { + assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty(); + + 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 + '}'; + } + } } |