All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.am.CacheOomRanker Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.am;

import android.os.Process;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.Executor;

/**
 * Class to re-rank a number of the least recently used processes before they
 * are assigned oom adjust scores.
 */
public class CacheOomRanker {
    @VisibleForTesting
    static final String KEY_USE_OOM_RE_RANKING = "use_oom_re_ranking";
    private static final boolean DEFAULT_USE_OOM_RE_RANKING = false;
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank";
    @VisibleForTesting
    static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS =
            "oom_re_ranking_preserve_top_n_apps";
    @VisibleForTesting
    static final int DEFAULT_PRESERVE_TOP_N_APPS = 3;
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_USE_FREQUENT_RSS = "oom_re_ranking_rss_use_frequent_rss";
    @VisibleForTesting
    static final boolean DEFAULT_USE_FREQUENT_RSS = true;
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS = "oom_re_ranking_rss_update_rate_ms";
    @VisibleForTesting
    static final long DEFAULT_RSS_UPDATE_RATE_MS = 10_000; // 10 seconds
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight";
    @VisibleForTesting
    static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight";
    @VisibleForTesting
    static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
    @VisibleForTesting
    static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight";
    @VisibleForTesting
    static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;

    private static final Comparator SCORED_PROCESS_RECORD_COMPARATOR =
            new ScoreComparator();
    private static final Comparator CACHE_USE_COMPARATOR =
            new CacheUseComparator();
    private static final Comparator RSS_COMPARATOR =
            new RssComparator();
    private static final Comparator LAST_RSS_COMPARATOR =
            new LastRssComparator();
    private static final Comparator LAST_ACTIVITY_TIME_COMPARATOR =
            new LastActivityTimeComparator();

    private final Object mPhenotypeFlagLock = new Object();

    private final ActivityManagerService mService;
    private final ProcessDependencies mProcessDependencies;
    private final ActivityManagerGlobalLock mProcLock;
    private final Object mProfilerLock;

    @GuardedBy("mPhenotypeFlagLock")
    private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    boolean mUseFrequentRss = DEFAULT_USE_FREQUENT_RSS;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    long mRssUpdateRateMs = DEFAULT_RSS_UPDATE_RATE_MS;
    // Weight to apply to the LRU ordering.
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
    // Weight to apply to the ordering by number of times the process has been added to the cache.
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
    // Weight to apply to the ordering by RSS used by the processes.
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;

    // Positions to replace in the lru list.
    @GuardedBy("mPhenotypeFlagLock")
    private int[] mLruPositions;
    // Processes to re-rank
    @GuardedBy("mPhenotypeFlagLock")
    private RankedProcessRecord[] mScoredProcessRecords;

    private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener =
            new DeviceConfig.OnPropertiesChangedListener() {
                @Override
                public void onPropertiesChanged(DeviceConfig.Properties properties) {
                    synchronized (mPhenotypeFlagLock) {
                        for (String name : properties.getKeyset()) {
                            if (KEY_USE_OOM_RE_RANKING.equals(name)) {
                                updateUseOomReranking();
                            } else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) {
                                updateNumberToReRank();
                            } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) {
                                updatePreserveTopNApps();
                            } else if (KEY_OOM_RE_RANKING_USE_FREQUENT_RSS.equals(name)) {
                                updateUseFrequentRss();
                            } else if (KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS.equals(name)) {
                                updateRssUpdateRateMs();
                            } else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) {
                                updateLruWeight();
                            } else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) {
                                updateUsesWeight();
                            } else if (KEY_OOM_RE_RANKING_RSS_WEIGHT.equals(name)) {
                                updateRssWeight();
                            }
                        }
                    }
                }
            };

    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. */
    public void init(Executor executor) {
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                executor, mOnFlagsChangedListener);
        synchronized (mPhenotypeFlagLock) {
            updateUseOomReranking();
            updateNumberToReRank();
            updateLruWeight();
            updateUsesWeight();
            updateRssWeight();
        }
    }

    /**
     * Returns whether oom re-ranking is enabled.
     */
    public boolean useOomReranking() {
        synchronized (mPhenotypeFlagLock) {
            return mUseOomReRanking;
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateUseOomReranking() {
        mUseOomReRanking = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_USE_OOM_RE_RANKING, DEFAULT_USE_OOM_RE_RANKING);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateNumberToReRank() {
        int previousNumberToReRank = getNumberToReRank();
        int numberToReRank = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK);
        if (previousNumberToReRank != numberToReRank) {
            mScoredProcessRecords = new RankedProcessRecord[numberToReRank];
            for (int i = 0; i < mScoredProcessRecords.length; ++i) {
                mScoredProcessRecords[i] = new RankedProcessRecord();
            }
            mLruPositions = new int[numberToReRank];
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting
    int getNumberToReRank() {
        return mScoredProcessRecords == null ? 0 : mScoredProcessRecords.length;
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updatePreserveTopNApps() {
        int preserveTopNApps = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, DEFAULT_PRESERVE_TOP_N_APPS);
        if (preserveTopNApps < 0) {
            Slog.w(OomAdjuster.TAG,
                    "Found negative value for preserveTopNApps, setting to default: "
                            + preserveTopNApps);
            preserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
        }
        mPreserveTopNApps = preserveTopNApps;
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateRssUpdateRateMs() {
        mRssUpdateRateMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, DEFAULT_RSS_UPDATE_RATE_MS);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateUseFrequentRss() {
        mUseFrequentRss = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, DEFAULT_USE_FREQUENT_RSS);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateLruWeight() {
        mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateUsesWeight() {
        mUsesWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_USES_WEIGHT, DEFAULT_OOM_RE_RANKING_USES_WEIGHT);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateRssWeight() {
        mRssWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_OOM_RE_RANKING_RSS_WEIGHT, DEFAULT_OOM_RE_RANKING_RSS_WEIGHT);
    }

    /**
     * Re-rank the cached processes in the lru list with a weighted ordering
     * of lru, rss size and number of times the process has been put in the cache.
     */
    @GuardedBy({"mService", "mProcLock"})
    void reRankLruCachedAppsLSP(ArrayList lruList, int lruProcessServiceStart) {
        // The lruList is a list of processes ordered by how recently they were used. The
        // least-recently-used apps are at the beginning of the list. We keep track of two
        // indices in the lruList:
        //
        // getNumberToReRank=5, preserveTopNApps=3, lruProcessServiceStart=7,
        // lruList=
        //   0: app A       ^
        //   1: app B       | These apps are re-ranked, as they are the first five apps (see
        //   2: app C       | getNumberToReRank), excluding...
        //   3: app D       v
        //   4: app E       ^
        //   5: app F       | The three most-recently-used apps in the cache (see preserveTopNApps).
        //   6: app G       v
        //   7: service A   ^
        //   8: service B   | Everything beyond lruProcessServiceStart is ignored, as these aren't
        //   9: service C   | apps.
        //  10: activity A  |
        //      ...         |
        //
        // `numProcessesEvaluated` moves across the apps (indices 0-6) or until we've found enough
        // apps to re-rank, and made sure none of them are in the top `preserveTopNApps` apps.
        // Re-ranked apps are copied into `scoredProcessRecords`, where the re-ranking calculation
        // happens.
        //
        // Note that some apps in the `lruList` can be skipped, if they don't pass
        //`appCanBeReRanked`.

        float lruWeight;
        float usesWeight;
        float rssWeight;
        int preserveTopNApps;
        boolean useFrequentRss;
        long rssUpdateRateMs;
        int[] lruPositions;
        RankedProcessRecord[] scoredProcessRecords;

        synchronized (mPhenotypeFlagLock) {
            lruWeight = mLruWeight;
            usesWeight = mUsesWeight;
            rssWeight = mRssWeight;
            preserveTopNApps = mPreserveTopNApps;
            useFrequentRss = mUseFrequentRss;
            rssUpdateRateMs = mRssUpdateRateMs;
            lruPositions = mLruPositions;
            scoredProcessRecords = mScoredProcessRecords;
        }

        // Don't re-rank if the class hasn't been initialized with defaults.
        if (lruPositions == null || scoredProcessRecords == null) {
            return;
        }

        int numProcessesEvaluated = 0;
        // Collect the least recently used processes to re-rank, only rank cached
        // processes further down the list than mLruProcessServiceStart.
        int numProcessesReRanked = 0;
        while (numProcessesEvaluated < lruProcessServiceStart
                && numProcessesReRanked < scoredProcessRecords.length) {
            ProcessRecord process = lruList.get(numProcessesEvaluated);
            // Processes that will be assigned a cached oom adj score.
            if (appCanBeReRanked(process)) {
                scoredProcessRecords[numProcessesReRanked].proc = process;
                scoredProcessRecords[numProcessesReRanked].score = 0.0f;
                lruPositions[numProcessesReRanked] = numProcessesEvaluated;
                ++numProcessesReRanked;
            }
            ++numProcessesEvaluated;
        }

        // Count how many apps we're not re-ranking (up to preserveTopNApps).
        int numProcessesNotReRanked = 0;
        while (numProcessesEvaluated < lruProcessServiceStart
                && numProcessesNotReRanked < preserveTopNApps) {
            ProcessRecord process = lruList.get(numProcessesEvaluated);
            if (appCanBeReRanked(process)) {
                numProcessesNotReRanked++;
            }
            numProcessesEvaluated++;
        }
        // Exclude the top `preserveTopNApps` apps from re-ranking.
        if (numProcessesNotReRanked < preserveTopNApps) {
            numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked;
            if (numProcessesReRanked < 0) {
                numProcessesReRanked = 0;
            }
        }

        if (useFrequentRss) {
            // Update RSS values for re-ranked apps.
            long nowMs = SystemClock.elapsedRealtime();
            for (int i = 0; i < numProcessesReRanked; ++i) {
                RankedProcessRecord scoredProcessRecord = scoredProcessRecords[i];
                long sinceUpdateMs =
                        nowMs - scoredProcessRecord.proc.mState.getCacheOomRankerRssTimeMs();
                if (scoredProcessRecord.proc.mState.getCacheOomRankerRss() != 0
                        && sinceUpdateMs < rssUpdateRateMs) {
                    continue;
                }

                long[] rss = mProcessDependencies.getRss(scoredProcessRecord.proc.getPid());
                if (rss == null || rss.length == 0) {
                    Slog.e(
                            OomAdjuster.TAG,
                            "Process.getRss returned bad value, not re-ranking: "
                                    + Arrays.toString(rss));
                    return;
                }
                // First element is total RSS:
                // frameworks/base/core/jni/android_util_Process.cpp:1192
                scoredProcessRecord.proc.mState.setCacheOomRankerRss(rss[0], nowMs);
                scoredProcessRecord.proc.mProfile.setLastRss(rss[0]);
            }
        }

        // Add scores for each of the weighted features we want to rank based on.
        if (lruWeight > 0.0f) {
            // This doesn't use the LRU list ordering as after the first re-ranking
            // that will no longer be lru.
            Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
                    LAST_ACTIVITY_TIME_COMPARATOR);
            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) {
            Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, CACHE_USE_COMPARATOR);
            addToScore(scoredProcessRecords, usesWeight);
        }

        // Re-rank by the new combined score.
        Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
                SCORED_PROCESS_RECORD_COMPARATOR);

        if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
            boolean printedHeader = false;
            for (int i = 0; i < numProcessesReRanked; ++i) {
                if (scoredProcessRecords[i].proc.getPid()
                        != lruList.get(lruPositions[i]).getPid()) {
                    if (!printedHeader) {
                        Slog.i(OomAdjuster.TAG, "reRankLruCachedApps");
                        printedHeader = true;
                    }
                    Slog.i(OomAdjuster.TAG, "  newPos=" + lruPositions[i] + " "
                            + scoredProcessRecords[i].proc);
                }
            }
        }

        for (int i = 0; i < numProcessesReRanked; ++i) {
            lruList.set(lruPositions[i], scoredProcessRecords[i].proc);
            scoredProcessRecords[i].proc = null;
        }
    }

    private static boolean appCanBeReRanked(ProcessRecord process) {
        return !process.isKilledByAm()
                && process.getThread() != null
                && process.mState.getCurAdj() >= ProcessList.UNKNOWN_ADJ;
    }

    private static void addToScore(RankedProcessRecord[] scores, float weight) {
        for (int i = 1; i < scores.length; ++i) {
            scores[i].score += i * weight;
        }
    }

    void dump(PrintWriter pw) {
        pw.println("CacheOomRanker settings");
        synchronized (mPhenotypeFlagLock) {
            pw.println("  " + KEY_USE_OOM_RE_RANKING + "=" + mUseOomReRanking);
            pw.println("  " + KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK + "=" + getNumberToReRank());
            pw.println("  " + KEY_OOM_RE_RANKING_LRU_WEIGHT + "=" + mLruWeight);
            pw.println("  " + KEY_OOM_RE_RANKING_USES_WEIGHT + "=" + mUsesWeight);
            pw.println("  " + KEY_OOM_RE_RANKING_RSS_WEIGHT + "=" + mRssWeight);
        }
    }

    private static class ScoreComparator implements Comparator {
        @Override
        public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
            return Float.compare(o1.score, o2.score);
        }
    }

    private static class LastActivityTimeComparator implements Comparator {
        @Override
        public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
            return Long.compare(o1.proc.getLastActivityTime(), o2.proc.getLastActivityTime());
        }
    }

    private static class CacheUseComparator implements Comparator {
        @Override
        public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
            return Long.compare(o1.proc.mState.getCacheOomRankerUseCount(),
                    o2.proc.mState.getCacheOomRankerUseCount());
        }
    }

    private static class RssComparator implements Comparator {
        @Override
        public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
            // High RSS first to match least recently used.
            return Long.compare(
                    o2.proc.mState.getCacheOomRankerRss(),
                    o1.proc.mState.getCacheOomRankerRss());
        }
    }

    private static class LastRssComparator implements Comparator {
        @Override
        public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
            // High RSS first to match least recently used.
            return Long.compare(o2.proc.mProfile.getLastRss(), o1.proc.mProfile.getLastRss());
        }
    }

    private static class RankedProcessRecord {
        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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy