summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Xiang Wang <xwxw@google.com> 2024-12-16 16:17:25 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2024-12-16 16:17:25 -0800
commitfa53dbd15e81d1bec8dff03e92130da7abc1d327 (patch)
tree5d210fd65558c5ab4761b028a207817a621f8e58
parent7177560ed60a9d57c439a523a0cee8d6fb62a1ae (diff)
parent59cc8a31cda9d5e42f46fae6c75a9bbe842ee12f (diff)
Merge "Add support for affinity check" into main
-rw-r--r--core/java/android/os/Process.java11
-rw-r--r--core/jni/android_util_Process.cpp23
-rw-r--r--core/tests/coretests/src/android/os/ProcessTest.java18
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java28
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java72
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