From 500e43dcf574b896b9ed9399827295db7d0a2c82 Mon Sep 17 00:00:00 2001 From: Misha Wagner Date: Fri, 18 Jun 2021 08:41:48 +0100 Subject: [PATCH 1/5] Fix CacheOomRankerTest. These tests were using `assertThat(x).containsExactly(y)`, which does not check for order. We need to append `.inOrder()`. This exposes a couple of other issues in the tests (but fortunately none in the actual code!). Test: atest CacheOomRankerTest Bug: 191357172 Change-Id: I9facbdaf3b1fe7c91f5e387e7c44a9b1e9be2d6f Merged-In: I9facbdaf3b1fe7c91f5e387e7c44a9b1e9be2d6f (cherry picked from commit 44993614def14430633944bd18bf578c47337922) --- .../android/server/am/CacheOomRankerTest.java | 109 ++++++++++-------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java index 022fadcc6dd0f..a6732da1023e8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java @@ -21,12 +21,15 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; @@ -48,7 +51,10 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; -import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -62,6 +68,8 @@ import java.util.concurrent.TimeUnit; */ @RunWith(MockitoJUnitRunner.class) public class CacheOomRankerTest { + private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay( + ZoneOffset.UTC).toInstant(); @Mock private AppOpsService mAppOpsService; @@ -172,31 +180,32 @@ public class CacheOomRankerTest { ProcessList list = new ProcessList(); ArrayList processList = list.getLruProcessesLSP(); ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); processList.add(lastUsed40MinutesAgo); ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); processList.add(lastUsed42MinutesAgo); ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(60).toMillis(), 1024L, 10000); + NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000); processList.add(lastUsed60MinutesAgo); ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); processList.add(lastUsed15MinutesAgo); ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(17).toMillis(), 1024L, 20); + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20); processList.add(lastUsed17MinutesAgo); // Only re-ranking 5 entries so this should stay in most recent position. ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 1024L, 20); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20); processList.add(lastUsed30MinutesAgo); + list.setLruProcessServiceStartLSP(processList.size()); mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); // First 5 ordered by least recently used first, then last processes position unchanged. assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo, lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo, - lastUsed30MinutesAgo); + lastUsed30MinutesAgo).inOrder(); } @Test @@ -209,33 +218,34 @@ public class CacheOomRankerTest { ProcessList list = new ProcessList(); ArrayList processList = list.getLruProcessesLSP(); ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); processList.add(rss10k); ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); processList.add(rss20k); ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(60).toMillis(), 1024L, 10000); + NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000); processList.add(rss1k); ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); processList.add(rss100k); ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); processList.add(rss2k); ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 15 * 1024L, 20); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20); processList.add(rss15k); // Only re-ranking 6 entries so this should stay in most recent position. ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20); processList.add(rss16k); + list.setLruProcessServiceStartLSP(processList.size()); mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); // First 6 ordered by largest pss, then last processes position unchanged. assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, - rss16k); + rss16k).inOrder(); } @Test @@ -246,69 +256,70 @@ public class CacheOomRankerTest { /* lruWeight= */ 0.0f); ProcessList list = new ProcessList(); - list.setLruProcessServiceStartLSP(1); ArrayList processList = list.getLruProcessesLSP(); ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); processList.add(used1000); ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); processList.add(used2000); ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); processList.add(used10); ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); processList.add(used20); // Only re-ranking 6 entries so last two should stay in most recent position. ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); processList.add(used500); ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); processList.add(used200); + list.setLruProcessServiceStartLSP(processList.size()); mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); // First 4 ordered by uses, then last processes position unchanged. assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500, - used200); + used200).inOrder(); } @Test public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException { setConfig(/* numberToReRank= */ 4, - /* usesWeight= */ 0.5f, - /* pssWeight= */ 0.2f, - /* lruWeight= */ 0.3f); + /* usesWeight= */ 1.0f, + /* pssWeight= */ 0.0f, + /* lruWeight= */ 0.0f); ProcessList list = new ProcessList(); ArrayList processList = list.getLruProcessesLSP(); ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); processList.add(unknownAdj1); ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); processList.add(unknownAdj2); ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); processList.add(unknownAdj3); ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, - Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); processList.add(foregroundAdj); ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, - Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); processList.add(serviceAdj); ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, - Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); processList.add(systemAdj); + list.setLruProcessServiceStartLSP(processList.size()); // 6 Processes but only 3 in eligible for cache so no re-ranking. mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); // All positions unchanged. assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3, - foregroundAdj, serviceAdj, systemAdj); + foregroundAdj, serviceAdj, systemAdj).inOrder(); } @Test @@ -319,35 +330,35 @@ public class CacheOomRankerTest { /* lruWeight= */ 0.0f); ProcessList list = new ProcessList(); - list.setLruProcessServiceStartLSP(4); ArrayList processList = list.getLruProcessesLSP(); ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); processList.add(used1000); ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); processList.add(used2000); ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); processList.add(used10); ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); processList.add(used20); ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); processList.add(used500); ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); processList.add(used200); + list.setLruProcessServiceStartLSP(3); mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); // All positions unchanged. assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500, - used200); + used200).inOrder(); } - private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight) + private void setConfig(int numberToReRank, float usesWeight, float pssWeight, float lruWeight) throws InterruptedException { mExecutor.init(4); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -364,12 +375,12 @@ public class CacheOomRankerTest { false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT, - Float.toString(useWeight), + Float.toString(usesWeight), false); mExecutor.waitForLatch(); assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank); assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight); - assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight); + assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight); assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight); } @@ -382,7 +393,7 @@ public class CacheOomRankerTest { app.info.uid = mNextPackageUid++; // Exact value does not mater, it can be any state for which compaction is allowed. app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); - app.mState.setSetAdj(setAdj); + app.mState.setCurAdj(setAdj); app.setLastActivityTime(lastActivityTime); app.mProfile.setLastRss(lastRss); app.mState.setCached(false); @@ -390,6 +401,12 @@ public class CacheOomRankerTest { app.mState.setCached(false); app.mState.setCached(true); } + // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the + // app is currently launching. + ProcessStatsService processStatsService = new ProcessStatsService( + mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(), + "procstats")); + app.makeActive(mock(IApplicationThread.class), processStatsService); return app; } From 850b432adef2e16df110c370202f4d692fb99168 Mon Sep 17 00:00:00 2001 From: Misha Wagner Date: Wed, 16 Jun 2021 17:00:44 +0100 Subject: [PATCH 2/5] Allow CacheOomRanker to rerank when there's <8 processes in the cache. This allows us to increase the number of process we rerank without losing the impact of the reranking. This would also mean that the top apps in the cache get reranked more often, so we also add a flag to prevent the top N processes from being re-ranked. Discussed with vvaldes@ offline: this is closer to the logic in the simulations that decided the weights, so this logic change shouldn't break any assumptions of the model. Test: atest FrameworksMockingServicesTests:CacheOomRankerTest Bug: 191357172 Change-Id: I0533da0a70cfe7ed1b48e4f98be24a46f17f7845 Merged-In: I0533da0a70cfe7ed1b48e4f98be24a46f17f7845 (cherry picked from commit f913ae217a16b337a3125c2b49aad95a4946673e) --- .../com/android/server/am/CacheOomRanker.java | 132 ++++++++--- .../android/server/am/CacheOomRankerTest.java | 224 ++++++++++++++++-- 2 files changed, 315 insertions(+), 41 deletions(-) diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java index 50278fd81bb24..1ead7e3c589fe 100644 --- a/services/core/java/com/android/server/am/CacheOomRanker.java +++ b/services/core/java/com/android/server/am/CacheOomRanker.java @@ -38,16 +38,25 @@ public class CacheOomRanker { private static final boolean DEFAULT_USE_OOM_RE_RANKING = false; @VisibleForTesting static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank"; - @VisibleForTesting static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8; + @VisibleForTesting + static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8; + @VisibleForTesting + static final String KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS = + "oom_re_ranking_preserve_top_n_apps"; + @VisibleForTesting + static final int DEFAULT_PRESERVE_TOP_N_APPS = 3; @VisibleForTesting static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight"; - @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f; + @VisibleForTesting + static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f; @VisibleForTesting static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight"; - @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f; + @VisibleForTesting + static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f; @VisibleForTesting static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight"; - @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f; + @VisibleForTesting + static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f; private static final Comparator SCORED_PROCESS_RECORD_COMPARATOR = new ScoreComparator(); @@ -66,15 +75,21 @@ public class CacheOomRanker { @GuardedBy("mPhenotypeFlagLock") private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting + int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS; // Weight to apply to the LRU ordering. @GuardedBy("mPhenotypeFlagLock") - @VisibleForTesting float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT; + @VisibleForTesting + float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT; // Weight to apply to the ordering by number of times the process has been added to the cache. @GuardedBy("mPhenotypeFlagLock") - @VisibleForTesting float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT; + @VisibleForTesting + float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT; // Weight to apply to the ordering by RSS used by the processes. @GuardedBy("mPhenotypeFlagLock") - @VisibleForTesting float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT; + @VisibleForTesting + float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT; // Positions to replace in the lru list. @GuardedBy("mPhenotypeFlagLock") @@ -93,6 +108,8 @@ public class CacheOomRanker { updateUseOomReranking(); } else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) { updateNumberToReRank(); + } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) { + updatePreserveTopNApps(); } else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) { updateLruWeight(); } else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) { @@ -159,6 +176,19 @@ public class CacheOomRanker { return mScoredProcessRecords == null ? 0 : mScoredProcessRecords.length; } + @GuardedBy("mPhenotypeFlagLock") + private void updatePreserveTopNApps() { + int preserveTopNApps = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, DEFAULT_PRESERVE_TOP_N_APPS); + if (preserveTopNApps < 0) { + Slog.w(OomAdjuster.TAG, + "Found negative value for preserveTopNApps, setting to default: " + + preserveTopNApps); + preserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS; + } + mPreserveTopNApps = preserveTopNApps; + } + @GuardedBy("mPhenotypeFlagLock") private void updateLruWeight() { mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -183,6 +213,33 @@ public class CacheOomRanker { */ @GuardedBy({"mService", "mProcLock"}) void reRankLruCachedAppsLSP(ArrayList lruList, int lruProcessServiceStart) { + // The lruList is a list of processes ordered by how recently they were used. The + // least-recently-used apps are at the beginning of the list. We keep track of two + // indices in the lruList: + // + // getNumberToReRank=5, preserveTopNApps=3, lruProcessServiceStart=7, + // lruList= + // 0: app A ^ + // 1: app B | These apps are re-ranked, as they are the first five apps (see + // 2: app C | getNumberToReRank), excluding... + // 3: app D v + // 4: app E ^ + // 5: app F | The three most-recently-used apps in the cache (see preserveTopNApps). + // 6: app G v + // 7: service A ^ + // 8: service B | Everything beyond lruProcessServiceStart is ignored, as these aren't + // 9: service C | apps. + // 10: activity A | + // ... | + // + // `numProcessesEvaluated` moves across the apps (indices 0-6) or until we've found enough + // apps to re-rank, and made sure none of them are in the top `preserveTopNApps` apps. + // Re-ranked apps are copied into `scoredProcessRecords`, where the re-ranking calculation + // happens. + // + // Note that some apps in the `lruList` can be skipped, if they don't pass + //`appCanBeReRanked`. + float lruWeight; float usesWeight; float rssWeight; @@ -202,52 +259,67 @@ public class CacheOomRanker { return; } + int numProcessesEvaluated = 0; // Collect the least recently used processes to re-rank, only rank cached // processes further down the list than mLruProcessServiceStart. - int cachedProcessPos = 0; - for (int i = 0; i < lruProcessServiceStart - && cachedProcessPos < scoredProcessRecords.length; ++i) { - ProcessRecord app = lruList.get(i); + int numProcessesReRanked = 0; + while (numProcessesEvaluated < lruProcessServiceStart + && numProcessesReRanked < scoredProcessRecords.length) { + ProcessRecord process = lruList.get(numProcessesEvaluated); // Processes that will be assigned a cached oom adj score. - if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj() - >= ProcessList.UNKNOWN_ADJ) { - scoredProcessRecords[cachedProcessPos].proc = app; - scoredProcessRecords[cachedProcessPos].score = 0.0f; - lruPositions[cachedProcessPos] = i; - ++cachedProcessPos; + if (appCanBeReRanked(process)) { + scoredProcessRecords[numProcessesReRanked].proc = process; + scoredProcessRecords[numProcessesReRanked].score = 0.0f; + lruPositions[numProcessesReRanked] = numProcessesEvaluated; + ++numProcessesReRanked; } + ++numProcessesEvaluated; } - // TODO maybe ensure a certain number above this in the cache before re-ranking. - if (cachedProcessPos < scoredProcessRecords.length) { - // Ignore we don't have enough processes to worry about re-ranking. - return; + // Count how many apps we're not re-ranking (up to mPreserveTopNApps). + int numProcessesNotReRanked = 0; + while (numProcessesEvaluated < lruProcessServiceStart + && numProcessesNotReRanked < mPreserveTopNApps) { + ProcessRecord process = lruList.get(numProcessesEvaluated); + if (appCanBeReRanked(process)) { + numProcessesNotReRanked++; + } + numProcessesEvaluated++; + } + // Exclude the top `mPreserveTopNApps` apps from re-ranking. + if (numProcessesNotReRanked < mPreserveTopNApps) { + numProcessesReRanked -= mPreserveTopNApps - numProcessesNotReRanked; + if (numProcessesReRanked < 0) { + numProcessesReRanked = 0; + } } // Add scores for each of the weighted features we want to rank based on. if (lruWeight > 0.0f) { // This doesn't use the LRU list ordering as after the first re-ranking // that will no longer be lru. - Arrays.sort(scoredProcessRecords, LAST_ACTIVITY_TIME_COMPARATOR); + Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, + LAST_ACTIVITY_TIME_COMPARATOR); addToScore(scoredProcessRecords, lruWeight); } if (rssWeight > 0.0f) { synchronized (mService.mAppProfiler.mProfilerLock) { - Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR); + Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR); } addToScore(scoredProcessRecords, rssWeight); } if (usesWeight > 0.0f) { - Arrays.sort(scoredProcessRecords, CACHE_USE_COMPARATOR); + Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, CACHE_USE_COMPARATOR); addToScore(scoredProcessRecords, usesWeight); } // Re-rank by the new combined score. - Arrays.sort(scoredProcessRecords, SCORED_PROCESS_RECORD_COMPARATOR); + Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, + SCORED_PROCESS_RECORD_COMPARATOR); if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) { boolean printedHeader = false; - for (int i = 0; i < scoredProcessRecords.length; ++i) { + for (int i = 0; i < numProcessesReRanked; ++i) { if (scoredProcessRecords[i].proc.getPid() != lruList.get(lruPositions[i]).getPid()) { if (!printedHeader) { @@ -260,12 +332,18 @@ public class CacheOomRanker { } } - for (int i = 0; i < scoredProcessRecords.length; ++i) { + for (int i = 0; i < numProcessesReRanked; ++i) { lruList.set(lruPositions[i], scoredProcessRecords[i].proc); scoredProcessRecords[i].proc = null; } } + private static boolean appCanBeReRanked(ProcessRecord process) { + return !process.isKilledByAm() + && process.getThread() != null + && process.mState.getCurAdj() >= ProcessList.UNKNOWN_ADJ; + } + private static void addToScore(RankedProcessRecord[] scores, float weight) { for (int i = 1; i < scores.length; ++i) { scores[i].score += i * weight; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java index a6732da1023e8..15dfd266853ee 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java @@ -142,6 +142,15 @@ public class CacheOomRankerTest { assertThat(mCacheOomRanker.getNumberToReRank()) .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2); + mExecutor.init(); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, + Integer.toString(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1), + false); + mExecutor.waitForLatch(); + assertThat(mCacheOomRanker.mPreserveTopNApps) + .isEqualTo(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1); + mExecutor.init(); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, @@ -173,6 +182,7 @@ public class CacheOomRankerTest { @Test public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 5, + /* preserveTopNApps= */ 0, /* usesWeight= */ 0.0f, /* pssWeight= */ 0.0f, /* lruWeight= */1.0f); @@ -211,6 +221,7 @@ public class CacheOomRankerTest { @Test public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 0, /* usesWeight= */ 0.0f, /* pssWeight= */ 1.0f, /* lruWeight= */ 0.0f); @@ -251,6 +262,7 @@ public class CacheOomRankerTest { @Test public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 4, + /* preserveTopNApps= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -286,23 +298,24 @@ public class CacheOomRankerTest { } @Test - public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException { + public void reRankLruCachedApps_fewProcesses() throws InterruptedException { setConfig(/* numberToReRank= */ 4, + /* preserveTopNApps= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); ProcessList list = new ProcessList(); ArrayList processList = list.getLruProcessesLSP(); - ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); - processList.add(unknownAdj1); - ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + processList.add(used1000); + ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); - processList.add(unknownAdj2); - ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + processList.add(used2000); + ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); - processList.add(unknownAdj3); + processList.add(used10); ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); processList.add(foregroundAdj); @@ -314,17 +327,156 @@ public class CacheOomRankerTest { processList.add(systemAdj); list.setLruProcessServiceStartLSP(processList.size()); - // 6 Processes but only 3 in eligible for cache so no re-ranking. mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); - // All positions unchanged. - assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3, + // 6 processes, only 3 in eligible for cache, so only those are re-ranked. + assertThat(processList).containsExactly(used10, used1000, used2000, foregroundAdj, serviceAdj, systemAdj).inOrder(); } @Test - public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException { + public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException { setConfig(/* numberToReRank= */ 4, + /* preserveTopNApps= */ 0, + /* usesWeight= */ 1.0f, + /* pssWeight= */ 0.0f, + /* lruWeight= */ 0.0f); + + ProcessList list = new ProcessList(); + ArrayList processList = list.getLruProcessesLSP(); + ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); + processList.add(used1000); + ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); + processList.add(used2000); + ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); + processList.add(used10); + ProcessRecord service1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + processList.add(service1); + ProcessRecord service2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); + processList.add(service2); + ProcessRecord service3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); + processList.add(service3); + list.setLruProcessServiceStartLSP(3); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + + // Services unchanged, rest re-ranked. + assertThat(processList).containsExactly(used10, used1000, used2000, service1, service2, + service3).inOrder(); + } + + @Test + public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 0, + /* usesWeight= */ 1.0f, + /* pssWeight= */ 0.0f, + /* lruWeight= */ 0.0f); + + ProcessList set1List = new ProcessList(); + ArrayList set1ProcessList = set1List.getLruProcessesLSP(); + ProcessRecord set1Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); + set1ProcessList.add(set1Used1000); + ProcessRecord set1Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); + set1ProcessList.add(set1Used2000); + ProcessRecord set1Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); + set1ProcessList.add(set1Used10); + ProcessRecord set1Uses20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + set1ProcessList.add(set1Uses20); + ProcessRecord set1Uses500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); + set1ProcessList.add(set1Uses500); + ProcessRecord set1Uses200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); + set1ProcessList.add(set1Uses200); + set1List.setLruProcessServiceStartLSP(set1ProcessList.size()); + + mCacheOomRanker.reRankLruCachedAppsLSP(set1ProcessList, + set1List.getLruProcessServiceStartLOSP()); + assertThat(set1ProcessList).containsExactly(set1Used10, set1Uses20, set1Uses200, + set1Uses500, set1Used1000, set1Used2000).inOrder(); + + ProcessList set2List = new ProcessList(); + ArrayList set2ProcessList = set2List.getLruProcessesLSP(); + ProcessRecord set2Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); + set2ProcessList.add(set2Used1000); + ProcessRecord set2Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); + set2ProcessList.add(set2Used2000); + ProcessRecord set2Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); + set2ProcessList.add(set2Used10); + ProcessRecord set2ForegroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + set2ProcessList.add(set2ForegroundAdj); + ProcessRecord set2ServiceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); + set2ProcessList.add(set2ServiceAdj); + ProcessRecord set2SystemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); + set2ProcessList.add(set2SystemAdj); + set2List.setLruProcessServiceStartLSP(set2ProcessList.size()); + + mCacheOomRanker.reRankLruCachedAppsLSP(set2ProcessList, + set2List.getLruProcessServiceStartLOSP()); + assertThat(set2ProcessList).containsExactly(set2Used10, set2Used1000, set2Used2000, + set2ForegroundAdj, set2ServiceAdj, set2SystemAdj).inOrder(); + } + + @Test + public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 3, + /* usesWeight= */ 1.0f, + /* pssWeight= */ 0.0f, + /* lruWeight= */ 0.0f); + + ProcessList list = new ProcessList(); + ArrayList processList = list.getLruProcessesLSP(); + ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); + processList.add(used1000); + ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); + processList.add(used2000); + ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); + processList.add(used10); + // Preserving the top 3 processes, so these should not be re-ranked. + ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + processList.add(used20); + ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); + processList.add(used500); + ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); + processList.add(used200); + list.setLruProcessServiceStartLSP(processList.size()); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + + // First 3 ordered by uses, then last processes position unchanged. + assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500, + used200).inOrder(); + } + + @Test + public void reRankLruCachedApps_preservesTopNApps_allAppsUnchanged() + throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 100, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -349,22 +501,66 @@ public class CacheOomRankerTest { ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); processList.add(used200); - list.setLruProcessServiceStartLSP(3); + list.setLruProcessServiceStartLSP(processList.size()); mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); - // All positions unchanged. + // Nothing reordered, as we preserve the top 100 apps. assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500, used200).inOrder(); } - private void setConfig(int numberToReRank, float usesWeight, float pssWeight, float lruWeight) + @Test + public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault() + throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ -100, + /* usesWeight= */ 1.0f, + /* pssWeight= */ 0.0f, + /* lruWeight= */ 0.0f); + + ProcessList list = new ProcessList(); + ArrayList processList = list.getLruProcessesLSP(); + ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); + processList.add(used1000); + ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); + processList.add(used2000); + ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); + processList.add(used10); + // Negative preserveTopNApps interpreted as the default (3), so the last three are unranked. + ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + processList.add(used20); + ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); + processList.add(used500); + ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); + processList.add(used200); + list.setLruProcessServiceStartLSP(processList.size()); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + + // First 3 apps re-ranked, as preserveTopNApps is interpreted as 3. + assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500, + used200).inOrder(); + } + + private void setConfig(int numberToReRank, int preserveTopNApps, float usesWeight, + float pssWeight, float lruWeight) throws InterruptedException { mExecutor.init(4); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, Integer.toString(numberToReRank), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, + Integer.toString(preserveTopNApps), + false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, Float.toString(lruWeight), From f81d50e255b53cb2e3e54d7159978a8035b162f7 Mon Sep 17 00:00:00 2001 From: Misha Wagner Date: Tue, 10 Aug 2021 14:12:48 +0100 Subject: [PATCH 3/5] Fix race condition when using mPreserveTopNApps. Bug: 191357172 Test: atest CacheOomRankerTest Change-Id: I8cdf96ed1ddf56261e6ee953fb1078e961a0bc41 Merged-In: I8cdf96ed1ddf56261e6ee953fb1078e961a0bc41 (cherry picked from commit a0326c86ad318483ce05c30290fdba72c7b6e33d) --- .../java/com/android/server/am/CacheOomRanker.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java index 1ead7e3c589fe..bd7ee019e1eec 100644 --- a/services/core/java/com/android/server/am/CacheOomRanker.java +++ b/services/core/java/com/android/server/am/CacheOomRanker.java @@ -243,6 +243,7 @@ public class CacheOomRanker { float lruWeight; float usesWeight; float rssWeight; + int preserveTopNApps; int[] lruPositions; RankedProcessRecord[] scoredProcessRecords; @@ -250,6 +251,7 @@ public class CacheOomRanker { lruWeight = mLruWeight; usesWeight = mUsesWeight; rssWeight = mRssWeight; + preserveTopNApps = mPreserveTopNApps; lruPositions = mLruPositions; scoredProcessRecords = mScoredProcessRecords; } @@ -276,19 +278,19 @@ public class CacheOomRanker { ++numProcessesEvaluated; } - // Count how many apps we're not re-ranking (up to mPreserveTopNApps). + // Count how many apps we're not re-ranking (up to preserveTopNApps). int numProcessesNotReRanked = 0; while (numProcessesEvaluated < lruProcessServiceStart - && numProcessesNotReRanked < mPreserveTopNApps) { + && numProcessesNotReRanked < preserveTopNApps) { ProcessRecord process = lruList.get(numProcessesEvaluated); if (appCanBeReRanked(process)) { numProcessesNotReRanked++; } numProcessesEvaluated++; } - // Exclude the top `mPreserveTopNApps` apps from re-ranking. - if (numProcessesNotReRanked < mPreserveTopNApps) { - numProcessesReRanked -= mPreserveTopNApps - numProcessesNotReRanked; + // Exclude the top `preserveTopNApps` apps from re-ranking. + if (numProcessesNotReRanked < preserveTopNApps) { + numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked; if (numProcessesReRanked < 0) { numProcessesReRanked = 0; } From 7d611c750c3d518794b2e580539bf9a22222a41f Mon Sep 17 00:00:00 2001 From: Misha Wagner Date: Mon, 20 Sep 2021 13:42:27 +0100 Subject: [PATCH 4/5] Change CacheOomRanker's "uses" feature. The previous implementation was incorrect, as an app goes in and out of the cache several times during oom_adj calculation. If all apps started at the same time, then this would be OK - as they all get incremented the same amount, plus get incremented when they actually go in and out of the cache. This would result in the relative counts being correct. However, as all apps aren't started at the same time, the old implementation is instead a measure of how long the app has been started. So instead we use mSetProcState, which isn't changed on each oom_adj calculation. This measure should accurately reflect the number of times the process is used - whether that's by a user app open, or a content receiver call, or an intent being broadcast. See go/sim-v-impl for more details. Test: atest CacheOomRankerTest Bug: 196031723 Change-Id: I49d02362b8277f8472355e78412f79256c2289a1 Merged-In: I49d02362b8277f8472355e78412f79256c2289a1 (cherry picked from commit 480cc3123c5ee6ae4fff56599124794a0ff25aa4) --- core/java/android/app/ActivityManager.java | 5 +++++ .../com/android/server/am/ProcessStateRecord.java | 11 +++++------ .../src/com/android/server/am/CacheOomRankerTest.java | 9 +++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 4376d225e676d..f53c5b6c97482 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -771,6 +771,11 @@ public class ActivityManager { return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND; } + /** @hide Should this process state be considered in the cache? */ + public static final boolean isProcStateCached(int procState) { + return procState >= PROCESS_STATE_CACHED_ACTIVITY; + } + /** @hide Is this a foreground service type? */ public static boolean isForegroundService(int procState) { return procState == PROCESS_STATE_FOREGROUND_SERVICE; diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index d4474d6637695..7c3928570ae2b 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -577,6 +577,10 @@ final class ProcessStateRecord { @GuardedBy({"mService", "mProcLock"}) void setSetProcState(int setProcState) { + if (ActivityManager.isProcStateCached(mSetProcState) + && !ActivityManager.isProcStateCached(setProcState)) { + mCacheOomRankerUseCount++; + } mSetProcState = setProcState; } @@ -840,12 +844,7 @@ final class ProcessStateRecord { @GuardedBy("mService") void setCached(boolean cached) { - if (mCached != cached) { - mCached = cached; - if (cached) { - ++mCacheOomRankerUseCount; - } - } + mCached = cached; } @GuardedBy("mService") diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java index 15dfd266853ee..d0c9242969454 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import android.app.ActivityManager; import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Context; @@ -581,7 +582,7 @@ public class CacheOomRankerTest { } private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss, - int returnedToCacheCount) { + int wentToForegroundCount) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = "a.package.name" + mNextPackageName++; ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++); @@ -593,9 +594,9 @@ public class CacheOomRankerTest { app.setLastActivityTime(lastActivityTime); app.mProfile.setLastRss(lastRss); app.mState.setCached(false); - for (int i = 0; i < returnedToCacheCount; ++i) { - app.mState.setCached(false); - app.mState.setCached(true); + for (int i = 0; i < wentToForegroundCount; ++i) { + app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT); } // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the // app is currently launching. From 665e91e17196c499462553e2c463c39727ba8738 Mon Sep 17 00:00:00 2001 From: Misha Wagner Date: Mon, 20 Sep 2021 13:53:00 +0100 Subject: [PATCH 5/5] Use Process.getRss instead of mProfile.getLastRss in CacheOomRanker. RSS values are cached in ProcessStateRecord, and repopulated when they are >10s old. mProfile.getLastRss values can be 15s-2h stale, which is why we're using this implementation. My understanding is that the best solution would be to populate mProfile.getLastRss more often - but it is currently populated alongside PSS, which is more expensive to calculate. Therefore, rather than the more complex change of splitting up PSS & RSS collection, we opt for this simple (if not optimal) approach as CacheOomRanker is still experimental. Upon successful launch, we can revisit this. When logging the duration of CacheOomRanker calculations, we saw no significant increase in computation time with this change. If we turn off caching RSS values, we see a 10x increase in computation time. Test: atest CacheOomRankerTest Bug: 196031723 Merged-In: I8e80db2aab338df07f07842131a9d4b55bebd054 Change-Id: I8e80db2aab338df07f07842131a9d4b55bebd054 (cherry picked from commit 8bd226e360e0594f3421fa12abfa36340df170e4) --- .../com/android/server/am/CacheOomRanker.java | 104 ++++++++++- .../android/server/am/ProcessStateRecord.java | 29 +++ .../android/server/am/CacheOomRankerTest.java | 166 +++++++++++++++++- 3 files changed, 293 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java index bd7ee019e1eec..e6ffcfcd35101 100644 --- a/services/core/java/com/android/server/am/CacheOomRanker.java +++ b/services/core/java/com/android/server/am/CacheOomRanker.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.os.Process; +import android.os.SystemClock; import android.provider.DeviceConfig; import android.util.Slog; @@ -46,6 +48,14 @@ public class CacheOomRanker { @VisibleForTesting static final int DEFAULT_PRESERVE_TOP_N_APPS = 3; @VisibleForTesting + static final String KEY_OOM_RE_RANKING_USE_FREQUENT_RSS = "oom_re_ranking_rss_use_frequent_rss"; + @VisibleForTesting + static final boolean DEFAULT_USE_FREQUENT_RSS = true; + @VisibleForTesting + static final String KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS = "oom_re_ranking_rss_update_rate_ms"; + @VisibleForTesting + static final long DEFAULT_RSS_UPDATE_RATE_MS = 10_000; // 10 seconds + @VisibleForTesting static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight"; @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f; @@ -62,6 +72,8 @@ public class CacheOomRanker { new ScoreComparator(); private static final Comparator CACHE_USE_COMPARATOR = new CacheUseComparator(); + private static final Comparator RSS_COMPARATOR = + new RssComparator(); private static final Comparator LAST_RSS_COMPARATOR = new LastRssComparator(); private static final Comparator LAST_ACTIVITY_TIME_COMPARATOR = @@ -70,6 +82,7 @@ public class CacheOomRanker { private final Object mPhenotypeFlagLock = new Object(); private final ActivityManagerService mService; + private final ProcessDependencies mProcessDependencies; private final ActivityManagerGlobalLock mProcLock; private final Object mProfilerLock; @@ -78,6 +91,12 @@ public class CacheOomRanker { @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting + boolean mUseFrequentRss = DEFAULT_USE_FREQUENT_RSS; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting + long mRssUpdateRateMs = DEFAULT_RSS_UPDATE_RATE_MS; // Weight to apply to the LRU ordering. @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting @@ -110,6 +129,10 @@ public class CacheOomRanker { updateNumberToReRank(); } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) { updatePreserveTopNApps(); + } else if (KEY_OOM_RE_RANKING_USE_FREQUENT_RSS.equals(name)) { + updateUseFrequentRss(); + } else if (KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS.equals(name)) { + updateRssUpdateRateMs(); } else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) { updateLruWeight(); } else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) { @@ -123,9 +146,15 @@ public class CacheOomRanker { }; CacheOomRanker(final ActivityManagerService service) { + this(service, new ProcessDependenciesImpl()); + } + + @VisibleForTesting + CacheOomRanker(final ActivityManagerService service, ProcessDependencies processDependencies) { mService = service; mProcLock = service.mProcLock; mProfilerLock = service.mAppProfiler.mProfilerLock; + mProcessDependencies = processDependencies; } /** Load settings from device config and register a listener for changes. */ @@ -189,6 +218,18 @@ public class CacheOomRanker { mPreserveTopNApps = preserveTopNApps; } + @GuardedBy("mPhenotypeFlagLock") + private void updateRssUpdateRateMs() { + mRssUpdateRateMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, DEFAULT_RSS_UPDATE_RATE_MS); + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateUseFrequentRss() { + mUseFrequentRss = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, DEFAULT_USE_FREQUENT_RSS); + } + @GuardedBy("mPhenotypeFlagLock") private void updateLruWeight() { mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -244,6 +285,8 @@ public class CacheOomRanker { float usesWeight; float rssWeight; int preserveTopNApps; + boolean useFrequentRss; + long rssUpdateRateMs; int[] lruPositions; RankedProcessRecord[] scoredProcessRecords; @@ -252,6 +295,8 @@ public class CacheOomRanker { usesWeight = mUsesWeight; rssWeight = mRssWeight; preserveTopNApps = mPreserveTopNApps; + useFrequentRss = mUseFrequentRss; + rssUpdateRateMs = mRssUpdateRateMs; lruPositions = mLruPositions; scoredProcessRecords = mScoredProcessRecords; } @@ -296,6 +341,33 @@ public class CacheOomRanker { } } + if (useFrequentRss) { + // Update RSS values for re-ranked apps. + long nowMs = SystemClock.elapsedRealtime(); + for (int i = 0; i < numProcessesReRanked; ++i) { + RankedProcessRecord scoredProcessRecord = scoredProcessRecords[i]; + long sinceUpdateMs = + nowMs - scoredProcessRecord.proc.mState.getCacheOomRankerRssTimeMs(); + if (scoredProcessRecord.proc.mState.getCacheOomRankerRss() != 0 + && sinceUpdateMs < rssUpdateRateMs) { + continue; + } + + long[] rss = mProcessDependencies.getRss(scoredProcessRecord.proc.getPid()); + if (rss == null || rss.length == 0) { + Slog.e( + OomAdjuster.TAG, + "Process.getRss returned bad value, not re-ranking: " + + Arrays.toString(rss)); + return; + } + // First element is total RSS: + // frameworks/base/core/jni/android_util_Process.cpp:1192 + scoredProcessRecord.proc.mState.setCacheOomRankerRss(rss[0], nowMs); + scoredProcessRecord.proc.mProfile.setLastRss(rss[0]); + } + } + // Add scores for each of the weighted features we want to rank based on. if (lruWeight > 0.0f) { // This doesn't use the LRU list ordering as after the first re-ranking @@ -305,8 +377,12 @@ public class CacheOomRanker { addToScore(scoredProcessRecords, lruWeight); } if (rssWeight > 0.0f) { - synchronized (mService.mAppProfiler.mProfilerLock) { - Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR); + if (useFrequentRss) { + Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, RSS_COMPARATOR); + } else { + synchronized (mService.mAppProfiler.mProfilerLock) { + Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR); + } } addToScore(scoredProcessRecords, rssWeight); } @@ -385,6 +461,16 @@ public class CacheOomRanker { } } + private static class RssComparator implements Comparator { + @Override + public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { + // High RSS first to match least recently used. + return Long.compare( + o2.proc.mState.getCacheOomRankerRss(), + o1.proc.mState.getCacheOomRankerRss()); + } + } + private static class LastRssComparator implements Comparator { @Override public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { @@ -397,4 +483,18 @@ public class CacheOomRanker { public ProcessRecord proc; public float score; } + + /** + * Interface for mocking {@link Process} static methods. + */ + interface ProcessDependencies { + long[] getRss(int pid); + } + + private static class ProcessDependenciesImpl implements ProcessDependencies { + @Override + public long[] getRss(int pid) { + return Process.getRss(pid); + } + } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 7c3928570ae2b..206dd889fb705 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -342,6 +342,20 @@ final class ProcessStateRecord { @GuardedBy("mService") private int mCacheOomRankerUseCount; + /** + * Process memory usage (RSS). + * + * Periodically populated by {@code CacheOomRanker}, stored in this object to cache the values. + */ + @GuardedBy("mService") + private long mCacheOomRankerRss; + + /** + * The last time, in milliseconds since boot, since {@link #mCacheOomRankerRss} was updated. + */ + @GuardedBy("mService") + private long mCacheOomRankerRssTimeMs; + /** * Whether or not this process is reachable from given process. */ @@ -1150,6 +1164,21 @@ final class ProcessStateRecord { return mLastInvisibleTime; } + public void setCacheOomRankerRss(long rss, long rssTimeMs) { + mCacheOomRankerRss = rss; + mCacheOomRankerRssTimeMs = rssTimeMs; + } + + @GuardedBy("mService") + public long getCacheOomRankerRss() { + return mCacheOomRankerRss; + } + + @GuardedBy("mService") + public long getCacheOomRankerRssTimeMs() { + return mCacheOomRankerRssTimeMs; + } + @GuardedBy({"mService", "mProcLock"}) void dump(PrintWriter pw, String prefix, long nowUptime) { if (mReportedInteraction || mFgInteractionTime != 0) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java index d0c9242969454..609768c0e62a4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java @@ -57,6 +57,9 @@ import java.time.LocalDate; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -67,6 +70,7 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest FrameworksMockingServicesTests:CacheOomRankerTest */ +@SuppressWarnings("GuardedBy") // No tests are concurrent, so no need to test locking. @RunWith(MockitoJUnitRunner.class) public class CacheOomRankerTest { private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay( @@ -91,6 +95,7 @@ public class CacheOomRankerTest { private int mNextUid = 30000; private int mNextPackageUid = 40000; private int mNextPackageName = 1; + private Map mPidToRss; private TestExecutor mExecutor = new TestExecutor(); private CacheOomRanker mCacheOomRanker; @@ -116,7 +121,15 @@ public class CacheOomRankerTest { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); - mCacheOomRanker = new CacheOomRanker(mAms); + mPidToRss = new HashMap<>(); + mCacheOomRanker = new CacheOomRanker( + mAms, + pid -> { + Long rss = mPidToRss.get(pid); + assertThat(rss).isNotNull(); + return new long[]{rss}; + } + ); mCacheOomRanker.init(mExecutor); } @@ -184,6 +197,8 @@ public class CacheOomRankerTest { public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 5, /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 0.0f, /* pssWeight= */ 0.0f, /* lruWeight= */1.0f); @@ -223,6 +238,8 @@ public class CacheOomRankerTest { public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 6, /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 0.0f, /* pssWeight= */ 1.0f, /* lruWeight= */ 0.0f); @@ -260,10 +277,129 @@ public class CacheOomRankerTest { rss16k).inOrder(); } + @Test + public void reRankLruCachedApps_rssImpactsOrdering_cachedRssValues() + throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 10000000, + /* usesWeight= */ 0.0f, + /* pssWeight= */ 1.0f, + /* lruWeight= */ 0.0f); + + ProcessList list = new ProcessList(); + ArrayList processList = list.getLruProcessesLSP(); + ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); + processList.add(rss10k); + ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); + processList.add(rss20k); + ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000); + processList.add(rss1k); + ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); + processList.add(rss100k); + ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + processList.add(rss2k); + ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20); + processList.add(rss15k); + // Only re-ranking 6 entries so this should stay in most recent position. + ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20); + processList.add(rss16k); + list.setLruProcessServiceStartLSP(processList.size()); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + // First 6 ordered by largest pss, then last processes position unchanged. + assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, + rss16k).inOrder(); + + // Clear mPidToRss so that Process.getRss calls fail. + mPidToRss.clear(); + // Mix up the process list to ensure that CacheOomRanker actually re-ranks. + Collections.swap(processList, 0, 1); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + // Re ranking is the same. + assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, + rss16k).inOrder(); + } + + @Test + public void reRankLruCachedApps_rssImpactsOrdering_profileRss() + throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 0, + /* useFrequentRss= */ false, + /* rssUpdateRateMs= */ 10000000, + /* usesWeight= */ 0.0f, + /* pssWeight= */ 1.0f, + /* lruWeight= */ 0.0f); + + ProcessList list = new ProcessList(); + ArrayList processList = list.getLruProcessesLSP(); + ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 0L, 1000); + rss10k.mProfile.setLastRss(10 * 1024L); + processList.add(rss10k); + ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 0L, 2000); + rss20k.mProfile.setLastRss(20 * 1024L); + processList.add(rss20k); + ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10000); + rss1k.mProfile.setLastRss(1024L); + processList.add(rss1k); + ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10); + rss100k.mProfile.setLastRss(100 * 1024L); + processList.add(rss100k); + ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 0L, 20); + rss2k.mProfile.setLastRss(2 * 1024L); + processList.add(rss2k); + ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20); + rss15k.mProfile.setLastRss(15 * 1024L); + processList.add(rss15k); + // Only re-ranking 6 entries so this should stay in most recent position. + ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20); + rss16k.mProfile.setLastRss(16 * 1024L); + processList.add(rss16k); + list.setLruProcessServiceStartLSP(processList.size()); + + // This should not be used, as RSS values are taken from mProfile. + mPidToRss.clear(); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + // First 6 ordered by largest pss, then last processes position unchanged. + assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, + rss16k).inOrder(); + + // Clear mPidToRss so that Process.getRss calls fail. + mPidToRss.clear(); + // Mix up the process list to ensure that CacheOomRanker actually re-ranks. + Collections.swap(processList, 0, 1); + + mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); + // Re ranking is the same. + assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, + rss16k).inOrder(); + } + + @Test public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 4, /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -302,6 +438,8 @@ public class CacheOomRankerTest { public void reRankLruCachedApps_fewProcesses() throws InterruptedException { setConfig(/* numberToReRank= */ 4, /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -339,6 +477,8 @@ public class CacheOomRankerTest { public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException { setConfig(/* numberToReRank= */ 4, /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -376,6 +516,8 @@ public class CacheOomRankerTest { public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException { setConfig(/* numberToReRank= */ 6, /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -439,6 +581,8 @@ public class CacheOomRankerTest { public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException { setConfig(/* numberToReRank= */ 6, /* preserveTopNApps= */ 3, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -478,6 +622,8 @@ public class CacheOomRankerTest { throws InterruptedException { setConfig(/* numberToReRank= */ 6, /* preserveTopNApps= */ 100, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -516,6 +662,8 @@ public class CacheOomRankerTest { throws InterruptedException { setConfig(/* numberToReRank= */ 6, /* preserveTopNApps= */ -100, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* lruWeight= */ 0.0f); @@ -550,8 +698,8 @@ public class CacheOomRankerTest { used200).inOrder(); } - private void setConfig(int numberToReRank, int preserveTopNApps, float usesWeight, - float pssWeight, float lruWeight) + private void setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss, + long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight) throws InterruptedException { mExecutor.init(4); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -562,6 +710,14 @@ public class CacheOomRankerTest { CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, Integer.toString(preserveTopNApps), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CacheOomRanker.KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, + Boolean.toString(useFrequentRss), + false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CacheOomRanker.KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, + Long.toString(rssUpdateRateMs), + false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, Float.toString(lruWeight), @@ -576,6 +732,8 @@ public class CacheOomRankerTest { false); mExecutor.waitForLatch(); assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank); + assertThat(mCacheOomRanker.mUseFrequentRss).isEqualTo(useFrequentRss); + assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs); assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight); assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight); assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight); @@ -592,7 +750,7 @@ public class CacheOomRankerTest { app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); app.mState.setCurAdj(setAdj); app.setLastActivityTime(lastActivityTime); - app.mProfile.setLastRss(lastRss); + mPidToRss.put(app.getPid(), lastRss); app.mState.setCached(false); for (int i = 0; i < wentToForegroundCount; ++i) { app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);