Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 59cc8a31 authored by Xiang Wang's avatar Xiang Wang
Browse files

Add support for affinity check

Bug: 346604998
Flag: com.android.server.power.hint.cpu_headroom_affinity_check
Test: atest HintManagerServiceTest ProcessTest#testGetSchedAffinity
Change-Id: I8e5f73398afc8decb0dde0aa71d2960e1aa45d92
parent 49c11f51
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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.
+23 −0
Original line number Diff line number Diff line
@@ -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},
+18 −0
Original line number Diff line number Diff line
@@ -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));
    }
}
+27 −1
Original line number Diff line number Diff line
@@ -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();
@@ -1499,6 +1500,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
@@ -1527,6 +1532,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;
+71 −1
Original line number Diff line number Diff line
@@ -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;
@@ -1321,6 +1328,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();
@@ -1334,11 +1342,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();
@@ -1410,6 +1421,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