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/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java index 50278fd81bb24..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; @@ -38,21 +40,40 @@ 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_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; + @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(); 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 = @@ -61,20 +82,33 @@ 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; @GuardedBy("mPhenotypeFlagLock") private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING; + @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 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 +127,12 @@ 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_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)) { @@ -106,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. */ @@ -159,6 +205,31 @@ 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 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, @@ -183,9 +254,39 @@ 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; + int preserveTopNApps; + boolean useFrequentRss; + long rssUpdateRateMs; int[] lruPositions; RankedProcessRecord[] scoredProcessRecords; @@ -193,6 +294,9 @@ public class CacheOomRanker { lruWeight = mLruWeight; usesWeight = mUsesWeight; rssWeight = mRssWeight; + preserveTopNApps = mPreserveTopNApps; + useFrequentRss = mUseFrequentRss; + rssUpdateRateMs = mRssUpdateRateMs; lruPositions = mLruPositions; scoredProcessRecords = mScoredProcessRecords; } @@ -202,52 +306,98 @@ 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; + } + + // Count how many apps we're not re-ranking (up to preserveTopNApps). + int numProcessesNotReRanked = 0; + while (numProcessesEvaluated < lruProcessServiceStart + && numProcessesNotReRanked < preserveTopNApps) { + ProcessRecord process = lruList.get(numProcessesEvaluated); + if (appCanBeReRanked(process)) { + numProcessesNotReRanked++; + } + numProcessesEvaluated++; + } + // Exclude the top `preserveTopNApps` apps from re-ranking. + if (numProcessesNotReRanked < preserveTopNApps) { + numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked; + if (numProcessesReRanked < 0) { + numProcessesReRanked = 0; } } - // 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; + 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 // 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); + 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); } 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 +410,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; @@ -305,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) { @@ -317,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 d4474d6637695..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. */ @@ -577,6 +591,10 @@ final class ProcessStateRecord { @GuardedBy({"mService", "mProcLock"}) void setSetProcState(int setProcState) { + if (ActivityManager.isProcStateCached(mSetProcState) + && !ActivityManager.isProcStateCached(setProcState)) { + mCacheOomRankerUseCount++; + } mSetProcState = setProcState; } @@ -840,12 +858,7 @@ final class ProcessStateRecord { @GuardedBy("mService") void setCached(boolean cached) { - if (mCached != cached) { - mCached = cached; - if (cached) { - ++mCacheOomRankerUseCount; - } - } + mCached = cached; } @GuardedBy("mService") @@ -1151,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 022fadcc6dd0f..609768c0e62a4 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,16 @@ 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.ActivityManager; +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,8 +52,14 @@ 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.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -60,8 +70,11 @@ 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( + ZoneOffset.UTC).toInstant(); @Mock private AppOpsService mAppOpsService; @@ -82,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; @@ -107,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); } @@ -134,6 +156,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, @@ -165,6 +196,9 @@ public class CacheOomRankerTest { @Test public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 5, + /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 0.0f, /* pssWeight= */ 0.0f, /* lruWeight= */1.0f); @@ -172,36 +206,40 @@ 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 public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException { setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 0.0f, /* pssWeight= */ 1.0f, /* lruWeight= */ 0.0f); @@ -209,151 +247,477 @@ 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 + 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); 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); - - ProcessList list = new ProcessList(); - ArrayList processList = list.getLruProcessesLSP(); - ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); - processList.add(unknownAdj1); - ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); - processList.add(unknownAdj2); - ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); - processList.add(unknownAdj3); - ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, - Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); - processList.add(foregroundAdj); - ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, - Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); - processList.add(serviceAdj); - ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, - Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); - processList.add(systemAdj); - - // 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); - } - - @Test - public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException { + public void reRankLruCachedApps_fewProcesses() throws InterruptedException { setConfig(/* numberToReRank= */ 4, + /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 0, /* usesWeight= */ 1.0f, /* pssWeight= */ 0.0f, /* 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); - processList.add(used20); - ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); - processList.add(used500); - ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, - Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); - processList.add(used200); + ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, + NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); + processList.add(foregroundAdj); + ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); + processList.add(serviceAdj); + ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, + NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); + processList.add(systemAdj); + list.setLruProcessServiceStartLSP(processList.size()); mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); - // All positions unchanged. - assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500, - used200); + // 6 processes, only 3 in eligible for cache, so only those are re-ranked. + assertThat(processList).containsExactly(used10, used1000, used2000, + foregroundAdj, serviceAdj, systemAdj).inOrder(); } - private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight) + @Test + public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException { + setConfig(/* numberToReRank= */ 4, + /* preserveTopNApps= */ 0, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 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, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 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, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 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); + // 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, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 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 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()); + + // Nothing reordered, as we preserve the top 100 apps. + assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500, + used200).inOrder(); + } + + @Test + public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault() + throws InterruptedException { + setConfig(/* numberToReRank= */ 6, + /* preserveTopNApps= */ -100, + /* useFrequentRss= */ true, + /* rssUpdateRateMs= */ 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); + // 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, boolean useFrequentRss, + long rssUpdateRateMs, 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_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), @@ -364,17 +728,19 @@ 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.mUseFrequentRss).isEqualTo(useFrequentRss); + assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs); assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight); - assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight); + assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight); assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight); } 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++); @@ -382,14 +748,20 @@ 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); + mPidToRss.put(app.getPid(), 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. + ProcessStatsService processStatsService = new ProcessStatsService( + mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(), + "procstats")); + app.makeActive(mock(IApplicationThread.class), processStatsService); return app; }