Loading services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND; import com.android.internal.util.Preconditions; /** CPU availability information. */ public final class CpuAvailabilityInfo { /** Constant to indicate missing CPU availability percent. */ public static final int MISSING_CPU_AVAILABILITY_PERCENT = -1; /** * The CPUSET whose availability info is recorded in this object. * * <p>The contained value is one of the CPUSET_* constants from the * {@link CpuAvailabilityMonitoringConfig}. */ @CpuAvailabilityMonitoringConfig.Cpuset public final int cpuset; /** The latest average CPU availability percent. */ public final int latestAvgAvailabilityPercent; /** The past N-second average CPU availability percent. */ public final int pastNSecAvgAvailabilityPercent; /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */ public final int avgAvailabilityDurationSec; @Override public String toString() { return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent=" + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent=" + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec=" + avgAvailabilityDurationSec + '}'; } CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent, int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) { this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND, "cpuset"); this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent; this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent; this.avgAvailabilityDurationSec = avgAvailabilityDurationSec; } } services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import android.annotation.IntDef; import android.util.IntArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** CPU availability monitoring config. */ public final class CpuAvailabilityMonitoringConfig { /** Constant to monitor all cpusets. */ public static final int CPUSET_ALL = 1; /** Constant to monitor background cpusets. */ public static final int CPUSET_BACKGROUND = 2; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CPUSET_"}, value = { CPUSET_ALL, CPUSET_BACKGROUND }) public @interface Cpuset { } /** * The CPUSET to monitor. * * <p>The value must be one of the {@code CPUSET_*} constants. */ @Cpuset public final int cpuset; /** * CPU availability percent thresholds. * * <p>CPU availability change notifications are sent when the latest or last N seconds average * CPU availability percent crosses any of these thresholds since the last notification. */ private final IntArray mThresholds; public IntArray getThresholds() { return mThresholds; } /** * Builder for the construction of {@link CpuAvailabilityMonitoringConfig} objects. * * <p>The builder must contain at least one threshold before calling {@link build}. */ public static final class Builder { private final int mCpuset; private final IntArray mThresholds = new IntArray(); public Builder(int cpuset, int... thresholds) { mCpuset = cpuset; for (int threshold : thresholds) { addThreshold(threshold); } } /** Adds the given threshold to the builder object. */ public Builder addThreshold(int threshold) { if (mThresholds.indexOf(threshold) == -1) { mThresholds.add(threshold); } return this; } /** Returns the {@link CpuAvailabilityMonitoringConfig} object. */ public CpuAvailabilityMonitoringConfig build() { return new CpuAvailabilityMonitoringConfig(this); } } @Override public String toString() { return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds + ')'; } private CpuAvailabilityMonitoringConfig(Builder builder) { if (builder.mCpuset != CPUSET_ALL && builder.mCpuset != CPUSET_BACKGROUND) { throw new IllegalStateException("Cpuset must be either CPUSET_ALL (" + CPUSET_ALL + ") or CPUSET_BACKGROUND (" + CPUSET_BACKGROUND + "). Builder contains " + builder.mCpuset); } if (builder.mThresholds.size() == 0) { throw new IllegalStateException("Must provide at least one threshold"); } this.cpuset = builder.mCpuset; this.mThresholds = builder.mThresholds.clone(); } } services/core/java/com/android/server/cpu/CpuMonitorInternal.java 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import android.annotation.CallbackExecutor; import java.util.concurrent.Executor; /** CpuMonitorInternal hosts internal APIs to monitor CPU. */ public abstract class CpuMonitorInternal { /** Callback to get CPU availability change notifications. */ public interface CpuAvailabilityCallback { /** * Called when the CPU availability crosses the provided thresholds. * * <p>Called when the latest or past N-second (which will be specified in the * {@link CpuAvailabilityInfo}) average CPU availability percent has crossed * (either goes above or drop below) the {@link CpuAvailabilityMonitoringConfig#thresholds} * since the last notification. Also called when a callback is added to the service. * * <p>The callback is called at the executor which is specified in * {@link addCpuAvailabilityCallback} or at the service handler thread. * * @param info CPU availability information. */ void onAvailabilityChanged(CpuAvailabilityInfo info); /** * Called when the CPU monitoring interval changes. * * <p>Also called when a callback is added to the service. * * @param intervalMilliseconds CPU monitoring interval in milliseconds. */ void onMonitoringIntervalChanged(long intervalMilliseconds); } /** * Adds the {@link CpuAvailabilityCallback} for the caller. * * <p>When the callback is added, the callback will be called to notify the current CPU * availability and monitoring interval. * * <p>When the client needs to update the {@link config} for a previously added callback, * the client has to remove the callback and add the callback with a new {@link config}. * * @param executor Executor to execute the callback. If an executor is not provided, * the callback will be executed on the service handler thread. * @param config CPU availability monitoring config. * @param callback Callback implementing {@link CpuAvailabilityCallback} * interface. * * @throws IllegalStateException if {@code callback} is already added. */ public abstract void addCpuAvailabilityCallback(@CallbackExecutor Executor executor, CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback); /** * Removes the {@link CpuAvailabilityCallback} for the caller. * * @param callback Callback implementing {@link CpuAvailabilityCallback} * interface. * * @throws IllegalArgumentException if {@code callback} is not previously added. */ public abstract void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback); } services/core/java/com/android/server/cpu/CpuMonitorService.java 0 → 100644 +173 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import android.content.Context; import android.os.Binder; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.utils.PriorityDump; import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; import java.util.concurrent.Executor; /** Service to monitor CPU availability and usage. */ public final class CpuMonitorService extends SystemService { static final String TAG = CpuMonitorService.class.getSimpleName(); static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG); // TODO(b/242722241): Make this a resource overlay property. // Maintain 3 monitoring intervals: // * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and // CPU availability is above a threshold (such as at least 10% of CPU is available). // * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available // and CPU availability is below a threshold (such as less than 10% of CPU is available). // * One to poll very less frequently when no callbacks are available and the build is either // user-debug or eng. This will be useful for debugging in development environment. static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000; private final Context mContext; private final Object mLock = new Object(); @GuardedBy("mLock") private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo> mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>(); @GuardedBy("mLock") private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS; private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() { @Override public void addCpuAvailabilityCallback(Executor executor, CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) { Objects.requireNonNull(callback, "Callback must be non-null"); Objects.requireNonNull(config, "Config must be non-null"); synchronized (mLock) { if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) { Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s", mCpuAvailabilityCallbackInfoByCallbacks.get(callback)); // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs) // that maps callbacks based on the CPU availability thresholds. } CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config, executor); mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info); if (DEBUG) { Slogf.d(TAG, "Added a CPU availability callback: %s", info); } } // TODO(b/242722241): // * On the executor or on the handler thread, call the callback with the latest CPU // availability info and monitoring interval. // * Monitor the CPU stats more frequently when the first callback is added. } @Override public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) { synchronized (mLock) { if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) { Slogf.i(TAG, "CpuAvailabilityCallback was not previously added." + " Ignoring the remove request"); return; } CpuAvailabilityCallbackInfo info = mCpuAvailabilityCallbackInfoByCallbacks.remove(callback); if (DEBUG) { Slogf.d(TAG, "Removed a CPU availability callback: %s", info); } } // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed. } }; public CpuMonitorService(Context context) { super(context); mContext = context; } @Override public void onStart() { publishLocalService(CpuMonitorInternal.class, mLocalService); publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); } private void doDump(IndentingPrintWriter writer) { writer.printf("*%s*\n", getClass().getSimpleName()); writer.increaseIndent(); synchronized (mLock) { writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds); if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) { writer.println("CPU availability change callbacks:"); writer.increaseIndent(); for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) { writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i), mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i)); } writer.decreaseIndent(); } } // TODO(b/242722241): Print the recent past CPU stats. writer.decreaseIndent(); } private static final class CpuAvailabilityCallbackInfo { public final CpuAvailabilityMonitoringConfig config; public final Executor executor; CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config, Executor executor) { this.config = config; this.executor = executor; } @Override public String toString() { return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor + '}'; } } private final class CpuMonitorBinder extends Binder { private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw) || asProto) { return; } try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) { doDump(ipw); } } }; @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { PriorityDump.dump(mPriorityDumper, fd, pw, args); } } } services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL; 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 android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.os.ServiceManager; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.server.ExtendedMockitoTestCase; import com.android.server.LocalServices; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase { private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG = new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) .addThreshold(30).addThreshold(70).build(); private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 = new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) .addThreshold(10).addThreshold(90).build(); @Mock private Context mContext; private CpuMonitorService mService; private HandlerExecutor mHandlerExecutor; private CpuMonitorInternal mLocalService; @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { builder.mockStatic(ServiceManager.class); } @Before public void setUp() { mService = new CpuMonitorService(mContext); mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper())); doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class), anyBoolean(), anyInt())); mService.onStart(); mLocalService = LocalServices.getService(CpuMonitorInternal.class); } @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); } @Test public void testAddRemoveCpuAvailabilityCallback() { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added. mLocalService.removeCpuAvailabilityCallback(mockCallback); } @Test public void testDuplicateAddCpuAvailabilityCallback() { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback); // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability // thresholds cross the bounds specified in the // {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config. mLocalService.removeCpuAvailabilityCallback(mockCallback); } @Test public void testRemoveInvalidCpuAvailabilityCallback() { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.removeCpuAvailabilityCallback(mockCallback); } } Loading
services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND; import com.android.internal.util.Preconditions; /** CPU availability information. */ public final class CpuAvailabilityInfo { /** Constant to indicate missing CPU availability percent. */ public static final int MISSING_CPU_AVAILABILITY_PERCENT = -1; /** * The CPUSET whose availability info is recorded in this object. * * <p>The contained value is one of the CPUSET_* constants from the * {@link CpuAvailabilityMonitoringConfig}. */ @CpuAvailabilityMonitoringConfig.Cpuset public final int cpuset; /** The latest average CPU availability percent. */ public final int latestAvgAvailabilityPercent; /** The past N-second average CPU availability percent. */ public final int pastNSecAvgAvailabilityPercent; /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */ public final int avgAvailabilityDurationSec; @Override public String toString() { return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent=" + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent=" + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec=" + avgAvailabilityDurationSec + '}'; } CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent, int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) { this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND, "cpuset"); this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent; this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent; this.avgAvailabilityDurationSec = avgAvailabilityDurationSec; } }
services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import android.annotation.IntDef; import android.util.IntArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** CPU availability monitoring config. */ public final class CpuAvailabilityMonitoringConfig { /** Constant to monitor all cpusets. */ public static final int CPUSET_ALL = 1; /** Constant to monitor background cpusets. */ public static final int CPUSET_BACKGROUND = 2; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CPUSET_"}, value = { CPUSET_ALL, CPUSET_BACKGROUND }) public @interface Cpuset { } /** * The CPUSET to monitor. * * <p>The value must be one of the {@code CPUSET_*} constants. */ @Cpuset public final int cpuset; /** * CPU availability percent thresholds. * * <p>CPU availability change notifications are sent when the latest or last N seconds average * CPU availability percent crosses any of these thresholds since the last notification. */ private final IntArray mThresholds; public IntArray getThresholds() { return mThresholds; } /** * Builder for the construction of {@link CpuAvailabilityMonitoringConfig} objects. * * <p>The builder must contain at least one threshold before calling {@link build}. */ public static final class Builder { private final int mCpuset; private final IntArray mThresholds = new IntArray(); public Builder(int cpuset, int... thresholds) { mCpuset = cpuset; for (int threshold : thresholds) { addThreshold(threshold); } } /** Adds the given threshold to the builder object. */ public Builder addThreshold(int threshold) { if (mThresholds.indexOf(threshold) == -1) { mThresholds.add(threshold); } return this; } /** Returns the {@link CpuAvailabilityMonitoringConfig} object. */ public CpuAvailabilityMonitoringConfig build() { return new CpuAvailabilityMonitoringConfig(this); } } @Override public String toString() { return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds + ')'; } private CpuAvailabilityMonitoringConfig(Builder builder) { if (builder.mCpuset != CPUSET_ALL && builder.mCpuset != CPUSET_BACKGROUND) { throw new IllegalStateException("Cpuset must be either CPUSET_ALL (" + CPUSET_ALL + ") or CPUSET_BACKGROUND (" + CPUSET_BACKGROUND + "). Builder contains " + builder.mCpuset); } if (builder.mThresholds.size() == 0) { throw new IllegalStateException("Must provide at least one threshold"); } this.cpuset = builder.mCpuset; this.mThresholds = builder.mThresholds.clone(); } }
services/core/java/com/android/server/cpu/CpuMonitorInternal.java 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import android.annotation.CallbackExecutor; import java.util.concurrent.Executor; /** CpuMonitorInternal hosts internal APIs to monitor CPU. */ public abstract class CpuMonitorInternal { /** Callback to get CPU availability change notifications. */ public interface CpuAvailabilityCallback { /** * Called when the CPU availability crosses the provided thresholds. * * <p>Called when the latest or past N-second (which will be specified in the * {@link CpuAvailabilityInfo}) average CPU availability percent has crossed * (either goes above or drop below) the {@link CpuAvailabilityMonitoringConfig#thresholds} * since the last notification. Also called when a callback is added to the service. * * <p>The callback is called at the executor which is specified in * {@link addCpuAvailabilityCallback} or at the service handler thread. * * @param info CPU availability information. */ void onAvailabilityChanged(CpuAvailabilityInfo info); /** * Called when the CPU monitoring interval changes. * * <p>Also called when a callback is added to the service. * * @param intervalMilliseconds CPU monitoring interval in milliseconds. */ void onMonitoringIntervalChanged(long intervalMilliseconds); } /** * Adds the {@link CpuAvailabilityCallback} for the caller. * * <p>When the callback is added, the callback will be called to notify the current CPU * availability and monitoring interval. * * <p>When the client needs to update the {@link config} for a previously added callback, * the client has to remove the callback and add the callback with a new {@link config}. * * @param executor Executor to execute the callback. If an executor is not provided, * the callback will be executed on the service handler thread. * @param config CPU availability monitoring config. * @param callback Callback implementing {@link CpuAvailabilityCallback} * interface. * * @throws IllegalStateException if {@code callback} is already added. */ public abstract void addCpuAvailabilityCallback(@CallbackExecutor Executor executor, CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback); /** * Removes the {@link CpuAvailabilityCallback} for the caller. * * @param callback Callback implementing {@link CpuAvailabilityCallback} * interface. * * @throws IllegalArgumentException if {@code callback} is not previously added. */ public abstract void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback); }
services/core/java/com/android/server/cpu/CpuMonitorService.java 0 → 100644 +173 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import android.content.Context; import android.os.Binder; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.utils.PriorityDump; import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; import java.util.concurrent.Executor; /** Service to monitor CPU availability and usage. */ public final class CpuMonitorService extends SystemService { static final String TAG = CpuMonitorService.class.getSimpleName(); static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG); // TODO(b/242722241): Make this a resource overlay property. // Maintain 3 monitoring intervals: // * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and // CPU availability is above a threshold (such as at least 10% of CPU is available). // * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available // and CPU availability is below a threshold (such as less than 10% of CPU is available). // * One to poll very less frequently when no callbacks are available and the build is either // user-debug or eng. This will be useful for debugging in development environment. static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000; private final Context mContext; private final Object mLock = new Object(); @GuardedBy("mLock") private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo> mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>(); @GuardedBy("mLock") private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS; private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() { @Override public void addCpuAvailabilityCallback(Executor executor, CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) { Objects.requireNonNull(callback, "Callback must be non-null"); Objects.requireNonNull(config, "Config must be non-null"); synchronized (mLock) { if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) { Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s", mCpuAvailabilityCallbackInfoByCallbacks.get(callback)); // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs) // that maps callbacks based on the CPU availability thresholds. } CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config, executor); mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info); if (DEBUG) { Slogf.d(TAG, "Added a CPU availability callback: %s", info); } } // TODO(b/242722241): // * On the executor or on the handler thread, call the callback with the latest CPU // availability info and monitoring interval. // * Monitor the CPU stats more frequently when the first callback is added. } @Override public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) { synchronized (mLock) { if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) { Slogf.i(TAG, "CpuAvailabilityCallback was not previously added." + " Ignoring the remove request"); return; } CpuAvailabilityCallbackInfo info = mCpuAvailabilityCallbackInfoByCallbacks.remove(callback); if (DEBUG) { Slogf.d(TAG, "Removed a CPU availability callback: %s", info); } } // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed. } }; public CpuMonitorService(Context context) { super(context); mContext = context; } @Override public void onStart() { publishLocalService(CpuMonitorInternal.class, mLocalService); publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); } private void doDump(IndentingPrintWriter writer) { writer.printf("*%s*\n", getClass().getSimpleName()); writer.increaseIndent(); synchronized (mLock) { writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds); if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) { writer.println("CPU availability change callbacks:"); writer.increaseIndent(); for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) { writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i), mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i)); } writer.decreaseIndent(); } } // TODO(b/242722241): Print the recent past CPU stats. writer.decreaseIndent(); } private static final class CpuAvailabilityCallbackInfo { public final CpuAvailabilityMonitoringConfig config; public final Executor executor; CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config, Executor executor) { this.config = config; this.executor = executor; } @Override public String toString() { return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor + '}'; } } private final class CpuMonitorBinder extends Binder { private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw) || asProto) { return; } try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) { doDump(ipw); } } }; @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { PriorityDump.dump(mPriorityDumper, fd, pw, args); } } }
services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.cpu; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL; 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 android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.os.ServiceManager; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.server.ExtendedMockitoTestCase; import com.android.server.LocalServices; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase { private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG = new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) .addThreshold(30).addThreshold(70).build(); private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 = new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) .addThreshold(10).addThreshold(90).build(); @Mock private Context mContext; private CpuMonitorService mService; private HandlerExecutor mHandlerExecutor; private CpuMonitorInternal mLocalService; @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { builder.mockStatic(ServiceManager.class); } @Before public void setUp() { mService = new CpuMonitorService(mContext); mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper())); doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class), anyBoolean(), anyInt())); mService.onStart(); mLocalService = LocalServices.getService(CpuMonitorInternal.class); } @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); } @Test public void testAddRemoveCpuAvailabilityCallback() { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added. mLocalService.removeCpuAvailabilityCallback(mockCallback); } @Test public void testDuplicateAddCpuAvailabilityCallback() { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback); // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability // thresholds cross the bounds specified in the // {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config. mLocalService.removeCpuAvailabilityCallback(mockCallback); } @Test public void testRemoveInvalidCpuAvailabilityCallback() { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.removeCpuAvailabilityCallback(mockCallback); } }