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

Commit fa53dbd1 authored by Xiang Wang's avatar Xiang Wang Committed by Android (Google) Code Review
Browse files

Merge "Add support for affinity check" into main

parents 7177560e 59cc8a31
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();
@@ -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;
+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;
@@ -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