blob: d372108e0a471bd7d4f9267f0fe43252ff104382 [file] [log] [blame]
/*
* Copyright (C) 2023 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 static android.app.ActivityManager.PROCESS_STATE_BACKUP;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_HOME;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
import static com.android.server.am.ProcessList.HOME_APP_ADJ;
import static com.android.server.am.ProcessList.NATIVE_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
import static com.android.server.am.ProcessList.SYSTEM_ADJ;
import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.content.pm.ServiceInfo;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceThread;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
/**
* A modern implementation of the oom adjuster.
*/
public class OomAdjusterModernImpl extends OomAdjuster {
static final String TAG = "OomAdjusterModernImpl";
// The ADJ_SLOT_INVALID is NOT an actual slot.
static final int ADJ_SLOT_INVALID = -1;
static final int ADJ_SLOT_NATIVE = 0;
static final int ADJ_SLOT_SYSTEM = 1;
static final int ADJ_SLOT_PERSISTENT_PROC = 2;
static final int ADJ_SLOT_PERSISTENT_SERVICE = 3;
static final int ADJ_SLOT_FOREGROUND_APP = 4;
static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5;
static final int ADJ_SLOT_VISIBLE_APP = 6;
static final int ADJ_SLOT_PERCEPTIBLE_APP = 7;
static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8;
static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9;
static final int ADJ_SLOT_BACKUP_APP = 10;
static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11;
static final int ADJ_SLOT_SERVICE = 12;
static final int ADJ_SLOT_HOME_APP = 13;
static final int ADJ_SLOT_PREVIOUS_APP = 14;
static final int ADJ_SLOT_SERVICE_B = 15;
static final int ADJ_SLOT_CACHED_APP = 16;
static final int ADJ_SLOT_UNKNOWN = 17;
@IntDef(prefix = { "ADJ_SLOT_" }, value = {
ADJ_SLOT_INVALID,
ADJ_SLOT_NATIVE,
ADJ_SLOT_SYSTEM,
ADJ_SLOT_PERSISTENT_PROC,
ADJ_SLOT_PERSISTENT_SERVICE,
ADJ_SLOT_FOREGROUND_APP,
ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP,
ADJ_SLOT_VISIBLE_APP,
ADJ_SLOT_PERCEPTIBLE_APP,
ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP,
ADJ_SLOT_PERCEPTIBLE_LOW_APP,
ADJ_SLOT_BACKUP_APP,
ADJ_SLOT_HEAVY_WEIGHT_APP,
ADJ_SLOT_SERVICE,
ADJ_SLOT_HOME_APP,
ADJ_SLOT_PREVIOUS_APP,
ADJ_SLOT_SERVICE_B,
ADJ_SLOT_CACHED_APP,
ADJ_SLOT_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
@interface AdjSlot{}
static final int[] ADJ_SLOT_VALUES = new int[] {
NATIVE_ADJ,
SYSTEM_ADJ,
PERSISTENT_PROC_ADJ,
PERSISTENT_SERVICE_ADJ,
FOREGROUND_APP_ADJ,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ,
VISIBLE_APP_ADJ,
PERCEPTIBLE_APP_ADJ,
PERCEPTIBLE_MEDIUM_APP_ADJ,
PERCEPTIBLE_LOW_APP_ADJ,
BACKUP_APP_ADJ,
HEAVY_WEIGHT_APP_ADJ,
SERVICE_ADJ,
HOME_APP_ADJ,
PREVIOUS_APP_ADJ,
SERVICE_B_ADJ,
CACHED_APP_MIN_ADJ,
UNKNOWN_ADJ,
};
/**
* Note: Always use the raw adj to call this API.
*/
static @AdjSlot int adjToSlot(int adj) {
if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) {
// Conduct a binary search, in most of the cases it'll get a hit.
final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj);
if (index >= 0) {
return index;
}
// If not found, the returned index above should be (-(insertion point) - 1),
// let's return the first slot that's less than the adj value.
return -(index + 1) - 1;
}
return ADJ_SLOT_VALUES.length - 1;
}
static final int[] PROC_STATE_SLOTS = new int[] {
PROCESS_STATE_PERSISTENT, // 0
PROCESS_STATE_PERSISTENT_UI,
PROCESS_STATE_TOP,
PROCESS_STATE_BOUND_TOP,
PROCESS_STATE_FOREGROUND_SERVICE,
PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
PROCESS_STATE_IMPORTANT_FOREGROUND,
PROCESS_STATE_IMPORTANT_BACKGROUND,
PROCESS_STATE_TRANSIENT_BACKGROUND,
PROCESS_STATE_BACKUP,
PROCESS_STATE_SERVICE,
PROCESS_STATE_RECEIVER,
PROCESS_STATE_TOP_SLEEPING,
PROCESS_STATE_HEAVY_WEIGHT,
PROCESS_STATE_HOME,
PROCESS_STATE_LAST_ACTIVITY,
PROCESS_STATE_CACHED_ACTIVITY,
PROCESS_STATE_CACHED_ACTIVITY_CLIENT,
PROCESS_STATE_CACHED_RECENT,
PROCESS_STATE_CACHED_EMPTY,
PROCESS_STATE_UNKNOWN, // -1
};
static int processStateToSlot(@ActivityManager.ProcessState int state) {
if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) {
return state;
}
return PROC_STATE_SLOTS.length - 1;
}
/**
* A container node in the {@link LinkedProcessRecordList},
* holding the references to {@link ProcessRecord}.
*/
static class ProcessRecordNode {
static final int NODE_TYPE_PROC_STATE = 0;
static final int NODE_TYPE_ADJ = 1;
@IntDef(prefix = { "NODE_TYPE_" }, value = {
NODE_TYPE_PROC_STATE,
NODE_TYPE_ADJ,
})
@Retention(RetentionPolicy.SOURCE)
@interface NodeType {}
static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1;
@Nullable ProcessRecordNode mPrev;
@Nullable ProcessRecordNode mNext;
final @Nullable ProcessRecord mApp;
ProcessRecordNode(@Nullable ProcessRecord app) {
mApp = app;
}
void unlink() {
if (mPrev != null) {
mPrev.mNext = mNext;
}
if (mNext != null) {
mNext.mPrev = mPrev;
}
mPrev = mNext = null;
}
boolean isLinked() {
return mPrev != null && mNext != null;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("ProcessRecordNode{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
sb.append(mApp);
sb.append(' ');
sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN);
sb.append(' ');
sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ);
sb.append(' ');
sb.append(Integer.toHexString(System.identityHashCode(mPrev)));
sb.append(' ');
sb.append(Integer.toHexString(System.identityHashCode(mNext)));
sb.append('}');
return sb.toString();
}
}
private class ProcessRecordNodes {
private final @ProcessRecordNode.NodeType int mType;
private final LinkedProcessRecordList[] mProcessRecordNodes;
// The last node besides the tail.
private final ProcessRecordNode[] mLastNode;
ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) {
mType = type;
mProcessRecordNodes = new LinkedProcessRecordList[size];
for (int i = 0; i < size; i++) {
mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
}
mLastNode = new ProcessRecordNode[size];
}
int size() {
return mProcessRecordNodes.length;
}
@VisibleForTesting
void reset() {
for (int i = 0; i < mProcessRecordNodes.length; i++) {
mProcessRecordNodes[i].reset();
mLastNode[i] = null;
}
}
void resetLastNodes() {
for (int i = 0; i < mProcessRecordNodes.length; i++) {
mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail();
}
}
void setLastNodeToHead(int slot) {
mLastNode[slot] = mProcessRecordNodes[slot].HEAD;
}
void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
ProcessRecordNode node = mLastNode[slot].mNext;
final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
while (node != tail) {
mTmpOomAdjusterArgs.mApp = node.mApp;
// Save the next before calling callback, since that may change the node.mNext.
final ProcessRecordNode next = node.mNext;
callback.accept(mTmpOomAdjusterArgs);
// There are couple of cases:
// a) The current node is moved to another slot
// - for this case, we'd need to keep using the "next" node.
// b) There are one or more new nodes being appended to this slot
// - for this case, we'd need to make sure we scan the new node too.
// Based on the assumption that case a) is only possible with
// the computeInitialOomAdjLSP(), where the movings are for single node only,
// we may safely assume that, if the "next" used to be the "tail" here, and it's
// now a new tail somewhere else, that's case a); otherwise, it's case b);
node = next == tail && node.mNext != null && node.mNext.mNext != null
? node.mNext : next;
}
}
int getNumberOfSlots() {
return mProcessRecordNodes.length;
}
void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) {
final ProcessRecordNode node = app.mLinkedNodes[mType];
if (prevSlot != ADJ_SLOT_INVALID) {
if (mLastNode[prevSlot] == node) {
mLastNode[prevSlot] = node.mPrev;
}
node.unlink();
}
mProcessRecordNodes[newSlot].append(node);
}
void moveAllNodesTo(int fromSlot, int toSlot) {
final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot];
final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot];
if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) {
fromList.moveTo(toList);
mLastNode[fromSlot] = fromList.getLastNodeBeforeTail();
}
}
void moveAppToTail(ProcessRecord app) {
final ProcessRecordNode node = app.mLinkedNodes[mType];
int slot;
switch (mType) {
case ProcessRecordNode.NODE_TYPE_PROC_STATE:
slot = processStateToSlot(app.mState.getCurProcState());
if (mLastNode[slot] == node) {
mLastNode[slot] = node.mPrev;
}
mProcessRecordNodes[slot].moveNodeToTail(node);
break;
case ProcessRecordNode.NODE_TYPE_ADJ:
slot = adjToSlot(app.mState.getCurRawAdj());
if (mLastNode[slot] == node) {
mLastNode[slot] = node.mPrev;
}
mProcessRecordNodes[slot].moveNodeToTail(node);
break;
default:
return;
}
}
void reset(int slot) {
mProcessRecordNodes[slot].reset();
}
void unlink(@NonNull ProcessRecord app) {
final ProcessRecordNode node = app.mLinkedNodes[mType];
final int slot = getCurrentSlot(app);
if (slot != ADJ_SLOT_INVALID) {
if (mLastNode[slot] == node) {
mLastNode[slot] = node.mPrev;
}
}
node.unlink();
}
void append(@NonNull ProcessRecord app) {
append(app, getCurrentSlot(app));
}
void append(@NonNull ProcessRecord app, int targetSlot) {
final ProcessRecordNode node = app.mLinkedNodes[mType];
mProcessRecordNodes[targetSlot].append(node);
}
private int getCurrentSlot(@NonNull ProcessRecord app) {
switch (mType) {
case ProcessRecordNode.NODE_TYPE_PROC_STATE:
return processStateToSlot(app.mState.getCurProcState());
case ProcessRecordNode.NODE_TYPE_ADJ:
return adjToSlot(app.mState.getCurRawAdj());
}
return ADJ_SLOT_INVALID;
}
String toString(int slot, int logUid) {
return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid);
}
/**
* A simple version of {@link java.util.LinkedList}, as here we don't allocate new node
* while adding an object to it.
*/
private static class LinkedProcessRecordList {
// Sentinel head/tail, to make bookkeeping work easier.
final ProcessRecordNode HEAD = new ProcessRecordNode(null);
final ProcessRecordNode TAIL = new ProcessRecordNode(null);
final @ProcessRecordNode.NodeType int mNodeType;
LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) {
HEAD.mNext = TAIL;
TAIL.mPrev = HEAD;
mNodeType = nodeType;
}
void append(@NonNull ProcessRecordNode node) {
node.mNext = TAIL;
node.mPrev = TAIL.mPrev;
TAIL.mPrev.mNext = node;
TAIL.mPrev = node;
}
void moveTo(@NonNull LinkedProcessRecordList toList) {
if (HEAD.mNext != TAIL) {
toList.TAIL.mPrev.mNext = HEAD.mNext;
HEAD.mNext.mPrev = toList.TAIL.mPrev;
toList.TAIL.mPrev = TAIL.mPrev;
TAIL.mPrev.mNext = toList.TAIL;
HEAD.mNext = TAIL;
TAIL.mPrev = HEAD;
}
}
void moveNodeToTail(@NonNull ProcessRecordNode node) {
node.unlink();
append(node);
}
@NonNull ProcessRecordNode getLastNodeBeforeTail() {
return TAIL.mPrev;
}
@VisibleForTesting
void reset() {
HEAD.mNext = TAIL;
TAIL.mPrev = HEAD;
}
String toString(int logUid) {
final StringBuilder sb = new StringBuilder();
sb.append("LinkedProcessRecordList{");
sb.append(HEAD);
sb.append(' ');
sb.append(TAIL);
sb.append('[');
ProcessRecordNode node = HEAD.mNext;
while (node != TAIL) {
if (node.mApp != null && node.mApp.uid == logUid) {
sb.append(node);
sb.append(',');
}
node = node.mNext;
}
sb.append(']');
sb.append('}');
return sb.toString();
}
}
}
/**
* A data class for holding the parameters in computing oom adj.
*/
private class OomAdjusterArgs {
ProcessRecord mApp;
ProcessRecord mTopApp;
long mNow;
int mCachedAdj;
@OomAdjReason int mOomAdjReason;
@NonNull ActiveUids mUids;
boolean mFullUpdate;
void update(ProcessRecord topApp, long now, int cachedAdj,
@OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) {
mTopApp = topApp;
mNow = now;
mCachedAdj = cachedAdj;
mOomAdjReason = oomAdjReason;
mUids = uids;
mFullUpdate = fullUpdate;
}
}
/**
* A helper consumer for collecting processes that have not been reached yet. To avoid object
* allocations every OomAdjuster update, the results will be stored in
* {@link UnreachedProcessCollector#processList}. The process list reader is responsible
* for setting it before usage, as well as, clearing the reachable state of each process in the
* list.
*/
private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
public ArrayList<ProcessRecord> processList = null;
@Override
public void accept(ProcessRecord process) {
if (process.mState.isReachable()) {
return;
}
process.mState.setReachable(true);
processList.add(process);
}
}
private final UnreachedProcessCollector mUnreachedProcessCollector =
new UnreachedProcessCollector();
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
}
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids, ServiceThread adjusterThread) {
super(service, processList, activeUids, adjusterThread);
}
private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length);
private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes(
ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length);
private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs();
void linkProcessRecordToList(@NonNull ProcessRecord app) {
mProcessRecordProcStateNodes.append(app);
mProcessRecordAdjNodes.append(app);
}
void unlinkProcessRecordFromList(@NonNull ProcessRecord app) {
mProcessRecordProcStateNodes.unlink(app);
mProcessRecordAdjNodes.unlink(app);
}
@Override
@VisibleForTesting
void resetInternal() {
mProcessRecordProcStateNodes.reset();
mProcessRecordAdjNodes.reset();
}
@GuardedBy("mService")
@Override
void onProcessBeginLocked(@NonNull ProcessRecord app) {
// Check one type should be good enough.
if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) {
for (int i = 0; i < app.mLinkedNodes.length; i++) {
app.mLinkedNodes[i] = new ProcessRecordNode(app);
}
}
if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
linkProcessRecordToList(app);
}
}
@GuardedBy("mService")
@Override
void onProcessEndLocked(@NonNull ProcessRecord app) {
if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null
&& app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
unlinkProcessRecordFromList(app);
}
}
@GuardedBy("mService")
@Override
void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
updateProcStateSlotIfNecessary(app, prevProcState);
}
@GuardedBy("mService")
void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
updateAdjSlotIfNecessary(app, prevAdj);
}
@GuardedBy("mService")
@Override
protected int getInitialAdj(@NonNull ProcessRecord app) {
return UNKNOWN_ADJ;
}
@GuardedBy("mService")
@Override
protected int getInitialProcState(@NonNull ProcessRecord app) {
return PROCESS_STATE_UNKNOWN;
}
@GuardedBy("mService")
@Override
protected int getInitialCapability(@NonNull ProcessRecord app) {
return 0;
}
@GuardedBy("mService")
@Override
protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
return false;
}
private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) {
if (app.mState.getCurRawAdj() != prevRawAdj) {
final int slot = adjToSlot(app.mState.getCurRawAdj());
final int prevSlot = adjToSlot(prevRawAdj);
if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
}
}
}
private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
if (app.mState.getCurProcState() != prevProcState) {
final int slot = processStateToSlot(app.mState.getCurProcState());
final int prevSlot = processStateToSlot(prevProcState);
if (slot != prevSlot) {
mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
}
}
}
@Override
protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
mAdjSeq++;
final ProcessStateRecord state = app.mState;
final int oldAdj = state.getCurRawAdj();
final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
? oldAdj : UNKNOWN_ADJ;
final ActiveUids uids = mTmpUidRecords;
final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
uids.clear();
targetProcesses.clear();
targetProcesses.add(app);
reachableProcesses.clear();
// Find out all reachable processes from this app.
collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
// Copy all of the reachable processes into the target process set.
targetProcesses.addAll(reachableProcesses);
reachableProcesses.clear();
final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
topApp, targetProcesses, uids, false, now, cachedAdj);
reachableProcesses.addAll(targetProcesses);
assignCachedAdjIfNecessary(reachableProcesses);
for (int i = uids.size() - 1; i >= 0; i--) {
final UidRecord uidRec = uids.valueAt(i);
uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
}
updateUidsLSP(uids, nowElapsed);
for (int i = 0, size = targetProcesses.size(); i < size; i++) {
applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
}
targetProcesses.clear();
reachableProcesses.clear();
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return result;
}
@GuardedBy({"mService", "mProcLock"})
@Override
protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
boolean startProfiling) {
final boolean fullUpdate = processes == null;
final ArrayList<ProcessRecord> activeProcesses = fullUpdate
? mProcessList.getLruProcessesLOSP() : processes;
ActiveUids activeUids = uids;
if (activeUids == null) {
final int numUids = mActiveUids.size();
activeUids = mTmpUidRecords;
activeUids.clear();
for (int i = 0; i < numUids; i++) {
UidRecord uidRec = mActiveUids.valueAt(i);
activeUids.put(uidRec.getUid(), uidRec);
}
}
if (startProfiling) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
}
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
final int numProc = activeProcesses.size();
mAdjSeq++;
if (fullUpdate) {
mNewNumServiceProcs = 0;
mNewNumAServiceProcs = 0;
}
final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
targetProcesses.clear();
if (!fullUpdate) {
targetProcesses.addAll(activeProcesses);
}
performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
fullUpdate, now, UNKNOWN_ADJ);
if (fullUpdate) {
assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
} else {
activeProcesses.clear();
activeProcesses.addAll(targetProcesses);
assignCachedAdjIfNecessary(activeProcesses);
for (int i = activeUids.size() - 1; i >= 0; i--) {
final UidRecord uidRec = activeUids.valueAt(i);
uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
}
updateUidsLSP(activeUids, nowElapsed);
for (int i = 0, size = targetProcesses.size(); i < size; i++) {
applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
}
activeProcesses.clear();
}
targetProcesses.clear();
if (startProfiling) {
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
return;
}
/**
* Perform the oom adj update on the given {@code targetProcesses}.
*
* <p>Note: The expectation to the given {@code targetProcesses} is, the caller
* must have called {@link collectReachableProcessesLocked} on it.
*/
private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
boolean fullUpdate, long now, int cachedAdj) {
final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
clientProcesses.clear();
// We'll need to collect the upstream processes of the target apps here, because those
// processes would potentially impact the procstate/adj via bindings.
if (!fullUpdate) {
collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
for (int i = 0, size = targetProcesses.size(); i < size; i++) {
final ProcessRecord app = targetProcesses.valueAt(i);
app.mState.resetCachedInfo();
final UidRecord uidRec = app.getUidRecord();
if (uidRec != null) {
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
}
uidRec.reset();
}
}
} else {
final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
for (int i = 0, size = lru.size(); i < size; i++) {
final ProcessRecord app = lru.get(i);
app.mState.resetCachedInfo();
final UidRecord uidRec = app.getUidRecord();
if (uidRec != null) {
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
}
uidRec.reset();
}
}
}
updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
cachedAdj, now, fullUpdate);
clientProcesses.clear();
return true;
}
/**
* Collect the client processes from the given {@code apps}, the result will be returned in the
* given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
* {@code apps}.
*/
@GuardedBy("mService")
private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
ArrayList<ProcessRecord> clientProcesses) {
// Mark all of the provided apps as reachable to avoid including them in the client list.
final int appsSize = apps.size();
for (int i = 0; i < appsSize; i++) {
final ProcessRecord app = apps.valueAt(i);
app.mState.setReachable(true);
}
clientProcesses.clear();
mUnreachedProcessCollector.processList = clientProcesses;
for (int i = 0; i < appsSize; i++) {
final ProcessRecord app = apps.valueAt(i);
app.forEachClient(mUnreachedProcessCollector);
}
mUnreachedProcessCollector.processList = null;
// Reset the temporary bits.
for (int i = clientProcesses.size() - 1; i >= 0; i--) {
clientProcesses.get(i).mState.setReachable(false);
}
for (int i = 0, size = apps.size(); i < size; i++) {
final ProcessRecord app = apps.valueAt(i);
app.mState.setReachable(false);
}
}
@GuardedBy({"mService", "mProcLock"})
private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
mProcessRecordProcStateNodes.resetLastNodes();
mProcessRecordAdjNodes.resetLastNodes();
final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
final int adjTarget = mProcessRecordAdjNodes.size() - 1;
mAdjSeq++;
// All apps to be updated will be moved to the lowest slot.
if (fullUpdate) {
// Move all the process record node to the lowest slot, we'll do recomputation on all of
// them. Use the processes from the lru list, because the scanning order matters here.
final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
for (int i = procStateTarget; i >= 0; i--) {
mProcessRecordProcStateNodes.reset(i);
// Force the last node to the head since we'll recompute all of them.
mProcessRecordProcStateNodes.setLastNodeToHead(i);
}
// enqueue the targets in the reverse order of the lru list.
for (int i = lruList.size() - 1; i >= 0; i--) {
mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
}
// Do the same to the adj nodes.
for (int i = adjTarget; i >= 0; i--) {
mProcessRecordAdjNodes.reset(i);
// Force the last node to the head since we'll recompute all of them.
mProcessRecordAdjNodes.setLastNodeToHead(i);
}
for (int i = lruList.size() - 1; i >= 0; i--) {
mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
}
} else {
// Move the target processes to the lowest slot.
for (int i = 0, size = targetProcesses.size(); i < size; i++) {
final ProcessRecord app = targetProcesses.valueAt(i);
final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
}
// Move the "lastNode" to head to make sure we scan all nodes in this slot.
mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
}
// All apps to be updated have been moved to the lowest slot.
// Do an initial pass of the computation.
mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
this::computeInitialOomAdjLSP);
if (!fullUpdate) {
// We didn't update the client processes with the computeInitialOomAdjLSP
// because they don't need to do so. But they'll be playing vital roles in
// computing the bindings. So include them into the scan list below.
for (int i = 0, size = clientProcesses.size(); i < size; i++) {
mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
}
// We don't update the adj list since we're resetting it below.
}
// Now nodes are set into their slots, without factoring in the bindings.
// The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
//
// The whole rationale here is that, the bindings from client to host app, won't elevate
// the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
// is a special case here, but client app's raw adj is still no less than the host app's).
// Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
// check its bindings, elevate its host app's slot if necessary.
//
// We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
// Because the procstate and adj are not always in sync - there are cases where
// the processes with lower proc state could be getting a higher oom adj score.
// And because of this, the procstate and adj node lists are basically two priority heaps.
//
// As the 2nd pass with the adj node lists potentially includes a significant amount of
// duplicated scans as the 1st pass has done, we'll reset the last node pointers for
// the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
// gets bumped, we'll only scan those in 2nd pass.
mProcessRecordAdjNodes.resetLastNodes();
// 1st pass, scan each slot in the procstate node list.
for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
}
// 2nd pass, scan each slot in the adj node list.
for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
}
}
@GuardedBy({"mService", "mProcLock"})
private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
final ProcessRecord app = args.mApp;
final int cachedAdj = args.mCachedAdj;
final ProcessRecord topApp = args.mTopApp;
final long now = args.mNow;
final int oomAdjReason = args.mOomAdjReason;
final ActiveUids uids = args.mUids;
final boolean fullUpdate = args.mFullUpdate;
if (DEBUG_OOM_ADJ) {
Slog.i(TAG, "OOM ADJ initial args app=" + app
+ " cachedAdj=" + cachedAdj
+ " topApp=" + topApp
+ " now=" + now
+ " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
+ " fullUpdate=" + fullUpdate);
}
if (uids != null) {
final UidRecord uidRec = app.getUidRecord();
if (uidRec != null) {
uids.put(uidRec.getUid(), uidRec);
}
}
computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
false);
}
/**
* @return The proposed change to the schedGroup.
*/
@GuardedBy({"mService", "mProcLock"})
@Override
protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
int schedGroup) {
schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
updateAdjSlotIfNecessary(app, prevRawAppAdj);
return schedGroup;
}
@GuardedBy({"mService", "mProcLock"})
@Override
protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
int prevProcState) {
super.setIntermediateProcStateLSP(app, procState, prevProcState);
updateProcStateSlotIfNecessary(app, prevProcState);
}
@GuardedBy({"mService", "mProcLock"})
private void computeHostOomAdjLSP(OomAdjusterArgs args) {
final ProcessRecord app = args.mApp;
final int cachedAdj = args.mCachedAdj;
final ProcessRecord topApp = args.mTopApp;
final long now = args.mNow;
final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
final boolean fullUpdate = args.mFullUpdate;
final ActiveUids uids = args.mUids;
final ProcessServiceRecord psr = app.mServices;
for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
ConnectionRecord cr = psr.getConnectionAt(i);
ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS)
? cr.binding.service.isolationHostProc : cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
&& service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
&& service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
&& service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
oomAdjReason, cachedAdj, false);
}
for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i);
final ProcessRecord service = cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
&& service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
&& service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
&& service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
oomAdjReason, cachedAdj, false);
}
final ProcessProviderRecord ppr = app.mProviders;
for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) {
ContentProviderConnection cpc = ppr.getProviderConnectionAt(i);
ProcessRecord provider = cpc.provider.proc;
if (provider == null || provider == app
|| (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
&& provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
&& provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
&& provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
oomAdjReason, cachedAdj, false);
}
}
}