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