1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.am; 18 19 import android.os.Process; 20 import android.os.SystemClock; 21 import android.provider.DeviceConfig; 22 import android.util.Slog; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.internal.annotations.VisibleForTesting; 26 27 import java.io.PrintWriter; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Comparator; 31 import java.util.concurrent.Executor; 32 33 /** 34 * Class to re-rank a number of the least recently used processes before they 35 * are assigned oom adjust scores. 36 */ 37 public class CacheOomRanker { 38 @VisibleForTesting 39 static final String KEY_USE_OOM_RE_RANKING = "use_oom_re_ranking"; 40 private static final boolean DEFAULT_USE_OOM_RE_RANKING = false; 41 @VisibleForTesting 42 static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank"; 43 @VisibleForTesting 44 static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8; 45 @VisibleForTesting 46 static final String KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS = 47 "oom_re_ranking_preserve_top_n_apps"; 48 @VisibleForTesting 49 static final int DEFAULT_PRESERVE_TOP_N_APPS = 3; 50 @VisibleForTesting 51 static final String KEY_OOM_RE_RANKING_USE_FREQUENT_RSS = "oom_re_ranking_rss_use_frequent_rss"; 52 @VisibleForTesting 53 static final boolean DEFAULT_USE_FREQUENT_RSS = true; 54 @VisibleForTesting 55 static final String KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS = "oom_re_ranking_rss_update_rate_ms"; 56 @VisibleForTesting 57 static final long DEFAULT_RSS_UPDATE_RATE_MS = 10_000; // 10 seconds 58 @VisibleForTesting 59 static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight"; 60 @VisibleForTesting 61 static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f; 62 @VisibleForTesting 63 static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight"; 64 @VisibleForTesting 65 static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f; 66 @VisibleForTesting 67 static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight"; 68 @VisibleForTesting 69 static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f; 70 71 private static final Comparator<RankedProcessRecord> SCORED_PROCESS_RECORD_COMPARATOR = 72 new ScoreComparator(); 73 private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR = 74 new CacheUseComparator(); 75 private static final Comparator<RankedProcessRecord> RSS_COMPARATOR = 76 new RssComparator(); 77 private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR = 78 new LastRssComparator(); 79 private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR = 80 new LastActivityTimeComparator(); 81 82 private final Object mPhenotypeFlagLock = new Object(); 83 84 private final ActivityManagerService mService; 85 private final ProcessDependencies mProcessDependencies; 86 private final ActivityManagerGlobalLock mProcLock; 87 private final Object mProfilerLock; 88 89 @GuardedBy("mPhenotypeFlagLock") 90 private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING; 91 @GuardedBy("mPhenotypeFlagLock") 92 @VisibleForTesting 93 int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS; 94 @GuardedBy("mPhenotypeFlagLock") 95 @VisibleForTesting 96 boolean mUseFrequentRss = DEFAULT_USE_FREQUENT_RSS; 97 @GuardedBy("mPhenotypeFlagLock") 98 @VisibleForTesting 99 long mRssUpdateRateMs = DEFAULT_RSS_UPDATE_RATE_MS; 100 // Weight to apply to the LRU ordering. 101 @GuardedBy("mPhenotypeFlagLock") 102 @VisibleForTesting 103 float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT; 104 // Weight to apply to the ordering by number of times the process has been added to the cache. 105 @GuardedBy("mPhenotypeFlagLock") 106 @VisibleForTesting 107 float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT; 108 // Weight to apply to the ordering by RSS used by the processes. 109 @GuardedBy("mPhenotypeFlagLock") 110 @VisibleForTesting 111 float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT; 112 113 // Positions to replace in the lru list. 114 @GuardedBy("mPhenotypeFlagLock") 115 private int[] mLruPositions; 116 // Processes to re-rank 117 @GuardedBy("mPhenotypeFlagLock") 118 private RankedProcessRecord[] mScoredProcessRecords; 119 120 private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener = 121 new DeviceConfig.OnPropertiesChangedListener() { 122 @Override 123 public void onPropertiesChanged(DeviceConfig.Properties properties) { 124 synchronized (mPhenotypeFlagLock) { 125 for (String name : properties.getKeyset()) { 126 if (KEY_USE_OOM_RE_RANKING.equals(name)) { 127 updateUseOomReranking(); 128 } else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) { 129 updateNumberToReRank(); 130 } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) { 131 updatePreserveTopNApps(); 132 } else if (KEY_OOM_RE_RANKING_USE_FREQUENT_RSS.equals(name)) { 133 updateUseFrequentRss(); 134 } else if (KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS.equals(name)) { 135 updateRssUpdateRateMs(); 136 } else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) { 137 updateLruWeight(); 138 } else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) { 139 updateUsesWeight(); 140 } else if (KEY_OOM_RE_RANKING_RSS_WEIGHT.equals(name)) { 141 updateRssWeight(); 142 } 143 } 144 } 145 } 146 }; 147 CacheOomRanker(final ActivityManagerService service)148 CacheOomRanker(final ActivityManagerService service) { 149 this(service, new ProcessDependenciesImpl()); 150 } 151 152 @VisibleForTesting CacheOomRanker(final ActivityManagerService service, ProcessDependencies processDependencies)153 CacheOomRanker(final ActivityManagerService service, ProcessDependencies processDependencies) { 154 mService = service; 155 mProcLock = service.mProcLock; 156 mProfilerLock = service.mAppProfiler.mProfilerLock; 157 mProcessDependencies = processDependencies; 158 } 159 160 /** Load settings from device config and register a listener for changes. */ init(Executor executor)161 public void init(Executor executor) { 162 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 163 executor, mOnFlagsChangedListener); 164 synchronized (mPhenotypeFlagLock) { 165 updateUseOomReranking(); 166 updateNumberToReRank(); 167 updateLruWeight(); 168 updateUsesWeight(); 169 updateRssWeight(); 170 } 171 } 172 173 /** 174 * Returns whether oom re-ranking is enabled. 175 */ useOomReranking()176 public boolean useOomReranking() { 177 synchronized (mPhenotypeFlagLock) { 178 return mUseOomReRanking; 179 } 180 } 181 182 @GuardedBy("mPhenotypeFlagLock") updateUseOomReranking()183 private void updateUseOomReranking() { 184 mUseOomReRanking = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 185 KEY_USE_OOM_RE_RANKING, DEFAULT_USE_OOM_RE_RANKING); 186 } 187 188 @GuardedBy("mPhenotypeFlagLock") updateNumberToReRank()189 private void updateNumberToReRank() { 190 int previousNumberToReRank = getNumberToReRank(); 191 int numberToReRank = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 192 KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK); 193 if (previousNumberToReRank != numberToReRank) { 194 mScoredProcessRecords = new RankedProcessRecord[numberToReRank]; 195 for (int i = 0; i < mScoredProcessRecords.length; ++i) { 196 mScoredProcessRecords[i] = new RankedProcessRecord(); 197 } 198 mLruPositions = new int[numberToReRank]; 199 } 200 } 201 202 @GuardedBy("mPhenotypeFlagLock") 203 @VisibleForTesting getNumberToReRank()204 int getNumberToReRank() { 205 return mScoredProcessRecords == null ? 0 : mScoredProcessRecords.length; 206 } 207 208 @GuardedBy("mPhenotypeFlagLock") updatePreserveTopNApps()209 private void updatePreserveTopNApps() { 210 int preserveTopNApps = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 211 KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, DEFAULT_PRESERVE_TOP_N_APPS); 212 if (preserveTopNApps < 0) { 213 Slog.w(OomAdjuster.TAG, 214 "Found negative value for preserveTopNApps, setting to default: " 215 + preserveTopNApps); 216 preserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS; 217 } 218 mPreserveTopNApps = preserveTopNApps; 219 } 220 221 @GuardedBy("mPhenotypeFlagLock") updateRssUpdateRateMs()222 private void updateRssUpdateRateMs() { 223 mRssUpdateRateMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 224 KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, DEFAULT_RSS_UPDATE_RATE_MS); 225 } 226 227 @GuardedBy("mPhenotypeFlagLock") updateUseFrequentRss()228 private void updateUseFrequentRss() { 229 mUseFrequentRss = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 230 KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, DEFAULT_USE_FREQUENT_RSS); 231 } 232 233 @GuardedBy("mPhenotypeFlagLock") updateLruWeight()234 private void updateLruWeight() { 235 mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 236 KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT); 237 } 238 239 @GuardedBy("mPhenotypeFlagLock") updateUsesWeight()240 private void updateUsesWeight() { 241 mUsesWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 242 KEY_OOM_RE_RANKING_USES_WEIGHT, DEFAULT_OOM_RE_RANKING_USES_WEIGHT); 243 } 244 245 @GuardedBy("mPhenotypeFlagLock") updateRssWeight()246 private void updateRssWeight() { 247 mRssWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 248 KEY_OOM_RE_RANKING_RSS_WEIGHT, DEFAULT_OOM_RE_RANKING_RSS_WEIGHT); 249 } 250 251 /** 252 * Re-rank the cached processes in the lru list with a weighted ordering 253 * of lru, rss size and number of times the process has been put in the cache. 254 */ 255 @GuardedBy({"mService", "mProcLock"}) reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart)256 void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) { 257 // The lruList is a list of processes ordered by how recently they were used. The 258 // least-recently-used apps are at the beginning of the list. We keep track of two 259 // indices in the lruList: 260 // 261 // getNumberToReRank=5, preserveTopNApps=3, lruProcessServiceStart=7, 262 // lruList= 263 // 0: app A ^ 264 // 1: app B | These apps are re-ranked, as they are the first five apps (see 265 // 2: app C | getNumberToReRank), excluding... 266 // 3: app D v 267 // 4: app E ^ 268 // 5: app F | The three most-recently-used apps in the cache (see preserveTopNApps). 269 // 6: app G v 270 // 7: service A ^ 271 // 8: service B | Everything beyond lruProcessServiceStart is ignored, as these aren't 272 // 9: service C | apps. 273 // 10: activity A | 274 // ... | 275 // 276 // `numProcessesEvaluated` moves across the apps (indices 0-6) or until we've found enough 277 // apps to re-rank, and made sure none of them are in the top `preserveTopNApps` apps. 278 // Re-ranked apps are copied into `scoredProcessRecords`, where the re-ranking calculation 279 // happens. 280 // 281 // Note that some apps in the `lruList` can be skipped, if they don't pass 282 //`appCanBeReRanked`. 283 284 float lruWeight; 285 float usesWeight; 286 float rssWeight; 287 int preserveTopNApps; 288 boolean useFrequentRss; 289 long rssUpdateRateMs; 290 int[] lruPositions; 291 RankedProcessRecord[] scoredProcessRecords; 292 293 synchronized (mPhenotypeFlagLock) { 294 lruWeight = mLruWeight; 295 usesWeight = mUsesWeight; 296 rssWeight = mRssWeight; 297 preserveTopNApps = mPreserveTopNApps; 298 useFrequentRss = mUseFrequentRss; 299 rssUpdateRateMs = mRssUpdateRateMs; 300 lruPositions = mLruPositions; 301 scoredProcessRecords = mScoredProcessRecords; 302 } 303 304 // Don't re-rank if the class hasn't been initialized with defaults. 305 if (lruPositions == null || scoredProcessRecords == null) { 306 return; 307 } 308 309 int numProcessesEvaluated = 0; 310 // Collect the least recently used processes to re-rank, only rank cached 311 // processes further down the list than mLruProcessServiceStart. 312 int numProcessesReRanked = 0; 313 while (numProcessesEvaluated < lruProcessServiceStart 314 && numProcessesReRanked < scoredProcessRecords.length) { 315 ProcessRecord process = lruList.get(numProcessesEvaluated); 316 // Processes that will be assigned a cached oom adj score. 317 if (appCanBeReRanked(process)) { 318 scoredProcessRecords[numProcessesReRanked].proc = process; 319 scoredProcessRecords[numProcessesReRanked].score = 0.0f; 320 lruPositions[numProcessesReRanked] = numProcessesEvaluated; 321 ++numProcessesReRanked; 322 } 323 ++numProcessesEvaluated; 324 } 325 326 // Count how many apps we're not re-ranking (up to preserveTopNApps). 327 int numProcessesNotReRanked = 0; 328 while (numProcessesEvaluated < lruProcessServiceStart 329 && numProcessesNotReRanked < preserveTopNApps) { 330 ProcessRecord process = lruList.get(numProcessesEvaluated); 331 if (appCanBeReRanked(process)) { 332 numProcessesNotReRanked++; 333 } 334 numProcessesEvaluated++; 335 } 336 // Exclude the top `preserveTopNApps` apps from re-ranking. 337 if (numProcessesNotReRanked < preserveTopNApps) { 338 numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked; 339 if (numProcessesReRanked < 0) { 340 numProcessesReRanked = 0; 341 } 342 } 343 344 if (useFrequentRss) { 345 // Update RSS values for re-ranked apps. 346 long nowMs = SystemClock.elapsedRealtime(); 347 for (int i = 0; i < numProcessesReRanked; ++i) { 348 RankedProcessRecord scoredProcessRecord = scoredProcessRecords[i]; 349 long sinceUpdateMs = 350 nowMs - scoredProcessRecord.proc.mState.getCacheOomRankerRssTimeMs(); 351 if (scoredProcessRecord.proc.mState.getCacheOomRankerRss() != 0 352 && sinceUpdateMs < rssUpdateRateMs) { 353 continue; 354 } 355 356 long[] rss = mProcessDependencies.getRss(scoredProcessRecord.proc.getPid()); 357 if (rss == null || rss.length == 0) { 358 Slog.e( 359 OomAdjuster.TAG, 360 "Process.getRss returned bad value, not re-ranking: " 361 + Arrays.toString(rss)); 362 return; 363 } 364 // First element is total RSS: 365 // frameworks/base/core/jni/android_util_Process.cpp:1192 366 scoredProcessRecord.proc.mState.setCacheOomRankerRss(rss[0], nowMs); 367 scoredProcessRecord.proc.mProfile.setLastRss(rss[0]); 368 } 369 } 370 371 // Add scores for each of the weighted features we want to rank based on. 372 if (lruWeight > 0.0f) { 373 // This doesn't use the LRU list ordering as after the first re-ranking 374 // that will no longer be lru. 375 Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, 376 LAST_ACTIVITY_TIME_COMPARATOR); 377 addToScore(scoredProcessRecords, lruWeight); 378 } 379 if (rssWeight > 0.0f) { 380 if (useFrequentRss) { 381 Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, RSS_COMPARATOR); 382 } else { 383 synchronized (mService.mAppProfiler.mProfilerLock) { 384 Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR); 385 } 386 } 387 addToScore(scoredProcessRecords, rssWeight); 388 } 389 if (usesWeight > 0.0f) { 390 Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, CACHE_USE_COMPARATOR); 391 addToScore(scoredProcessRecords, usesWeight); 392 } 393 394 // Re-rank by the new combined score. 395 Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, 396 SCORED_PROCESS_RECORD_COMPARATOR); 397 398 if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) { 399 boolean printedHeader = false; 400 for (int i = 0; i < numProcessesReRanked; ++i) { 401 if (scoredProcessRecords[i].proc.getPid() 402 != lruList.get(lruPositions[i]).getPid()) { 403 if (!printedHeader) { 404 Slog.i(OomAdjuster.TAG, "reRankLruCachedApps"); 405 printedHeader = true; 406 } 407 Slog.i(OomAdjuster.TAG, " newPos=" + lruPositions[i] + " " 408 + scoredProcessRecords[i].proc); 409 } 410 } 411 } 412 413 for (int i = 0; i < numProcessesReRanked; ++i) { 414 lruList.set(lruPositions[i], scoredProcessRecords[i].proc); 415 scoredProcessRecords[i].proc = null; 416 } 417 } 418 appCanBeReRanked(ProcessRecord process)419 private static boolean appCanBeReRanked(ProcessRecord process) { 420 return !process.isKilledByAm() 421 && process.getThread() != null 422 && process.mState.getCurAdj() >= ProcessList.UNKNOWN_ADJ; 423 } 424 addToScore(RankedProcessRecord[] scores, float weight)425 private static void addToScore(RankedProcessRecord[] scores, float weight) { 426 for (int i = 1; i < scores.length; ++i) { 427 scores[i].score += i * weight; 428 } 429 } 430 dump(PrintWriter pw)431 void dump(PrintWriter pw) { 432 pw.println("CacheOomRanker settings"); 433 synchronized (mPhenotypeFlagLock) { 434 pw.println(" " + KEY_USE_OOM_RE_RANKING + "=" + mUseOomReRanking); 435 pw.println(" " + KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK + "=" + getNumberToReRank()); 436 pw.println(" " + KEY_OOM_RE_RANKING_LRU_WEIGHT + "=" + mLruWeight); 437 pw.println(" " + KEY_OOM_RE_RANKING_USES_WEIGHT + "=" + mUsesWeight); 438 pw.println(" " + KEY_OOM_RE_RANKING_RSS_WEIGHT + "=" + mRssWeight); 439 } 440 } 441 442 private static class ScoreComparator implements Comparator<RankedProcessRecord> { 443 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)444 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 445 return Float.compare(o1.score, o2.score); 446 } 447 } 448 449 private static class LastActivityTimeComparator implements Comparator<RankedProcessRecord> { 450 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)451 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 452 return Long.compare(o1.proc.getLastActivityTime(), o2.proc.getLastActivityTime()); 453 } 454 } 455 456 private static class CacheUseComparator implements Comparator<RankedProcessRecord> { 457 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)458 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 459 return Long.compare(o1.proc.mState.getCacheOomRankerUseCount(), 460 o2.proc.mState.getCacheOomRankerUseCount()); 461 } 462 } 463 464 private static class RssComparator implements Comparator<RankedProcessRecord> { 465 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)466 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 467 // High RSS first to match least recently used. 468 return Long.compare( 469 o2.proc.mState.getCacheOomRankerRss(), 470 o1.proc.mState.getCacheOomRankerRss()); 471 } 472 } 473 474 private static class LastRssComparator implements Comparator<RankedProcessRecord> { 475 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)476 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 477 // High RSS first to match least recently used. 478 return Long.compare(o2.proc.mProfile.getLastRss(), o1.proc.mProfile.getLastRss()); 479 } 480 } 481 482 private static class RankedProcessRecord { 483 public ProcessRecord proc; 484 public float score; 485 } 486 487 /** 488 * Interface for mocking {@link Process} static methods. 489 */ 490 interface ProcessDependencies { getRss(int pid)491 long[] getRss(int pid); 492 } 493 494 private static class ProcessDependenciesImpl implements ProcessDependencies { 495 @Override getRss(int pid)496 public long[] getRss(int pid) { 497 return Process.getRss(pid); 498 } 499 } 500 } 501