diff options
author | 2024-12-18 19:24:01 -0800 | |
---|---|---|
committer | 2024-12-18 19:24:01 -0800 | |
commit | 781d348b9906403f53b1610bd17c63fd9906c96a (patch) | |
tree | d21892069f0fef265c639db65a91cd6177d0917b | |
parent | 0b64be3af1792ee76ea8b399f2304ab0bd9debf4 (diff) | |
parent | 438a5205df565eb6b7889f1c855f1a0ee3525437 (diff) |
Merge "Add cpu user mode run time rate limiter per UID" into main
-rw-r--r-- | services/core/java/com/android/server/power/hint/HintManagerService.java | 135 | ||||
-rw-r--r-- | services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java | 85 |
2 files changed, 203 insertions, 17 deletions
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 83461125b404..a0bc77e939d1 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -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; 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 35f421e582d8..de6f9bd7527a 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 @@ -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)); |