Merge "Add cpu user mode run time rate limiter per UID" into main
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 8346112..a0bc77e 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.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.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.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.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 @@
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 @@
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 @@
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 @@
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 @@
mChannelMap.remove(uid);
}
}
+ synchronized (mCpuHeadroomLock) {
+ if (mSupportInfo.headroom.isCpuSupported && mUidToLastUserModeJiffies != null) {
+ mUidToLastUserModeJiffies.remove(uid);
+ }
+ }
});
}
@@ -1230,7 +1274,7 @@
// 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 @@
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 @@
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 @@
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 @@
}
}
+ // 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 @@
}
}
+ 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 35f421e..de6f9bd 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.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.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 @@
*/
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 @@
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 @@
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 @@
});
}
+ @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 @@
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 @@
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 @@
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 @@
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));