diff options
author | 2024-12-16 16:17:25 -0800 | |
---|---|---|
committer | 2024-12-16 16:17:25 -0800 | |
commit | fa53dbd15e81d1bec8dff03e92130da7abc1d327 (patch) | |
tree | 5d210fd65558c5ab4761b028a207817a621f8e58 | |
parent | 7177560ed60a9d57c439a523a0cee8d6fb62a1ae (diff) | |
parent | 59cc8a31cda9d5e42f46fae6c75a9bbe842ee12f (diff) |
Merge "Add support for affinity check" into main
5 files changed, 150 insertions, 2 deletions
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 907d96834857..0c5d9e97a77d 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -1221,6 +1221,17 @@ public class Process { */ public static final native int[] getExclusiveCores(); + + /** + * Get the CPU affinity masks from sched_getaffinity. + * + * @param tid The identifier of the thread/process to get the sched affinity. + * @return an array of CPU affinity masks, of which the size will be dynamic and just enough to + * include all bit masks for all currently online and possible CPUs of the device. + * @hide + */ + public static final native long[] getSchedAffinity(int tid); + /** * Set the priority of the calling thread, based on Linux priorities. See * {@link #setThreadPriority(int, int)} for more information. diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index dc7253954d44..67c97258a01d 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -490,6 +490,28 @@ jintArray android_os_Process_getExclusiveCores(JNIEnv* env, jobject clazz) { return cpus; } +jlongArray android_os_Process_getSchedAffinity(JNIEnv* env, jobject clazz, jint pid) { + // sched_getaffinity will do memset 0 for the unset bits within set_alloc_size_byte + cpu_set_t cpu_set; + if (sched_getaffinity(pid, sizeof(cpu_set_t), &cpu_set) != 0) { + signalExceptionForError(env, errno, pid); + return nullptr; + } + int cpu_cnt = std::min(CPU_SETSIZE, get_nprocs_conf()); + int masks_len = (int)(CPU_ALLOC_SIZE(cpu_cnt) / sizeof(__CPU_BITTYPE)); + jlongArray masks = env->NewLongArray(masks_len); + if (masks == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return nullptr; + } + jlong* mask_elements = env->GetLongArrayElements(masks, 0); + for (int i = 0; i < masks_len; i++) { + mask_elements[i] = cpu_set.__bits[i]; + } + env->ReleaseLongArrayElements(masks, mask_elements, 0); + return masks; +} + static void android_os_Process_setCanSelfBackground(JNIEnv* env, jobject clazz, jboolean bgOk) { // Establishes the calling thread as illegal to put into the background. // Typically used only for the system process's main looper. @@ -1370,6 +1392,7 @@ static const JNINativeMethod methods[] = { {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup}, {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup}, {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores}, + {"getSchedAffinity", "(I)[J", (void*)android_os_Process_getSchedAffinity}, {"setArgV0Native", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0}, {"setUid", "(I)I", (void*)android_os_Process_setUid}, {"setGid", "(I)I", (void*)android_os_Process_setGid}, diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java index 310baa371163..ea39db7b0057 100644 --- a/core/tests/coretests/src/android/os/ProcessTest.java +++ b/core/tests/coretests/src/android/os/ProcessTest.java @@ -18,6 +18,7 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.IgnoreUnderRavenwood; @@ -26,6 +27,8 @@ import android.platform.test.ravenwood.RavenwoodRule; import org.junit.Rule; import org.junit.Test; +import java.util.Arrays; + @IgnoreUnderRavenwood(blockedBy = Process.class) public class ProcessTest { @Rule @@ -92,4 +95,19 @@ public class ProcessTest { assertTrue(Process.getAdvertisedMem() > 0); assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem()); } + + @Test + public void testGetSchedAffinity() { + long[] tidMasks = Process.getSchedAffinity(Process.myTid()); + long[] pidMasks = Process.getSchedAffinity(Process.myPid()); + checkAffinityMasks(tidMasks); + checkAffinityMasks(pidMasks); + } + + static void checkAffinityMasks(long[] masks) { + assertNotNull(masks); + assertTrue(masks.length > 0); + assertTrue("At least one of the affinity mask should be non-zero but got " + + Arrays.toString(masks), Arrays.stream(masks).anyMatch(value -> value > 0)); + } } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index aae7417970eb..83461125b404 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -20,6 +20,7 @@ import static android.os.Flags.adpfUseFmqChannel; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.adpfSessionTag; +import static com.android.server.power.hint.Flags.cpuHeadroomAffinityCheck; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; import static com.android.server.power.hint.Flags.resetOnForkEnabled; @@ -191,7 +192,7 @@ public final class HintManagerService extends SystemService { private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms"; private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid"; - + private static final String PROPERTY_CHECK_HEADROOM_AFFINITY = "persist.hms.check_affinity"; private Boolean mFMQUsesIntegratedEventFlag = false; private final Object mCpuHeadroomLock = new Object(); @@ -1501,6 +1502,10 @@ public final class HintManagerService extends SystemService { } } } + if (cpuHeadroomAffinityCheck() && params.tids.length > 1 + && SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY, true)) { + checkThreadAffinityForTids(params.tids); + } halParams.tids = params.tids; } if (halParams.calculationWindowMillis @@ -1529,6 +1534,27 @@ public final class HintManagerService extends SystemService { return null; } } + private void checkThreadAffinityForTids(int[] tids) { + long[] reference = null; + for (int tid : tids) { + long[] affinity; + try { + affinity = Process.getSchedAffinity(tid); + } catch (Exception e) { + Slog.e(TAG, "Failed to get affinity " + tid, e); + throw new IllegalStateException("Could not check affinity for tid " + tid); + } + if (reference == null) { + reference = affinity; + } else if (!Arrays.equals(reference, affinity)) { + Slog.d(TAG, "Thread affinity is different: tid " + + tids[0] + "->" + Arrays.toString(reference) + ", tid " + + tid + "->" + Arrays.toString(affinity)); + throw new IllegalStateException("Thread affinity is not the same for tids " + + Arrays.toString(tids)); + } + } + } private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) { boolean calculationTypeMatched = false; diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java index 4b2e850d08e7..35f421e582d8 100644 --- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -70,9 +70,12 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SessionCreationConfig; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; import com.android.server.FgThread; @@ -89,6 +92,8 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -160,6 +165,8 @@ public class HintManagerServiceTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private HintManagerService mService; private ChannelConfig mConfig; @@ -1322,6 +1329,7 @@ public class HintManagerServiceTest { @Test + @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) public void testCpuHeadroomCache() throws Exception { CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal(); CpuHeadroomParams halParams1 = new CpuHeadroomParams(); @@ -1335,11 +1343,14 @@ public class HintManagerServiceTest { halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN; halParams2.tids = new int[]{}; + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal(); + params3.tids = tids; params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; CpuHeadroomParams halParams3 = new CpuHeadroomParams(); + halParams3.tids = tids; halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; - halParams3.tids = new int[]{Process.myPid()}; // this params should not be cached as the window is not default CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal(); @@ -1411,6 +1422,65 @@ public class HintManagerServiceTest { verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); + latch.countDown(); + } + + @Test + @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) + public void testGetCpuHeadroomDifferentAffinity_flagOn() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.tids = tids; + CpuHeadroomParams halParams = new CpuHeadroomParams(); + halParams.tids = tids; + float headroom = 0.1f; + CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom); + String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]); + String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]); + + HintManagerService service = createService(); + clearInvocations(mIPowerMock); + when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet); + assertThrows("taskset cmd return: " + ret1 + "\n" + ret2, IllegalStateException.class, + () -> service.getBinderServiceInstance().getCpuHeadroom(params)); + verify(mIPowerMock, times(0)).getCpuHeadroom(any()); + } + + @Test + @DisableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) + public void testGetCpuHeadroomDifferentAffinity_flagOff() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.tids = tids; + CpuHeadroomParams halParams = new CpuHeadroomParams(); + halParams.tids = tids; + float headroom = 0.1f; + CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom); + String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]); + String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]); + + HintManagerService service = createService(); + clearInvocations(mIPowerMock); + when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet); + assertEquals("taskset cmd return: " + ret1 + "\n" + ret2, halRet, + service.getBinderServiceInstance().getCpuHeadroom(params)); + verify(mIPowerMock, times(1)).getCpuHeadroom(any()); + } + + private String runAndWaitForCommand(String command) throws Exception { + java.lang.Process process = Runtime.getRuntime().exec(command); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + StringBuilder res = new StringBuilder(); + while ((line = reader.readLine()) != null) { + res.append(line); + } + process.waitFor(); + // somehow the exit code can be 1 for the taskset command though it exits successfully, + // thus we just return the output + return res.toString(); } @Test |