summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/cpu/CpuMonitorService.java196
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java616
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 + '}';
+ }
+ }
}