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.provider.DeviceConfig; 20 import android.util.Slog; 21 22 import com.android.internal.annotations.GuardedBy; 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.io.PrintWriter; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Comparator; 29 import java.util.concurrent.Executor; 30 31 /** 32 * Class to re-rank a number of the least recently used processes before they 33 * are assigned oom adjust scores. 34 */ 35 public class CacheOomRanker { 36 @VisibleForTesting 37 static final String KEY_USE_OOM_RE_RANKING = "use_oom_re_ranking"; 38 private static final boolean DEFAULT_USE_OOM_RE_RANKING = false; 39 @VisibleForTesting 40 static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank"; 41 @VisibleForTesting static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8; 42 @VisibleForTesting 43 static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight"; 44 @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f; 45 @VisibleForTesting 46 static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight"; 47 @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f; 48 @VisibleForTesting 49 static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight"; 50 @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f; 51 52 private static final Comparator<RankedProcessRecord> SCORED_PROCESS_RECORD_COMPARATOR = 53 new ScoreComparator(); 54 private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR = 55 new CacheUseComparator(); 56 private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR = 57 new LastRssComparator(); 58 private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR = 59 new LastActivityTimeComparator(); 60 61 private final Object mPhenotypeFlagLock = new Object(); 62 63 private final ActivityManagerService mService; 64 private final ActivityManagerGlobalLock mProcLock; 65 private final Object mProfilerLock; 66 67 @GuardedBy("mPhenotypeFlagLock") 68 private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING; 69 // Weight to apply to the LRU ordering. 70 @GuardedBy("mPhenotypeFlagLock") 71 @VisibleForTesting float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT; 72 // Weight to apply to the ordering by number of times the process has been added to the cache. 73 @GuardedBy("mPhenotypeFlagLock") 74 @VisibleForTesting float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT; 75 // Weight to apply to the ordering by RSS used by the processes. 76 @GuardedBy("mPhenotypeFlagLock") 77 @VisibleForTesting float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT; 78 79 // Positions to replace in the lru list. 80 @GuardedBy("mPhenotypeFlagLock") 81 private int[] mLruPositions; 82 // Processes to re-rank 83 @GuardedBy("mPhenotypeFlagLock") 84 private RankedProcessRecord[] mScoredProcessRecords; 85 86 private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener = 87 new DeviceConfig.OnPropertiesChangedListener() { 88 @Override 89 public void onPropertiesChanged(DeviceConfig.Properties properties) { 90 synchronized (mPhenotypeFlagLock) { 91 for (String name : properties.getKeyset()) { 92 if (KEY_USE_OOM_RE_RANKING.equals(name)) { 93 updateUseOomReranking(); 94 } else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) { 95 updateNumberToReRank(); 96 } else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) { 97 updateLruWeight(); 98 } else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) { 99 updateUsesWeight(); 100 } else if (KEY_OOM_RE_RANKING_RSS_WEIGHT.equals(name)) { 101 updateRssWeight(); 102 } 103 } 104 } 105 } 106 }; 107 CacheOomRanker(final ActivityManagerService service)108 CacheOomRanker(final ActivityManagerService service) { 109 mService = service; 110 mProcLock = service.mProcLock; 111 mProfilerLock = service.mAppProfiler.mProfilerLock; 112 } 113 114 /** Load settings from device config and register a listener for changes. */ init(Executor executor)115 public void init(Executor executor) { 116 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 117 executor, mOnFlagsChangedListener); 118 synchronized (mPhenotypeFlagLock) { 119 updateUseOomReranking(); 120 updateNumberToReRank(); 121 updateLruWeight(); 122 updateUsesWeight(); 123 updateRssWeight(); 124 } 125 } 126 127 /** 128 * Returns whether oom re-ranking is enabled. 129 */ useOomReranking()130 public boolean useOomReranking() { 131 synchronized (mPhenotypeFlagLock) { 132 return mUseOomReRanking; 133 } 134 } 135 136 @GuardedBy("mPhenotypeFlagLock") updateUseOomReranking()137 private void updateUseOomReranking() { 138 mUseOomReRanking = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 139 KEY_USE_OOM_RE_RANKING, DEFAULT_USE_OOM_RE_RANKING); 140 } 141 142 @GuardedBy("mPhenotypeFlagLock") updateNumberToReRank()143 private void updateNumberToReRank() { 144 int previousNumberToReRank = getNumberToReRank(); 145 int numberToReRank = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 146 KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK); 147 if (previousNumberToReRank != numberToReRank) { 148 mScoredProcessRecords = new RankedProcessRecord[numberToReRank]; 149 for (int i = 0; i < mScoredProcessRecords.length; ++i) { 150 mScoredProcessRecords[i] = new RankedProcessRecord(); 151 } 152 mLruPositions = new int[numberToReRank]; 153 } 154 } 155 156 @GuardedBy("mPhenotypeFlagLock") 157 @VisibleForTesting getNumberToReRank()158 int getNumberToReRank() { 159 return mScoredProcessRecords == null ? 0 : mScoredProcessRecords.length; 160 } 161 162 @GuardedBy("mPhenotypeFlagLock") updateLruWeight()163 private void updateLruWeight() { 164 mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 165 KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT); 166 } 167 168 @GuardedBy("mPhenotypeFlagLock") updateUsesWeight()169 private void updateUsesWeight() { 170 mUsesWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 171 KEY_OOM_RE_RANKING_USES_WEIGHT, DEFAULT_OOM_RE_RANKING_USES_WEIGHT); 172 } 173 174 @GuardedBy("mPhenotypeFlagLock") updateRssWeight()175 private void updateRssWeight() { 176 mRssWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 177 KEY_OOM_RE_RANKING_RSS_WEIGHT, DEFAULT_OOM_RE_RANKING_RSS_WEIGHT); 178 } 179 180 /** 181 * Re-rank the cached processes in the lru list with a weighted ordering 182 * of lru, rss size and number of times the process has been put in the cache. 183 */ 184 @GuardedBy({"mService", "mProcLock"}) reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart)185 void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) { 186 float lruWeight; 187 float usesWeight; 188 float rssWeight; 189 int[] lruPositions; 190 RankedProcessRecord[] scoredProcessRecords; 191 192 synchronized (mPhenotypeFlagLock) { 193 lruWeight = mLruWeight; 194 usesWeight = mUsesWeight; 195 rssWeight = mRssWeight; 196 lruPositions = mLruPositions; 197 scoredProcessRecords = mScoredProcessRecords; 198 } 199 200 // Don't re-rank if the class hasn't been initialized with defaults. 201 if (lruPositions == null || scoredProcessRecords == null) { 202 return; 203 } 204 205 // Collect the least recently used processes to re-rank, only rank cached 206 // processes further down the list than mLruProcessServiceStart. 207 int cachedProcessPos = 0; 208 for (int i = 0; i < lruProcessServiceStart 209 && cachedProcessPos < scoredProcessRecords.length; ++i) { 210 ProcessRecord app = lruList.get(i); 211 // Processes that will be assigned a cached oom adj score. 212 if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj() 213 >= ProcessList.UNKNOWN_ADJ) { 214 scoredProcessRecords[cachedProcessPos].proc = app; 215 scoredProcessRecords[cachedProcessPos].score = 0.0f; 216 lruPositions[cachedProcessPos] = i; 217 ++cachedProcessPos; 218 } 219 } 220 221 // TODO maybe ensure a certain number above this in the cache before re-ranking. 222 if (cachedProcessPos < scoredProcessRecords.length) { 223 // Ignore we don't have enough processes to worry about re-ranking. 224 return; 225 } 226 227 // Add scores for each of the weighted features we want to rank based on. 228 if (lruWeight > 0.0f) { 229 // This doesn't use the LRU list ordering as after the first re-ranking 230 // that will no longer be lru. 231 Arrays.sort(scoredProcessRecords, LAST_ACTIVITY_TIME_COMPARATOR); 232 addToScore(scoredProcessRecords, lruWeight); 233 } 234 if (rssWeight > 0.0f) { 235 synchronized (mService.mAppProfiler.mProfilerLock) { 236 Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR); 237 } 238 addToScore(scoredProcessRecords, rssWeight); 239 } 240 if (usesWeight > 0.0f) { 241 Arrays.sort(scoredProcessRecords, CACHE_USE_COMPARATOR); 242 addToScore(scoredProcessRecords, usesWeight); 243 } 244 245 // Re-rank by the new combined score. 246 Arrays.sort(scoredProcessRecords, SCORED_PROCESS_RECORD_COMPARATOR); 247 248 if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) { 249 boolean printedHeader = false; 250 for (int i = 0; i < scoredProcessRecords.length; ++i) { 251 if (scoredProcessRecords[i].proc.getPid() 252 != lruList.get(lruPositions[i]).getPid()) { 253 if (!printedHeader) { 254 Slog.i(OomAdjuster.TAG, "reRankLruCachedApps"); 255 printedHeader = true; 256 } 257 Slog.i(OomAdjuster.TAG, " newPos=" + lruPositions[i] + " " 258 + scoredProcessRecords[i].proc); 259 } 260 } 261 } 262 263 for (int i = 0; i < scoredProcessRecords.length; ++i) { 264 lruList.set(lruPositions[i], scoredProcessRecords[i].proc); 265 scoredProcessRecords[i].proc = null; 266 } 267 } 268 addToScore(RankedProcessRecord[] scores, float weight)269 private static void addToScore(RankedProcessRecord[] scores, float weight) { 270 for (int i = 1; i < scores.length; ++i) { 271 scores[i].score += i * weight; 272 } 273 } 274 dump(PrintWriter pw)275 void dump(PrintWriter pw) { 276 pw.println("CacheOomRanker settings"); 277 synchronized (mPhenotypeFlagLock) { 278 pw.println(" " + KEY_USE_OOM_RE_RANKING + "=" + mUseOomReRanking); 279 pw.println(" " + KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK + "=" + getNumberToReRank()); 280 pw.println(" " + KEY_OOM_RE_RANKING_LRU_WEIGHT + "=" + mLruWeight); 281 pw.println(" " + KEY_OOM_RE_RANKING_USES_WEIGHT + "=" + mUsesWeight); 282 pw.println(" " + KEY_OOM_RE_RANKING_RSS_WEIGHT + "=" + mRssWeight); 283 } 284 } 285 286 private static class ScoreComparator implements Comparator<RankedProcessRecord> { 287 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)288 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 289 return Float.compare(o1.score, o2.score); 290 } 291 } 292 293 private static class LastActivityTimeComparator implements Comparator<RankedProcessRecord> { 294 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)295 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 296 return Long.compare(o1.proc.getLastActivityTime(), o2.proc.getLastActivityTime()); 297 } 298 } 299 300 private static class CacheUseComparator implements Comparator<RankedProcessRecord> { 301 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)302 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 303 return Long.compare(o1.proc.mState.getCacheOomRankerUseCount(), 304 o2.proc.mState.getCacheOomRankerUseCount()); 305 } 306 } 307 308 private static class LastRssComparator implements Comparator<RankedProcessRecord> { 309 @Override compare(RankedProcessRecord o1, RankedProcessRecord o2)310 public int compare(RankedProcessRecord o1, RankedProcessRecord o2) { 311 // High RSS first to match least recently used. 312 return Long.compare(o2.proc.mProfile.getLastRss(), o1.proc.mProfile.getLastRss()); 313 } 314 } 315 316 private static class RankedProcessRecord { 317 public ProcessRecord proc; 318 public float score; 319 } 320 } 321