Loading services/core/java/com/android/server/am/CacheOomRanker.java +101 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -62,6 +72,8 @@ public class CacheOomRanker { new ScoreComparator(); private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR = new CacheUseComparator(); private static final Comparator<RankedProcessRecord> RSS_COMPARATOR = new RssComparator(); private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR = new LastRssComparator(); private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR = Loading @@ -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; Loading @@ -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 Loading Loading @@ -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)) { Loading @@ -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. */ Loading Loading @@ -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, Loading Loading @@ -244,6 +285,8 @@ public class CacheOomRanker { float usesWeight; float rssWeight; int preserveTopNApps; boolean useFrequentRss; long rssUpdateRateMs; int[] lruPositions; RankedProcessRecord[] scoredProcessRecords; Loading @@ -252,6 +295,8 @@ public class CacheOomRanker { usesWeight = mUsesWeight; rssWeight = mRssWeight; preserveTopNApps = mPreserveTopNApps; useFrequentRss = mUseFrequentRss; rssUpdateRateMs = mRssUpdateRateMs; lruPositions = mLruPositions; scoredProcessRecords = mScoredProcessRecords; } Loading Loading @@ -296,6 +341,32 @@ 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); } } // 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 Loading @@ -305,9 +376,13 @@ public class CacheOomRanker { addToScore(scoredProcessRecords, lruWeight); } if (rssWeight > 0.0f) { 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) { Loading Loading @@ -385,6 +460,16 @@ public class CacheOomRanker { } } private static class RssComparator implements Comparator<RankedProcessRecord> { @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<RankedProcessRecord> { @Override public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { Loading @@ -397,4 +482,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); } } } services/core/java/com/android/server/am/ProcessStateRecord.java +29 −0 Original line number Diff line number Diff line Loading @@ -359,6 +359,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. */ Loading Loading @@ -1266,6 +1280,21 @@ final class ProcessStateRecord { return mLastCanKillOnBgRestrictedAndIdleTime; } 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) { Loading services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +162 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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( Loading @@ -91,6 +95,7 @@ public class CacheOomRankerTest { private int mNextUid = 30000; private int mNextPackageUid = 40000; private int mNextPackageName = 1; private Map<Integer, Long> mPidToRss; private TestExecutor mExecutor = new TestExecutor(); private CacheOomRanker mCacheOomRanker; Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -223,6 +238,52 @@ 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); ProcessList list = new ProcessList(); ArrayList<ProcessRecord> 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(); } @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); Loading Loading @@ -254,16 +315,91 @@ public class CacheOomRankerTest { 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<ProcessRecord> 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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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, Loading @@ -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), Loading @@ -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); Loading @@ -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); Loading Loading
services/core/java/com/android/server/am/CacheOomRanker.java +101 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -62,6 +72,8 @@ public class CacheOomRanker { new ScoreComparator(); private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR = new CacheUseComparator(); private static final Comparator<RankedProcessRecord> RSS_COMPARATOR = new RssComparator(); private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR = new LastRssComparator(); private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR = Loading @@ -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; Loading @@ -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 Loading Loading @@ -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)) { Loading @@ -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. */ Loading Loading @@ -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, Loading Loading @@ -244,6 +285,8 @@ public class CacheOomRanker { float usesWeight; float rssWeight; int preserveTopNApps; boolean useFrequentRss; long rssUpdateRateMs; int[] lruPositions; RankedProcessRecord[] scoredProcessRecords; Loading @@ -252,6 +295,8 @@ public class CacheOomRanker { usesWeight = mUsesWeight; rssWeight = mRssWeight; preserveTopNApps = mPreserveTopNApps; useFrequentRss = mUseFrequentRss; rssUpdateRateMs = mRssUpdateRateMs; lruPositions = mLruPositions; scoredProcessRecords = mScoredProcessRecords; } Loading Loading @@ -296,6 +341,32 @@ 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); } } // 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 Loading @@ -305,9 +376,13 @@ public class CacheOomRanker { addToScore(scoredProcessRecords, lruWeight); } if (rssWeight > 0.0f) { 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) { Loading Loading @@ -385,6 +460,16 @@ public class CacheOomRanker { } } private static class RssComparator implements Comparator<RankedProcessRecord> { @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<RankedProcessRecord> { @Override public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { Loading @@ -397,4 +482,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); } } }
services/core/java/com/android/server/am/ProcessStateRecord.java +29 −0 Original line number Diff line number Diff line Loading @@ -359,6 +359,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. */ Loading Loading @@ -1266,6 +1280,21 @@ final class ProcessStateRecord { return mLastCanKillOnBgRestrictedAndIdleTime; } 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) { Loading
services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +162 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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( Loading @@ -91,6 +95,7 @@ public class CacheOomRankerTest { private int mNextUid = 30000; private int mNextPackageUid = 40000; private int mNextPackageName = 1; private Map<Integer, Long> mPidToRss; private TestExecutor mExecutor = new TestExecutor(); private CacheOomRanker mCacheOomRanker; Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -223,6 +238,52 @@ 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); ProcessList list = new ProcessList(); ArrayList<ProcessRecord> 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(); } @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); Loading Loading @@ -254,16 +315,91 @@ public class CacheOomRankerTest { 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<ProcessRecord> 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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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, Loading @@ -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), Loading @@ -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); Loading @@ -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); Loading