Keep warming services out of cached adj
Add a pre-configured list that will help to prevent those services
which will keep critical code path of the host processes warm
from falling into cached adj; in case of multi-user, such service
running in the background user won't get this capability, unless it's
singleton.
The list will be empty by default and subjected to be overlaid
by device configurations.
Bug: 158685897
Test: atest MockingOomAdjusterTests
Test: Manual - Add test service into the pre-configured list
start it and verify it won't get into cached state
Change-Id: I062f5ec3947ad3acebcc2ffe79e420beb23443a2
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7a05b90..f60766a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4386,4 +4386,6 @@
0.333
</item>
+ <!-- Component names of the services which will keep critical code path warm -->
+ <string-array name="config_keep_warming_services" translatable="false" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 77f7dcd..5bb784a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4058,4 +4058,5 @@
<java-symbol type="integer" name="config_defaultBinderHeavyHitterAutoSamplerBatchSize" />
<java-symbol type="dimen" name="config_defaultBinderHeavyHitterAutoSamplerThreshold" />
+ <java-symbol type="array" name="config_keep_warming_services" />
</resources>
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index e8f0eaa..09ed16e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -19,6 +19,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
import android.app.ActivityThread;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -336,6 +337,11 @@
*/
public int PENDINGINTENT_WARNING_THRESHOLD = DEFAULT_PENDINGINTENT_WARNING_THRESHOLD;
+ /**
+ * Component names of the services which will keep critical code path of the host warm
+ */
+ public final ArraySet<ComponentName> KEEP_WARMING_SERVICES = new ArraySet<ComponentName>();
+
private List<String> mDefaultImperceptibleKillExemptPackages;
private List<Integer> mDefaultImperceptibleKillExemptProcStates;
@@ -496,6 +502,10 @@
BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE = mDefaultBinderHeavyHitterAutoSamplerBatchSize;
BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD = mDefaultBinderHeavyHitterAutoSamplerThreshold;
service.scheduleUpdateBinderHeavyHitterWatcherConfig();
+ KEEP_WARMING_SERVICES.addAll(Arrays.stream(
+ context.getResources().getStringArray(
+ com.android.internal.R.array.config_keep_warming_services))
+ .map(ComponentName::unflattenFromString).collect(Collectors.toSet()));
}
public void start(ContentResolver resolver) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index da5f489..f0343e1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -76,7 +76,11 @@
import android.app.usage.UsageEvents;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ServiceInfo;
import android.os.Debug;
import android.os.Handler;
@@ -265,6 +269,43 @@
void initSettings() {
mCachedAppOptimizer.init();
+ if (mService.mConstants.KEEP_WARMING_SERVICES.size() > 0) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mService) {
+ handleUserSwitchedLocked();
+ }
+ }
+ }, filter, null, mService.mHandler);
+ }
+ }
+
+ /**
+ * Update the keep-warming service flags upon user switches
+ */
+ @VisibleForTesting
+ @GuardedBy("mService")
+ void handleUserSwitchedLocked() {
+ final ArraySet<ComponentName> warmServices = mService.mConstants.KEEP_WARMING_SERVICES;
+ final ArrayList<ProcessRecord> processes = mProcessList.mLruProcesses;
+ for (int i = processes.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = processes.get(i);
+ boolean includeWarmPkg = false;
+ for (int j = warmServices.size() - 1; j >= 0; j--) {
+ if (app.pkgList.containsKey(warmServices.valueAt(j).getPackageName())) {
+ includeWarmPkg = true;
+ break;
+ }
+ }
+ if (!includeWarmPkg) {
+ continue;
+ }
+ for (int j = app.numberOfRunningServices() - 1; j >= 0; j--) {
+ app.getRunningServiceAt(j).updateKeepWarmLocked();
+ }
+ }
}
/**
@@ -1470,7 +1511,7 @@
"Raise procstate to started service: " + app);
}
}
- if (app.hasShownUi && !app.getCachedIsHomeProcess()) {
+ if (!s.mKeepWarming && app.hasShownUi && !app.getCachedIsHomeProcess()) {
// If this process has shown some UI, let it immediately
// go to the LRU list because it may be pretty heavy with
// UI stuff. We'll tag it with a label just to help
@@ -1479,7 +1520,8 @@
app.adjType = "cch-started-ui-services";
}
} else {
- if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
+ if (s.mKeepWarming
+ || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
// This service has seen some activity within
// recent memory, so we will keep its process ahead
// of the background processes.
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 749a990..828ac71 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -43,6 +43,7 @@
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.LocalServices;
@@ -149,6 +150,8 @@
private int lastStartId; // identifier of most recent start request.
+ boolean mKeepWarming; // Whether or not it'll keep critical code path of the host warm
+
static class StartItem {
final ServiceRecord sr;
final boolean taskRemoved;
@@ -517,6 +520,7 @@
lastActivity = SystemClock.uptimeMillis();
userId = UserHandle.getUserId(appInfo.uid);
createdFromFg = callerIsFg;
+ updateKeepWarmLocked();
}
public ServiceState getTracker() {
@@ -735,6 +739,14 @@
}
}
+ @GuardedBy("ams")
+ void updateKeepWarmLocked() {
+ mKeepWarming = ams.mConstants.KEEP_WARMING_SERVICES.contains(name)
+ && (ams.mUserController.getCurrentUserId() == userId
+ || ams.isSingleton(processName, appInfo, instanceName.getClassName(),
+ serviceInfo.flags));
+ }
+
public AppBindRecord retrieveAppBindingLocked(Intent intent,
ProcessRecord app) {
Intent.FilterComparison filter = new Intent.FilterComparison(intent);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index fde40aa..2a267c4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -84,6 +84,7 @@
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -130,6 +131,7 @@
private static final int MOCKAPP5_UID = MOCKAPP_UID + 4;
private static final String MOCKAPP5_PROCESSNAME = "test #5";
private static final String MOCKAPP5_PACKAGENAME = "com.android.test.test5";
+ private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE;
private static Context sContext;
private static PackageManagerInternal sPackageManagerInternal;
private static ActivityManagerService sService;
@@ -168,6 +170,8 @@
mock(SparseArray.class));
setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler",
mock(OomAdjProfiler.class));
+ setFieldValue(ActivityManagerService.class, sService, "mUserController",
+ mock(UserController.class));
doReturn(new ActivityManagerService.ProcessChangeItem()).when(sService)
.enqueueProcessChangeItemLocked(anyInt(), anyInt());
sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
@@ -1621,6 +1625,117 @@
assertEquals(SERVICE_ADJ, app.setAdj);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoAll_Service_KeepWarmingList() {
+ final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID_OTHER,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ final int userOwner = 0;
+ final int userOther = 1;
+ final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+ final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+ doReturn(userOwner).when(sService.mUserController).getCurrentUserId();
+
+ final ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ lru.clear();
+ lru.add(app2);
+ lru.add(app);
+
+ final ComponentName cn = ComponentName.unflattenFromString(
+ MOCKAPP_PACKAGENAME + "/.TestService");
+ final ComponentName cn2 = ComponentName.unflattenFromString(
+ MOCKAPP2_PACKAGENAME + "/.TestService");
+ final long now = SystemClock.uptimeMillis();
+
+ sService.mConstants.KEEP_WARMING_SERVICES.clear();
+ final ServiceInfo si = mock(ServiceInfo.class);
+ si.applicationInfo = mock(ApplicationInfo.class);
+ ServiceRecord s = spy(new ServiceRecord(sService, null, cn, cn, null, 0, null,
+ si, false, null));
+ doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
+ s.startRequested = true;
+ s.lastActivity = now;
+
+ app.setCached(false);
+ app.startService(s);
+ app.hasShownUi = true;
+
+ final ServiceInfo si2 = mock(ServiceInfo.class);
+ si2.applicationInfo = mock(ApplicationInfo.class);
+ si2.applicationInfo.uid = MOCKAPP2_UID_OTHER;
+ ServiceRecord s2 = spy(new ServiceRecord(sService, null, cn2, cn2, null, 0, null,
+ si2, false, null));
+ doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s2).getConnections();
+ s2.startRequested = true;
+ s2.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+
+ app2.setCached(false);
+ app2.startService(s2);
+ app2.hasShownUi = false;
+
+ sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
+ assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
+
+ app.setProcState = PROCESS_STATE_NONEXISTENT;
+ app.adjType = null;
+ app.setAdj = UNKNOWN_ADJ;
+ app.hasShownUi = false;
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
+
+ app.setCached(false);
+ app.setProcState = PROCESS_STATE_NONEXISTENT;
+ app.adjType = null;
+ app.setAdj = UNKNOWN_ADJ;
+ s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+
+ app.stopService(s);
+ app.setProcState = PROCESS_STATE_NONEXISTENT;
+ app.adjType = null;
+ app.setAdj = UNKNOWN_ADJ;
+ app.hasShownUi = true;
+ sService.mConstants.KEEP_WARMING_SERVICES.add(cn);
+ sService.mConstants.KEEP_WARMING_SERVICES.add(cn2);
+ s = spy(new ServiceRecord(sService, null, cn, cn, null, 0, null,
+ si, false, null));
+ doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
+ s.startRequested = true;
+ s.lastActivity = now;
+
+ app.startService(s);
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
+ assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+
+ app.setCached(true);
+ app.setProcState = PROCESS_STATE_NONEXISTENT;
+ app.adjType = null;
+ app.setAdj = UNKNOWN_ADJ;
+ app.hasShownUi = false;
+ s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
+ assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+
+ doReturn(userOther).when(sService.mUserController).getCurrentUserId();
+ sService.mOomAdjuster.handleUserSwitchedLocked();
+
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+ assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
long now = SystemClock.uptimeMillis();
@@ -1747,4 +1862,12 @@
assertEquals(expectedAdj, app.setAdj);
assertEquals(expectedSchedGroup, app.setSchedGroup);
}
+
+ private void assertProcStates(ProcessRecord app, boolean expectedCached,
+ int expectedProcState, int expectedAdj, String expectedAdjType) {
+ assertEquals(expectedCached, app.isCached());
+ assertEquals(expectedProcState, app.setProcState);
+ assertEquals(expectedAdj, app.setAdj);
+ assertEquals(expectedAdjType, app.adjType);
+ }
}