OomAdjuster: use oom_adj_score tiers for cached apps

Metrics suggest that cached apps do not get promoted out of cached in
anything resembling LRU ordering. Maintaining the LRU order is
expensive for both ActivityManager and LMKD. Instead of a strict LRU,
use tiers of oom_adj_scores for cached apps:

- 900 for cached apps bound with BIND_WAIVE_PRIORITY or similar
- 910 for freezer-eligible apps that entered cached in the last 60s
- 950 for freezer-eligible apps that entered cached more than 60s ago

Tiered oom_adj_scores for cached apps can be enabled with the
"use_tiered_cached_adj" DeviceConfig flag, and the decay time (in ms)
can be modified with the "tiered_cached_adj_decay_time" DeviceConfig
flag.

oom_adj_score tiers currently default to off.

Bug: 253908413
Test: atest MockingOomAdjusterTests

Change-Id: Ie386672c321227eb689902863df0b0022fa09e49
Merged-in: Ie386672c321227eb689902863df0b0022fa09e49
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9d4174df..9e95e5f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -146,6 +146,9 @@
      */
     static final String KEY_NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
 
+    static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
+    static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
+
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -201,6 +204,9 @@
 
     static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000;
 
+    private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
+    private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
+
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
      */
@@ -1011,6 +1017,12 @@
     public volatile long mShortFgsAnrExtraWaitDuration =
             DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
 
+    /** @see #KEY_USE_TIERED_CACHED_ADJ */
+    public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
+
+    /** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */
+    public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME;
+
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
                 @Override
@@ -1179,6 +1191,10 @@
                             case KEY_MAX_PREVIOUS_TIME:
                                 updateMaxPreviousTime();
                                 break;
+                            case KEY_USE_TIERED_CACHED_ADJ:
+                            case KEY_TIERED_CACHED_ADJ_DECAY_TIME:
+                                updateUseTieredCachedAdj();
+                                break;
                             default:
                                 break;
                         }
@@ -1924,6 +1940,17 @@
                 DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION);
     }
 
+    private void updateUseTieredCachedAdj() {
+        USE_TIERED_CACHED_ADJ = DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+            KEY_USE_TIERED_CACHED_ADJ,
+            DEFAULT_USE_TIERED_CACHED_ADJ);
+        TIERED_CACHED_ADJ_DECAY_TIME = DeviceConfig.getLong(
+            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+            KEY_TIERED_CACHED_ADJ_DECAY_TIME,
+            DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME);
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     void dump(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -2108,6 +2135,11 @@
         pw.print("  "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
         pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
 
+        pw.print("  "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
+        pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
+        pw.print("  "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
+        pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
+
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
             pw.print("  mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index baa2f8e..b68d993 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1073,135 +1073,165 @@
     @GuardedBy({"mService", "mProcLock"})
     private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
+        if (mConstants.USE_TIERED_CACHED_ADJ) {
+            final long now = SystemClock.uptimeMillis();
+            for (int i = numLru - 1; i >= 0; i--) {
+                ProcessRecord app = lruList.get(i);
+                final ProcessStateRecord state = app.mState;
+                final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
+                        >= UNKNOWN_ADJ) {
+                    final ProcessServiceRecord psr = app.mServices;
+                    int targetAdj = CACHED_APP_MIN_ADJ;
 
-        // First update the OOM adjustment for each of the
-        // application processes based on their current state.
-        int curCachedAdj = CACHED_APP_MIN_ADJ;
-        int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
-        int curCachedImpAdj = 0;
-        int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
-        int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
+                    if (opt != null && opt.isFreezeExempt()) {
+                        // BIND_WAIVE_PRIORITY and the like get oom_adj 900
+                        targetAdj += 0;
+                    } else if ((state.getSetAdj() >= CACHED_APP_MIN_ADJ)
+                            && (state.getLastStateTime()
+                                    + mConstants.TIERED_CACHED_ADJ_DECAY_TIME) < now) {
+                        // Older cached apps get 950
+                        targetAdj += 50;
+                    } else {
+                        // Newer cached apps get 910
+                        targetAdj += 10;
+                    }
+                    state.setCurRawAdj(targetAdj);
+                    state.setCurAdj(psr.modifyRawOomAdj(targetAdj));
+                }
+            }
+        } else {
+            // First update the OOM adjustment for each of the
+            // application processes based on their current state.
+            int curCachedAdj = CACHED_APP_MIN_ADJ;
+            int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
+            int curCachedImpAdj = 0;
+            int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
+            int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
 
-        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
-        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
-                - emptyProcessLimit;
-        // Let's determine how many processes we have running vs.
-        // how many slots we have for background processes; we may want
-        // to put multiple processes in a slot of there are enough of
-        // them.
-        int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
-        if (numEmptyProcs > cachedProcessLimit) {
-            // If there are more empty processes than our limit on cached
-            // processes, then use the cached process limit for the factor.
-            // This ensures that the really old empty processes get pushed
-            // down to the bottom, so if we are running low on memory we will
-            // have a better chance at keeping around more cached processes
-            // instead of a gazillion empty processes.
-            numEmptyProcs = cachedProcessLimit;
-        }
-        int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
-                / mNumSlots;
-        if (cachedFactor < 1) cachedFactor = 1;
+            final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+            final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
+                                           - emptyProcessLimit;
+            // Let's determine how many processes we have running vs.
+            // how many slots we have for background processes; we may want
+            // to put multiple processes in a slot of there are enough of
+            // them.
+            int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
+            if (numEmptyProcs > cachedProcessLimit) {
+                // If there are more empty processes than our limit on cached
+                // processes, then use the cached process limit for the factor.
+                // This ensures that the really old empty processes get pushed
+                // down to the bottom, so if we are running low on memory we will
+                // have a better chance at keeping around more cached processes
+                // instead of a gazillion empty processes.
+                numEmptyProcs = cachedProcessLimit;
+            }
+            int cachedFactor = (mNumCachedHiddenProcs > 0
+                    ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
+                               / mNumSlots;
+            if (cachedFactor < 1) cachedFactor = 1;
 
-        int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;
-        if (emptyFactor < 1) emptyFactor = 1;
+            int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;
+            if (emptyFactor < 1) emptyFactor = 1;
 
-        int stepCached = -1;
-        int stepEmpty = -1;
-        int lastCachedGroup = 0;
-        int lastCachedGroupImportance = 0;
-        int lastCachedGroupUid = 0;
+            int stepCached = -1;
+            int stepEmpty = -1;
+            int lastCachedGroup = 0;
+            int lastCachedGroupImportance = 0;
+            int lastCachedGroupUid = 0;
 
-        for (int i = numLru - 1; i >= 0; i--) {
-            ProcessRecord app = lruList.get(i);
-            final ProcessStateRecord state = app.mState;
-            // If we haven't yet assigned the final cached adj
-            // to the process, do that now.
-            if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
+
+            for (int i = numLru - 1; i >= 0; i--) {
+                ProcessRecord app = lruList.get(i);
+                final ProcessStateRecord state = app.mState;
+                // If we haven't yet assigned the final cached adj
+                // to the process, do that now.
+                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
                     >= UNKNOWN_ADJ) {
-                final ProcessServiceRecord psr = app.mServices;
-                switch (state.getCurProcState()) {
-                    case PROCESS_STATE_CACHED_ACTIVITY:
-                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
-                    case ActivityManager.PROCESS_STATE_CACHED_RECENT:
-                        // Figure out the next cached level, taking into account groups.
-                        boolean inGroup = false;
-                        final int connectionGroup = psr.getConnectionGroup();
-                        if (connectionGroup != 0) {
-                            final int connectionImportance = psr.getConnectionImportance();
-                            if (lastCachedGroupUid == app.uid
+                    final ProcessServiceRecord psr = app.mServices;
+                    switch (state.getCurProcState()) {
+                        case PROCESS_STATE_CACHED_ACTIVITY:
+                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+                            // Figure out the next cached level, taking into account groups.
+                            boolean inGroup = false;
+                            final int connectionGroup = psr.getConnectionGroup();
+                            if (connectionGroup != 0) {
+                                final int connectionImportance = psr.getConnectionImportance();
+                                if (lastCachedGroupUid == app.uid
                                     && lastCachedGroup == connectionGroup) {
-                                // This is in the same group as the last process, just tweak
-                                // adjustment by importance.
-                                if (connectionImportance > lastCachedGroupImportance) {
-                                    lastCachedGroupImportance = connectionImportance;
-                                    if (curCachedAdj < nextCachedAdj
+                                    // This is in the same group as the last process, just tweak
+                                    // adjustment by importance.
+                                    if (connectionImportance > lastCachedGroupImportance) {
+                                        lastCachedGroupImportance = connectionImportance;
+                                        if (curCachedAdj < nextCachedAdj
                                             && curCachedAdj < CACHED_APP_MAX_ADJ) {
-                                        curCachedImpAdj++;
+                                            curCachedImpAdj++;
+                                        }
+                                    }
+                                    inGroup = true;
+                                } else {
+                                    lastCachedGroupUid = app.uid;
+                                    lastCachedGroup = connectionGroup;
+                                    lastCachedGroupImportance = connectionImportance;
+                                }
+                            }
+                            if (!inGroup && curCachedAdj != nextCachedAdj) {
+                                stepCached++;
+                                curCachedImpAdj = 0;
+                                if (stepCached >= cachedFactor) {
+                                    stepCached = 0;
+                                    curCachedAdj = nextCachedAdj;
+                                    nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
+                                        nextCachedAdj = CACHED_APP_MAX_ADJ;
                                     }
                                 }
-                                inGroup = true;
-                            } else {
-                                lastCachedGroupUid = app.uid;
-                                lastCachedGroup = connectionGroup;
-                                lastCachedGroupImportance = connectionImportance;
                             }
-                        }
-                        if (!inGroup && curCachedAdj != nextCachedAdj) {
-                            stepCached++;
-                            curCachedImpAdj = 0;
-                            if (stepCached >= cachedFactor) {
-                                stepCached = 0;
-                                curCachedAdj = nextCachedAdj;
-                                nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
-                                    nextCachedAdj = CACHED_APP_MAX_ADJ;
+                            // This process is a cached process holding activities...
+                            // assign it the next cached value for that type, and then
+                            // step that cached level.
+                            state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+                            state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
+                            if (DEBUG_LRU) {
+                                Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+                                        + " adj: " + state.getCurAdj()
+                                        + " (curCachedAdj=" + curCachedAdj
+                                        + " curCachedImpAdj=" + curCachedImpAdj + ")");
+                            }
+                            break;
+                        default:
+                            // Figure out the next cached level.
+                            if (curEmptyAdj != nextEmptyAdj) {
+                                stepEmpty++;
+                                if (stepEmpty >= emptyFactor) {
+                                    stepEmpty = 0;
+                                    curEmptyAdj = nextEmptyAdj;
+                                    nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
+                                        nextEmptyAdj = CACHED_APP_MAX_ADJ;
+                                    }
                                 }
                             }
-                        }
-                        // This process is a cached process holding activities...
-                        // assign it the next cached value for that type, and then
-                        // step that cached level.
-                        state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
-                        state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
-                        if (DEBUG_LRU) {
-                            Slog.d(TAG_LRU, "Assigning activity LRU #" + i
-                                    + " adj: " + state.getCurAdj()
-                                    + " (curCachedAdj=" + curCachedAdj
-                                    + " curCachedImpAdj=" + curCachedImpAdj + ")");
-                        }
-                        break;
-                    default:
-                        // Figure out the next cached level.
-                        if (curEmptyAdj != nextEmptyAdj) {
-                            stepEmpty++;
-                            if (stepEmpty >= emptyFactor) {
-                                stepEmpty = 0;
-                                curEmptyAdj = nextEmptyAdj;
-                                nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
-                                    nextEmptyAdj = CACHED_APP_MAX_ADJ;
-                                }
+                            // For everything else, assign next empty cached process
+                            // level and bump that up.  Note that this means that
+                            // long-running services that have dropped down to the
+                            // cached level will be treated as empty (since their process
+                            // state is still as a service), which is what we want.
+                            state.setCurRawAdj(curEmptyAdj);
+                            state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
+                            if (DEBUG_LRU) {
+                                Slog.d(TAG_LRU, "Assigning empty LRU #" + i
+                                        + " adj: " + state.getCurAdj()
+                                        + " (curEmptyAdj=" + curEmptyAdj
+                                        + ")");
                             }
-                        }
-                        // For everything else, assign next empty cached process
-                        // level and bump that up.  Note that this means that
-                        // long-running services that have dropped down to the
-                        // cached level will be treated as empty (since their process
-                        // state is still as a service), which is what we want.
-                        state.setCurRawAdj(curEmptyAdj);
-                        state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
-                        if (DEBUG_LRU) {
-                            Slog.d(TAG_LRU, "Assigning empty LRU #" + i
-                                    + " adj: " + state.getCurAdj() + " (curEmptyAdj=" + curEmptyAdj
-                                    + ")");
-                        }
-                        break;
+                            break;
+                    }
                 }
             }
         }
     }
-
     private long mNextNoKillDebugMessageTime;
 
     private double mLastFreeSwapPercent = 1.00;
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 dc7f3cd..485ce33 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -143,8 +143,8 @@
     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 final int FIRST_CACHED_ADJ = ProcessList.CACHED_APP_MIN_ADJ
-            + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+    private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ
+                                          + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
     private static Context sContext;
     private static PackageManagerInternal sPackageManagerInternal;
     private static ActivityManagerService sService;
@@ -208,6 +208,9 @@
                 new ActiveUids(sService, false));
         sService.mOomAdjuster.mAdjSeq = 10000;
         sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        if (sService.mConstants.USE_TIERED_CACHED_ADJ) {
+            sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 10;
+        }
     }
 
     @AfterClass
@@ -834,7 +837,7 @@
         updateOomAdj(client, app);
         doReturn(null).when(sService).getTopApp();
 
-        assertProcStates(app, PROCESS_STATE_SERVICE, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_SERVICE, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -882,7 +885,7 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1286,7 +1289,7 @@
         bindProvider(app, app, null, null, false);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1301,7 +1304,7 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app, client);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -2378,8 +2381,12 @@
                 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;
+        final int cachedAdj1 = sService.mConstants.USE_TIERED_CACHED_ADJ
+                               ? CACHED_APP_MIN_ADJ + 10
+                               : CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        final int cachedAdj2 = sService.mConstants.USE_TIERED_CACHED_ADJ
+                               ? CACHED_APP_MIN_ADJ + 10
+                               : cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
         doReturn(userOwner).when(sService.mUserController).getCurrentUserId();
 
         final ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();