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

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

Merge "Add cpu user mode run time rate limiter per UID" into main

parents 0b64be3a 438a5205
Loading
Loading
Loading
Loading
+128 −7
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ 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;

import android.Manifest;
import android.adpf.ISessionManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +60,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SessionCreationConfig;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.system.Os;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -79,7 +83,10 @@ import com.android.server.SystemService;
import com.android.server.power.hint.HintManagerService.AppHintSession.SessionModes;
import com.android.server.utils.Slogf;

import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -95,6 +102,8 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** An hint service implementation that runs in System Server process. */
public final class HintManagerService extends SystemService {
@@ -103,10 +112,10 @@ public final class HintManagerService extends SystemService {

    private static final int EVENT_CLEAN_UP_UID = 3;
    @VisibleForTesting  static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
    // The minimum interval between the headroom calls as rate limiting.
    private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
    private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;

    // example: cpu  2255 34 2290 22625563 6290 127 456
    private static final Pattern PROC_STAT_CPU_TIME_TOTAL_PATTERN =
            Pattern.compile("cpu\\s+(?<user>[0-9]+)\\s(?<nice>[0-9]+).+");

    @VisibleForTesting final long mHintSessionPreferredRate;

@@ -192,10 +201,26 @@ 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 static final String PROPERTY_CHECK_HEADROOM_AFFINITY =
            "persist.hms.check_headroom_affinity";
    private static final String PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS =
            "persist.hms.check_headroom_proc_stat_min_millis";
    private Boolean mFMQUsesIntegratedEventFlag = false;

    private final Object mCpuHeadroomLock = new Object();
    @VisibleForTesting
    final float mJiffyMillis;
    private final int mCheckHeadroomProcStatMinMillis;
    @GuardedBy("mCpuHeadroomLock")
    private long mLastCpuUserModeTimeCheckedMillis = 0;
    @GuardedBy("mCpuHeadroomLock")
    private long mLastCpuUserModeJiffies = 0;
    @GuardedBy("mCpuHeadroomLock")
    private final Map<Integer, Long> mUidToLastUserModeJiffies;
    @VisibleForTesting
    private String mProcStatFilePathOverride = null;
    @VisibleForTesting
    private boolean mEnforceCpuHeadroomUserModeCpuTimeCheck = false;

    private ISessionManager mSessionManager;

@@ -310,8 +335,16 @@ public final class HintManagerService extends SystemService {
                new GpuHeadroomParamsInternal().calculationWindowMillis;
        if (mSupportInfo.headroom.isCpuSupported) {
            mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
            mUidToLastUserModeJiffies = new ArrayMap<>();
            long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
            mJiffyMillis = 1000.0f / jiffyHz;
            mCheckHeadroomProcStatMinMillis = SystemProperties.getInt(
                    PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS, 50);
        } else {
            mCpuHeadroomCache = null;
            mUidToLastUserModeJiffies = null;
            mJiffyMillis = 0.0f;
            mCheckHeadroomProcStatMinMillis = 0;
        }
        if (mSupportInfo.headroom.isGpuSupported) {
            mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
@@ -370,6 +403,12 @@ public final class HintManagerService extends SystemService {
        return supportInfo;
    }

    @VisibleForTesting
    void setProcStatPathOverride(String override) {
        mProcStatFilePathOverride = override;
        mEnforceCpuHeadroomUserModeCpuTimeCheck = true;
    }

    private ServiceThread createCleanUpThread() {
        final ServiceThread handlerThread = new ServiceThread(TAG,
                Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/);
@@ -851,6 +890,11 @@ public final class HintManagerService extends SystemService {
                        mChannelMap.remove(uid);
                    }
                }
                synchronized (mCpuHeadroomLock) {
                    if (mSupportInfo.headroom.isCpuSupported && mUidToLastUserModeJiffies != null) {
                        mUidToLastUserModeJiffies.remove(uid);
                    }
                }
            });
        }

@@ -1230,7 +1274,7 @@ public final class HintManagerService extends SystemService {
            // Only call into AM if the tid is either isolated or invalid
            if (isolatedPids == null) {
                // To avoid deadlock, do not call into AMS if the call is from system.
                if (uid == Process.SYSTEM_UID) {
                if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
                    return tid;
                }
                isolatedPids = mAmInternal.getIsolatedProcesses(uid);
@@ -1485,14 +1529,17 @@ public final class HintManagerService extends SystemService {
                throw new UnsupportedOperationException();
            }
            checkCpuHeadroomParams(params);
            final int uid = Binder.getCallingUid();
            final int pid = Binder.getCallingPid();
            final CpuHeadroomParams halParams = new CpuHeadroomParams();
            halParams.tids = new int[]{Binder.getCallingPid()};
            halParams.tids = new int[]{pid};
            halParams.calculationType = params.calculationType;
            halParams.calculationWindowMillis = params.calculationWindowMillis;
            if (params.usesDeviceHeadroom) {
                halParams.tids = new int[]{};
            } else if (params.tids != null && params.tids.length > 0) {
                if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
                if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && SystemProperties.getBoolean(
                        PROPERTY_CHECK_HEADROOM_TID, true)) {
                    final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
                    for (int tid : params.tids) {
                        if (Process.getThreadGroupLeader(tid) != tgid) {
@@ -1515,6 +1562,20 @@ public final class HintManagerService extends SystemService {
                    if (res != null) return res;
                }
            }
            final boolean shouldCheckUserModeCpuTime =
                    mEnforceCpuHeadroomUserModeCpuTimeCheck
                            || (UserHandle.getAppId(uid) != Process.SYSTEM_UID
                            && mContext.checkCallingPermission(
                            Manifest.permission.DEVICE_POWER)
                            == PackageManager.PERMISSION_DENIED);

            if (shouldCheckUserModeCpuTime) {
                synchronized (mCpuHeadroomLock) {
                    if (!checkPerUidUserModeCpuTimeElapsedLocked(uid)) {
                        return null;
                    }
                }
            }
            // return from HAL directly
            try {
                final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
@@ -1528,6 +1589,11 @@ public final class HintManagerService extends SystemService {
                        mCpuHeadroomCache.add(halParams, result);
                    }
                }
                if (shouldCheckUserModeCpuTime) {
                    synchronized (mCpuHeadroomLock) {
                        mUidToLastUserModeJiffies.put(uid, mLastCpuUserModeJiffies);
                    }
                }
                return result;
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
@@ -1556,6 +1622,40 @@ public final class HintManagerService extends SystemService {
            }
        }

        // check if there has been sufficient user mode cpu time elapsed since last call
        // from the same uid
        @GuardedBy("mCpuHeadroomLock")
        private boolean checkPerUidUserModeCpuTimeElapsedLocked(int uid) {
            // skip checking proc stat if it's within mCheckHeadroomProcStatMinMillis
            if (System.currentTimeMillis() - mLastCpuUserModeTimeCheckedMillis
                    > mCheckHeadroomProcStatMinMillis) {
                try {
                    mLastCpuUserModeJiffies = getUserModeJiffies();
                } catch (Exception e) {
                    Slog.e(TAG, "Failed to get user mode CPU time", e);
                    return false;
                }
                mLastCpuUserModeTimeCheckedMillis = System.currentTimeMillis();
            }
            if (mUidToLastUserModeJiffies.containsKey(uid)) {
                long uidLastUserModeJiffies = mUidToLastUserModeJiffies.get(uid);
                if ((mLastCpuUserModeJiffies - uidLastUserModeJiffies) * mJiffyMillis
                        < mSupportInfo.headroom.cpuMinIntervalMillis) {
                    Slog.w(TAG, "UID " + uid + " is requesting CPU headroom too soon");
                    Slog.d(TAG, "UID " + uid + " last request at "
                            + uidLastUserModeJiffies * mJiffyMillis
                            + "ms with device currently at "
                            + mLastCpuUserModeJiffies * mJiffyMillis
                            + "ms, the interval: "
                            + (mLastCpuUserModeJiffies - uidLastUserModeJiffies)
                            * mJiffyMillis + "ms is less than require minimum interval "
                            + mSupportInfo.headroom.cpuMinIntervalMillis + "ms");
                    return false;
                }
            }
            return true;
        }

        private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) {
            boolean calculationTypeMatched = false;
            try {
@@ -1731,6 +1831,27 @@ public final class HintManagerService extends SystemService {
            }
        }

        private long getUserModeJiffies() throws IOException {
            String filePath =
                    mProcStatFilePathOverride == null ? "/proc/stat" : mProcStatFilePathOverride;
            try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    Matcher matcher = PROC_STAT_CPU_TIME_TOTAL_PATTERN.matcher(line.trim());
                    if (matcher.find()) {
                        long userJiffies = Long.parseLong(matcher.group("user"));
                        long niceJiffies = Long.parseLong(matcher.group("nice"));
                        Slog.d(TAG,
                                "user: " + userJiffies + " nice: " + niceJiffies
                                        + " total " + (userJiffies + niceJiffies));
                        reader.close();
                        return userJiffies + niceJiffies;
                    }
                }
            }
            throw new IllegalStateException("Can't find cpu line in " + filePath);
        }

        private boolean checkGraphicsPipelineValid(SessionCreationConfig creationConfig, int uid) {
            if (creationConfig.modesToEnable == null) {
                return true;
+75 −10
Original line number Diff line number Diff line
@@ -78,12 +78,15 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;

import androidx.test.InstrumentationRegistry;

import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.power.hint.HintManagerService.AppHintSession;
import com.android.server.power.hint.HintManagerService.Injector;
import com.android.server.power.hint.HintManagerService.NativeWrapper;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -93,6 +96,8 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
@@ -111,6 +116,7 @@ import java.util.concurrent.locks.LockSupport;
 */
public class HintManagerServiceTest {
    private static final String TAG = "HintManagerServiceTest";
    private List<File> mFilesCreated = new ArrayList<>();

    private static WorkDuration makeWorkDuration(
            long timestamp, long duration, long workPeriodStartTime,
@@ -192,9 +198,9 @@ public class HintManagerServiceTest {
        mSupportInfo.sessionTags = -1;
        mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
        mSupportInfo.headroom.isCpuSupported = true;
        mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
        mSupportInfo.headroom.cpuMinIntervalMillis = 1000;
        mSupportInfo.headroom.isGpuSupported = true;
        mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
        mSupportInfo.headroom.gpuMinIntervalMillis = 1000;
        mSupportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo();
        return mSupportInfo;
    }
@@ -243,6 +249,13 @@ public class HintManagerServiceTest {
        LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
    }

    @After
    public void tearDown() {
        for (File file : mFilesCreated) {
            file.delete();
        }
    }

    /**
     * Mocks the creation calls, but without support for new createHintSessionWithConfig method
     */
@@ -1327,6 +1340,58 @@ public class HintManagerServiceTest {
        });
    }

    @Test
    public void testCpuHeadroomCpuProcStatPath() throws Exception {
        File dir = InstrumentationRegistry.getTargetContext().getFilesDir();
        dir.mkdir();
        String procStatFileStr = "mock_proc_stat";
        File file = new File(dir, procStatFileStr);
        mFilesCreated.add(file);
        try (FileOutputStream output = new FileOutputStream(file)) {
            output.write("cpu  2000 3000 4000 0 0 0 0 0 0 0".getBytes());
        }
        HintManagerService service = createService();
        service.setProcStatPathOverride(file.getPath());

        CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
        CpuHeadroomParams halParams1 = new CpuHeadroomParams();
        halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
        halParams1.tids = new int[]{Process.myPid()};

        float headroom1 = 0.1f;
        CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
        when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
        clearInvocations(mIPowerMock);
        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
        // expire the cache but cpu proc hasn't changed so we expect no value return
        Thread.sleep(1100);
        clearInvocations(mIPowerMock);
        assertEquals(null, service.getBinderServiceInstance().getCpuHeadroom(params1));
        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));

        // update user jiffies with 500 equivalent jiffies, which is not sufficient cpu time
        Thread.sleep(1100);
        try (FileOutputStream output = new FileOutputStream(file)) {
            output.write(("cpu  " + (2000 + (int) (500 / service.mJiffyMillis))
                    + " 3000 4000 0 0 0 0 0 0 0").getBytes());
        }
        clearInvocations(mIPowerMock);
        assertEquals(null, service.getBinderServiceInstance().getCpuHeadroom(params1));
        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));

        // update nice jiffies with 600 equivalent jiffies, now it exceeds 1000ms requirement
        Thread.sleep(1100);
        try (FileOutputStream output = new FileOutputStream(file)) {
            output.write(("cpu  " + (2000 + (int) (500 / service.mJiffyMillis))
                    + " " + +(3000 + (int) (600 / service.mJiffyMillis))
                    + " 4000 0 0 0 0 0 0 0").getBytes());
        }
        clearInvocations(mIPowerMock);
        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
    }


    @Test
    @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK})
@@ -1397,8 +1462,8 @@ public class HintManagerServiceTest {
        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));

        // after 1 more second it should be served with cache still
        Thread.sleep(1000);
        // after 500ms more it should be served with cache
        Thread.sleep(500);
        clearInvocations(mIPowerMock);
        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
        assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1410,8 +1475,8 @@ public class HintManagerServiceTest {
        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));

        // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
        Thread.sleep(1100);
        // after 1+ seconds it should be served from HAL as it exceeds 1000 millis interval
        Thread.sleep(600);
        clearInvocations(mIPowerMock);
        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
        assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1519,8 +1584,8 @@ public class HintManagerServiceTest {
        verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));

        // after 1 more second it should be served with cache still
        Thread.sleep(1000);
        // after 500ms it should be served with cache
        Thread.sleep(500);
        clearInvocations(mIPowerMock);
        assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
        assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
@@ -1528,8 +1593,8 @@ public class HintManagerServiceTest {
        verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));

        // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
        Thread.sleep(1100);
        // after 1+ seconds it should be served from HAL as it exceeds 1000 millis interval
        Thread.sleep(600);
        clearInvocations(mIPowerMock);
        assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
        assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));