Loading core/java/android/os/Process.java +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading core/jni/android_util_Process.cpp +23 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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}, Loading core/tests/coretests/src/android/os/ProcessTest.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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)); } } services/core/java/com/android/server/power/hint/HintManagerService.java +27 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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 Loading Loading @@ -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; Loading services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +71 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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(); Loading Loading @@ -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 Loading Loading
core/java/android/os/Process.java +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
core/jni/android_util_Process.cpp +23 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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}, Loading
core/tests/coretests/src/android/os/ProcessTest.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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)); } }
services/core/java/com/android/server/power/hint/HintManagerService.java +27 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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 Loading Loading @@ -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; Loading
services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +71 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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(); Loading Loading @@ -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 Loading