Improve compaction debugging with extra adb command to force compaction
This CL adds am compact command that allows forcing compactions this is specially useful for debugging compaction flows as well and it also allows for instrumentation to manually trigger compactions if needed and it also reduces some technical debt to allow forcing compaction code to be manageable. Bug: 226463719 Test: adb shell am compact <process_name> <uid> some|full Change-Id: I67af76a212611260bb226e74e48eb7f495341f12
This commit is contained in:
@@ -219,6 +219,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
|
||||
return runStopService(pw);
|
||||
case "broadcast":
|
||||
return runSendBroadcast(pw);
|
||||
case "compact":
|
||||
return runCompact(pw);
|
||||
case "instrument":
|
||||
getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
|
||||
return -1;
|
||||
@@ -966,6 +968,36 @@ final class ActivityManagerShellCommand extends ShellCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NeverCompile
|
||||
int runCompact(PrintWriter pw) {
|
||||
String processName = getNextArgRequired();
|
||||
String uid = getNextArgRequired();
|
||||
String op = getNextArgRequired();
|
||||
ProcessRecord app;
|
||||
synchronized (mInternal.mProcLock) {
|
||||
app = mInternal.getProcessRecordLocked(processName, Integer.parseInt(uid));
|
||||
}
|
||||
pw.println("Process record found pid: " + app.mPid);
|
||||
if (op.equals("full")) {
|
||||
pw.println("Executing full compaction for " + app.mPid);
|
||||
synchronized (mInternal.mProcLock) {
|
||||
mInternal.mOomAdjuster.mCachedAppOptimizer.compactAppFull(app, true);
|
||||
}
|
||||
pw.println("Finished full compaction for " + app.mPid);
|
||||
} else if (op.equals("some")) {
|
||||
pw.println("Executing some compaction for " + app.mPid);
|
||||
synchronized (mInternal.mProcLock) {
|
||||
mInternal.mOomAdjuster.mCachedAppOptimizer.compactAppSome(app, true);
|
||||
}
|
||||
pw.println("Finished some compaction for " + app.mPid);
|
||||
} else {
|
||||
getErrPrintWriter().println("Error: unknown compact command '" + op + "'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int runDumpHeap(PrintWriter pw) throws RemoteException {
|
||||
final PrintWriter err = getErrPrintWriter();
|
||||
boolean managed = true;
|
||||
@@ -3435,6 +3467,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
|
||||
pw.println(" --allow-background-activity-starts: The receiver may start activities");
|
||||
pw.println(" even if in the background.");
|
||||
pw.println(" --async: Send without waiting for the completion of the receiver.");
|
||||
pw.println(" compact <process_name> <Package UID> [some|full]");
|
||||
pw.println(" Force process compaction.");
|
||||
pw.println(" some: execute file compaction.");
|
||||
pw.println(" full: execute anon + file compaction.");
|
||||
pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
|
||||
pw.println(" [--user <USER_ID> | current]");
|
||||
pw.println(" [--no-hidden-api-checks [--no-test-api-access]]");
|
||||
|
||||
@@ -88,6 +88,12 @@ public final class CachedAppOptimizer {
|
||||
@VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
|
||||
"freeze_debounce_timeout";
|
||||
|
||||
// RSS Indices
|
||||
private static final int RSS_TOTAL_INDEX = 0;
|
||||
private static final int RSS_FILE_INDEX = 1;
|
||||
private static final int RSS_ANON_INDEX = 2;
|
||||
private static final int RSS_SWAP_INDEX = 3;
|
||||
|
||||
// Phenotype sends int configurations and we map them to the strings we'll use on device,
|
||||
// preventing a weird string value entering the kernel.
|
||||
private static final int COMPACT_ACTION_NONE = 0;
|
||||
@@ -106,8 +112,8 @@ public final class CachedAppOptimizer {
|
||||
// Defaults for phenotype flags.
|
||||
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
|
||||
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
|
||||
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
|
||||
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL;
|
||||
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
|
||||
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
|
||||
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
|
||||
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
|
||||
@@ -447,6 +453,16 @@ public final class CachedAppOptimizer {
|
||||
pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime()
|
||||
+ ": " + app.getPid() + " " + app.processName);
|
||||
}
|
||||
|
||||
if (!mPendingCompactionProcesses.isEmpty()) {
|
||||
pw.println(" Pending compactions:");
|
||||
size = mPendingCompactionProcesses.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ProcessRecord app = mPendingCompactionProcesses.get(i);
|
||||
pw.println(" pid: " + app.getPid() + ". name: " + app.processName
|
||||
+ ". hasPendingCompact: " + app.mOptRecord.hasPendingCompact());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG_COMPACTION) {
|
||||
for (Map.Entry<Integer, LastCompactionStats> entry
|
||||
@@ -461,12 +477,16 @@ public final class CachedAppOptimizer {
|
||||
}
|
||||
|
||||
@GuardedBy("mProcLock")
|
||||
void compactAppSome(ProcessRecord app) {
|
||||
void compactAppSome(ProcessRecord app, boolean force) {
|
||||
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
|
||||
if (!app.mOptRecord.hasPendingCompact()) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, " compactAppSome requested for " + app.processName + " force: " + force);
|
||||
}
|
||||
if (force || !app.mOptRecord.hasPendingCompact()) {
|
||||
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
|
||||
"compactAppSome " + app.processName != null ? app.processName : "");
|
||||
app.mOptRecord.setHasPendingCompact(true);
|
||||
app.mOptRecord.setForceCompact(force);
|
||||
mPendingCompactionProcesses.add(app);
|
||||
mCompactionHandler.sendMessage(
|
||||
mCompactionHandler.obtainMessage(
|
||||
@@ -475,21 +495,31 @@ public final class CachedAppOptimizer {
|
||||
}
|
||||
|
||||
@GuardedBy("mProcLock")
|
||||
void compactAppFull(ProcessRecord app) {
|
||||
// Apply OOM adj score throttle for Full App Compaction.
|
||||
if ((app.mState.getSetAdj() < mCompactThrottleMinOomAdj
|
||||
|| app.mState.getSetAdj() > mCompactThrottleMaxOomAdj)
|
||||
void compactAppFull(ProcessRecord app, boolean force) {
|
||||
boolean oomAdjEnteredCached = (app.mState.getSetAdj() < mCompactThrottleMinOomAdj
|
||||
|| app.mState.getSetAdj() > mCompactThrottleMaxOomAdj)
|
||||
&& app.mState.getCurAdj() >= mCompactThrottleMinOomAdj
|
||||
&& app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) {
|
||||
&& app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj;
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
" compactAppFull requested for " + app.processName + " force: " + force
|
||||
+ " oomAdjEnteredCached: " + oomAdjEnteredCached);
|
||||
}
|
||||
// Apply OOM adj score throttle for Full App Compaction.
|
||||
if (force || oomAdjEnteredCached) {
|
||||
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
|
||||
if (!app.mOptRecord.hasPendingCompact()) {
|
||||
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
|
||||
"compactAppFull " + app.processName != null ? app.processName : "");
|
||||
app.mOptRecord.setHasPendingCompact(true);
|
||||
app.mOptRecord.setForceCompact(force);
|
||||
mPendingCompactionProcesses.add(app);
|
||||
mCompactionHandler.sendMessage(
|
||||
mCompactionHandler.obtainMessage(
|
||||
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
|
||||
COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
|
||||
} else if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
" compactAppFull Skipped for " + app.processName
|
||||
+ " since it has a pending compact");
|
||||
}
|
||||
} else {
|
||||
if (DEBUG_COMPACTION) {
|
||||
@@ -1136,13 +1166,48 @@ public final class CachedAppOptimizer {
|
||||
// Perform a major compaction when any app enters cached
|
||||
if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
|
||||
&& (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
|
||||
compactAppSome(app);
|
||||
compactAppSome(app, false);
|
||||
} else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
|
||||
&& newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
|
||||
compactAppFull(app);
|
||||
compactAppFull(app, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method resolves which compaction method we should use for the proposed compaction.
|
||||
*/
|
||||
int resolveCompactionAction(int pendingAction) {
|
||||
int resolvedAction;
|
||||
|
||||
switch (pendingAction) {
|
||||
case COMPACT_PROCESS_SOME:
|
||||
resolvedAction = COMPACT_ACTION_FILE;
|
||||
break;
|
||||
// For the time being, treat these as equivalent.
|
||||
case COMPACT_PROCESS_FULL:
|
||||
case COMPACT_PROCESS_PERSISTENT:
|
||||
case COMPACT_PROCESS_BFGS:
|
||||
resolvedAction = COMPACT_ACTION_FULL;
|
||||
break;
|
||||
default:
|
||||
resolvedAction = COMPACT_ACTION_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
// Downgrade compaction if facing swap memory pressure
|
||||
if (resolvedAction == COMPACT_ACTION_FULL) {
|
||||
double swapUsagePercent = getFreeSwapPercent();
|
||||
if (swapUsagePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
|
||||
Slog.d(TAG_AM,
|
||||
"Downgraded compaction to file only due to low swap."
|
||||
+ " Swap Free% " + swapUsagePercent);
|
||||
resolvedAction = COMPACT_ACTION_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedAction;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static final class LastCompactionStats {
|
||||
private final long[] mRssAfterCompaction;
|
||||
@@ -1161,6 +1226,167 @@ public final class CachedAppOptimizer {
|
||||
super(mCachedAppOptimizerThread.getLooper());
|
||||
}
|
||||
|
||||
private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc, int action) {
|
||||
final String name = proc.processName;
|
||||
if (mAm.mInternal.isPendingTopUid(proc.uid)) {
|
||||
// In case the OOM Adjust has not yet been propagated we see if this is
|
||||
// pending on becoming top app in which case we should not compact.
|
||||
Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
|
||||
return true;
|
||||
}
|
||||
|
||||
// don't compact if the process has returned to perceptible
|
||||
// and this is only a cached/home/prev compaction
|
||||
if ((action == COMPACT_ACTION_FILE || action == COMPACT_ACTION_FULL)
|
||||
&& (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping compaction as process " + name + " is "
|
||||
+ "now perceptible.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldTimeThrottleCompaction(
|
||||
ProcessRecord proc, long start, int pendingAction) {
|
||||
final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
|
||||
final String name = proc.processName;
|
||||
|
||||
int lastCompactAction = opt.getLastCompactAction();
|
||||
long lastCompactTime = opt.getLastCompactTime();
|
||||
|
||||
// basic throttling
|
||||
// use the Phenotype flag knobs to determine whether current/prevous
|
||||
// compaction combo should be throtted or not
|
||||
|
||||
// Note that we explicitly don't take mPhenotypeFlagLock here as the flags
|
||||
// should very seldom change, and taking the risk of using the wrong action is
|
||||
// preferable to taking the lock for every single compaction action.
|
||||
if (lastCompactTime != 0) {
|
||||
if (pendingAction == COMPACT_PROCESS_SOME) {
|
||||
if ((lastCompactAction == COMPACT_PROCESS_SOME
|
||||
&& (start - lastCompactTime < mCompactThrottleSomeSome))
|
||||
|| (lastCompactAction == COMPACT_PROCESS_FULL
|
||||
&& (start - lastCompactTime < mCompactThrottleSomeFull))) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping some compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottleSomeSome
|
||||
+ "/" + mCompactThrottleSomeFull
|
||||
+ " last=" + (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (pendingAction == COMPACT_PROCESS_FULL) {
|
||||
if ((lastCompactAction == COMPACT_PROCESS_SOME
|
||||
&& (start - lastCompactTime < mCompactThrottleFullSome))
|
||||
|| (lastCompactAction == COMPACT_PROCESS_FULL
|
||||
&& (start - lastCompactTime < mCompactThrottleFullFull))) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping full compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottleFullSome
|
||||
+ "/" + mCompactThrottleFullFull
|
||||
+ " last=" + (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
|
||||
if (start - lastCompactTime < mCompactThrottlePersistent) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping persistent compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottlePersistent
|
||||
+ " last=" + (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (pendingAction == COMPACT_PROCESS_BFGS) {
|
||||
if (start - lastCompactTime < mCompactThrottleBFGS) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping bfgs compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottleBFGS
|
||||
+ " last=" + (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldThrottleMiscCompaction(
|
||||
ProcessRecord proc, int procState, int action) {
|
||||
final String name = proc.processName;
|
||||
if (mProcStateThrottle.contains(procState)) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping full compaction for process " + name + "; proc state is "
|
||||
+ procState);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (COMPACT_ACTION_NONE == action) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping compaction for process " + name + "since action is None");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldRssThrottleCompaction(
|
||||
int action, int pid, String name, long[] rssBefore) {
|
||||
long anonRssBefore = rssBefore[RSS_ANON_INDEX];
|
||||
LastCompactionStats lastCompactionStats = mLastCompactionStats.get(pid);
|
||||
|
||||
if (rssBefore[RSS_TOTAL_INDEX] == 0 && rssBefore[RSS_FILE_INDEX] == 0
|
||||
&& rssBefore[RSS_ANON_INDEX] == 0 && rssBefore[RSS_SWAP_INDEX] == 0) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping compaction for"
|
||||
+ "process " + pid + " with no memory usage. Dead?");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == COMPACT_ACTION_FULL || action == COMPACT_ACTION_ANON) {
|
||||
if (mFullAnonRssThrottleKb > 0L && anonRssBefore < mFullAnonRssThrottleKb) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping full compaction for process " + name
|
||||
+ "; anon RSS is too small: " + anonRssBefore + "KB.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
|
||||
long[] lastRss = lastCompactionStats.getRssAfterCompaction();
|
||||
long absDelta = Math.abs(rssBefore[RSS_FILE_INDEX] - lastRss[RSS_FILE_INDEX])
|
||||
+ Math.abs(rssBefore[RSS_ANON_INDEX] - lastRss[RSS_ANON_INDEX])
|
||||
+ Math.abs(rssBefore[RSS_SWAP_INDEX] - lastRss[RSS_SWAP_INDEX]);
|
||||
if (absDelta <= mFullDeltaRssThrottleKb) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping full compaction for process " + name
|
||||
+ "; abs delta is too small: " + absDelta + "KB.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
@@ -1171,180 +1397,65 @@ public final class CachedAppOptimizer {
|
||||
int pid;
|
||||
String action;
|
||||
final String name;
|
||||
int pendingAction, lastCompactAction;
|
||||
int requestedAction, lastCompactAction;
|
||||
long lastCompactTime;
|
||||
LastCompactionStats lastCompactionStats;
|
||||
int lastOomAdj = msg.arg1;
|
||||
int procState = msg.arg2;
|
||||
boolean forceCompaction;
|
||||
synchronized (mProcLock) {
|
||||
if(mPendingCompactionProcesses.isEmpty()) {
|
||||
if (mPendingCompactionProcesses.isEmpty()) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "No processes pending compaction, bail out");
|
||||
}
|
||||
return;
|
||||
}
|
||||
proc = mPendingCompactionProcesses.remove(0);
|
||||
opt = proc.mOptRecord;
|
||||
forceCompaction = opt.isForceCompact();
|
||||
opt.setForceCompact(false); // since this is a one-shot operation
|
||||
|
||||
pendingAction = opt.getReqCompactAction();
|
||||
requestedAction = opt.getReqCompactAction();
|
||||
pid = proc.getPid();
|
||||
name = proc.processName;
|
||||
opt.setHasPendingCompact(false);
|
||||
|
||||
if (mAm.mInternal.isPendingTopUid(proc.uid)) {
|
||||
// In case the OOM Adjust has not yet been propagated we see if this is
|
||||
// pending on becoming top app in which case we should not compact.
|
||||
Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
// don't compact if the process has returned to perceptible
|
||||
// and this is only a cached/home/prev compaction
|
||||
if ((pendingAction == COMPACT_PROCESS_SOME
|
||||
|| pendingAction == COMPACT_PROCESS_FULL)
|
||||
&& (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM,
|
||||
"Skipping compaction as process " + name + " is "
|
||||
+ "now perceptible.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lastCompactAction = opt.getLastCompactAction();
|
||||
lastCompactTime = opt.getLastCompactTime();
|
||||
lastCompactionStats = mLastCompactionStats.get(pid);
|
||||
}
|
||||
|
||||
int resolvedAction = resolveCompactionAction(requestedAction);
|
||||
long[] rssBefore;
|
||||
if (pid == 0) {
|
||||
// not a real process, either one being launched or one being killed
|
||||
return;
|
||||
}
|
||||
|
||||
// basic throttling
|
||||
// use the Phenotype flag knobs to determine whether current/prevous
|
||||
// compaction combo should be throtted or not
|
||||
|
||||
// Note that we explicitly don't take mPhenotypeFlagLock here as the flags
|
||||
// should very seldom change, and taking the risk of using the wrong action is
|
||||
// preferable to taking the lock for every single compaction action.
|
||||
if (lastCompactTime != 0) {
|
||||
if (pendingAction == COMPACT_PROCESS_SOME) {
|
||||
if ((lastCompactAction == COMPACT_PROCESS_SOME
|
||||
&& (start - lastCompactTime < mCompactThrottleSomeSome))
|
||||
|| (lastCompactAction == COMPACT_PROCESS_FULL
|
||||
&& (start - lastCompactTime
|
||||
< mCompactThrottleSomeFull))) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping some compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottleSomeSome
|
||||
+ "/" + mCompactThrottleSomeFull + " last="
|
||||
+ (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (pendingAction == COMPACT_PROCESS_FULL) {
|
||||
if ((lastCompactAction == COMPACT_PROCESS_SOME
|
||||
&& (start - lastCompactTime < mCompactThrottleFullSome))
|
||||
|| (lastCompactAction == COMPACT_PROCESS_FULL
|
||||
&& (start - lastCompactTime
|
||||
< mCompactThrottleFullFull))) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping full compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottleFullSome
|
||||
+ "/" + mCompactThrottleFullFull + " last="
|
||||
+ (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
|
||||
if (start - lastCompactTime < mCompactThrottlePersistent) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping persistent compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottlePersistent
|
||||
+ " last=" + (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (pendingAction == COMPACT_PROCESS_BFGS) {
|
||||
if (start - lastCompactTime < mCompactThrottleBFGS) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
|
||||
+ ": too soon. throttle=" + mCompactThrottleBFGS
|
||||
+ " last=" + (start - lastCompactTime) + "ms ago");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (pendingAction) {
|
||||
case COMPACT_PROCESS_SOME:
|
||||
action = mCompactActionSome;
|
||||
break;
|
||||
// For the time being, treat these as equivalent.
|
||||
case COMPACT_PROCESS_FULL:
|
||||
case COMPACT_PROCESS_PERSISTENT:
|
||||
case COMPACT_PROCESS_BFGS:
|
||||
action = mCompactActionFull;
|
||||
break;
|
||||
default:
|
||||
action = COMPACT_ACTION_STRING[COMPACT_ACTION_NONE];
|
||||
break;
|
||||
}
|
||||
|
||||
if (COMPACT_ACTION_STRING[COMPACT_ACTION_NONE].equals(action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mProcStateThrottle.contains(procState)) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping full compaction for process " + name
|
||||
+ "; proc state is " + procState);
|
||||
Slog.d(TAG_AM, "Compaction failed, pid is 0");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
long[] rssBefore = mProcessDependencies.getRss(pid);
|
||||
long anonRssBefore = rssBefore[2];
|
||||
|
||||
if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
|
||||
&& rssBefore[3] == 0) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
|
||||
+ " with no memory usage. Dead?");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
|
||||
|| action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
|
||||
if (mFullAnonRssThrottleKb > 0L
|
||||
&& anonRssBefore < mFullAnonRssThrottleKb) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping full compaction for process "
|
||||
+ name + "; anon RSS is too small: " + anonRssBefore
|
||||
+ "KB.");
|
||||
}
|
||||
if (!forceCompaction) {
|
||||
if (shouldOomAdjThrottleCompaction(proc, resolvedAction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
|
||||
long[] lastRss = lastCompactionStats.getRssAfterCompaction();
|
||||
long absDelta = Math.abs(rssBefore[1] - lastRss[1])
|
||||
+ Math.abs(rssBefore[2] - lastRss[2])
|
||||
+ Math.abs(rssBefore[3] - lastRss[3]);
|
||||
if (absDelta <= mFullDeltaRssThrottleKb) {
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Skipping full compaction for process "
|
||||
+ name + "; abs delta is too small: " + absDelta
|
||||
+ "KB.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (shouldTimeThrottleCompaction(proc, start, requestedAction)) {
|
||||
return;
|
||||
}
|
||||
if (shouldThrottleMiscCompaction(proc, procState, resolvedAction)) {
|
||||
return;
|
||||
}
|
||||
rssBefore = mProcessDependencies.getRss(pid);
|
||||
if (shouldRssThrottleCompaction(resolvedAction, pid, name, rssBefore)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
rssBefore = mProcessDependencies.getRss(pid);
|
||||
if (DEBUG_COMPACTION) {
|
||||
Slog.d(TAG_AM, "Forcing compaction for " + name);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we've passed through all the throttles and are going to compact, update
|
||||
// bookkeeping.
|
||||
switch (pendingAction) {
|
||||
switch (requestedAction) {
|
||||
case COMPACT_PROCESS_SOME:
|
||||
mSomeCompactionCount++;
|
||||
break;
|
||||
@@ -1360,57 +1471,56 @@ public final class CachedAppOptimizer {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Downgrade compaction if facing swap memory pressure
|
||||
if (action.equals(mCompactActionFull)) {
|
||||
double swapUsagePercent = getFreeSwapPercent();
|
||||
if (swapUsagePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
|
||||
Slog.d(TAG_AM,
|
||||
"Downgraded compaction to file only due to low swap."
|
||||
+ " Swap Free% " + swapUsagePercent);
|
||||
action = mCompactActionSome;
|
||||
pendingAction = COMPACT_PROCESS_SOME;
|
||||
}
|
||||
}
|
||||
action = compactActionIntToString(resolvedAction);
|
||||
|
||||
try {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
|
||||
+ ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
|
||||
+ ": " + name);
|
||||
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
||||
"Compact " + action + ": " + name);
|
||||
long zramFreeKbBefore = Debug.getZramFreeKb();
|
||||
mProcessDependencies.performCompaction(action, pid);
|
||||
long[] rssAfter = mProcessDependencies.getRss(pid);
|
||||
long end = SystemClock.uptimeMillis();
|
||||
long time = end - start;
|
||||
long zramFreeKbAfter = Debug.getZramFreeKb();
|
||||
long deltaTotalRss = rssAfter[RSS_TOTAL_INDEX] - rssBefore[RSS_TOTAL_INDEX];
|
||||
long deltaFileRss = rssAfter[RSS_FILE_INDEX] - rssBefore[RSS_FILE_INDEX];
|
||||
long deltaAnonRss = rssAfter[RSS_ANON_INDEX] - rssBefore[RSS_ANON_INDEX];
|
||||
long deltaSwapRss = rssAfter[RSS_SWAP_INDEX] - rssBefore[RSS_SWAP_INDEX];
|
||||
EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
|
||||
rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
|
||||
rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
|
||||
rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
|
||||
lastCompactAction, lastCompactTime, lastOomAdj, procState,
|
||||
zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
|
||||
rssBefore[RSS_TOTAL_INDEX], rssBefore[RSS_FILE_INDEX],
|
||||
rssBefore[RSS_ANON_INDEX], rssBefore[RSS_SWAP_INDEX], deltaTotalRss,
|
||||
deltaFileRss, deltaAnonRss, deltaSwapRss, time, lastCompactAction,
|
||||
lastCompactTime, lastOomAdj, procState, zramFreeKbBefore,
|
||||
zramFreeKbAfter - zramFreeKbBefore);
|
||||
// Note that as above not taking mPhenoTypeFlagLock here to avoid locking
|
||||
// on every single compaction for a flag that will seldom change and the
|
||||
// impact of reading the wrong value here is low.
|
||||
if (mRandom.nextFloat() < mCompactStatsdSampleRate) {
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPACTED, pid, name,
|
||||
pendingAction, rssBefore[0], rssBefore[1], rssBefore[2],
|
||||
rssBefore[3], rssAfter[0], rssAfter[1], rssAfter[2],
|
||||
rssAfter[3], time, lastCompactAction, lastCompactTime,
|
||||
lastOomAdj, ActivityManager.processStateAmToProto(procState),
|
||||
requestedAction, rssBefore[RSS_TOTAL_INDEX],
|
||||
rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX],
|
||||
rssBefore[RSS_SWAP_INDEX], rssAfter[RSS_TOTAL_INDEX],
|
||||
rssAfter[RSS_FILE_INDEX], rssAfter[RSS_ANON_INDEX],
|
||||
rssAfter[RSS_SWAP_INDEX], time, lastCompactAction,
|
||||
lastCompactTime, lastOomAdj,
|
||||
ActivityManager.processStateAmToProto(procState),
|
||||
zramFreeKbBefore, zramFreeKbAfter);
|
||||
}
|
||||
synchronized (mProcLock) {
|
||||
opt.setLastCompactTime(end);
|
||||
opt.setLastCompactAction(pendingAction);
|
||||
opt.setLastCompactAction(resolvedAction);
|
||||
}
|
||||
if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
|
||||
|| action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
|
||||
if (resolvedAction == COMPACT_ACTION_FULL
|
||||
|| resolvedAction == COMPACT_ACTION_ANON) {
|
||||
// Remove entry and insert again to update insertion order.
|
||||
mLastCompactionStats.remove(pid);
|
||||
mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// nothing to do, presumably the process died
|
||||
Slog.d(TAG_AM,
|
||||
"Exception occurred while compacting pid: " + name
|
||||
+ ". Exception:" + e.getMessage());
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ final class ProcessCachedOptimizerRecord {
|
||||
@GuardedBy("mProcLock")
|
||||
private boolean mPendingCompact;
|
||||
|
||||
@GuardedBy("mProcLock") private boolean mForceCompact;
|
||||
|
||||
/**
|
||||
* True when the process is frozen.
|
||||
*/
|
||||
@@ -132,6 +134,16 @@ final class ProcessCachedOptimizerRecord {
|
||||
mPendingCompact = pendingCompact;
|
||||
}
|
||||
|
||||
@GuardedBy("mProcLock")
|
||||
boolean isForceCompact() {
|
||||
return mForceCompact;
|
||||
}
|
||||
|
||||
@GuardedBy("mProcLock")
|
||||
void setForceCompact(boolean forceCompact) {
|
||||
mForceCompact = forceCompact;
|
||||
}
|
||||
|
||||
@GuardedBy("mProcLock")
|
||||
boolean isFrozen() {
|
||||
return mFrozen;
|
||||
@@ -205,6 +217,9 @@ final class ProcessCachedOptimizerRecord {
|
||||
void dump(PrintWriter pw, String prefix, long nowUptime) {
|
||||
pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
|
||||
pw.print(" lastCompactAction="); pw.println(mLastCompactAction);
|
||||
pw.print(prefix);
|
||||
pw.print("hasPendingCompaction=");
|
||||
pw.print(mPendingCompact);
|
||||
pw.print(prefix); pw.print("isFreezeExempt="); pw.print(mFreezeExempt);
|
||||
pw.print(" isPendingFreeze="); pw.print(mPendingFreeze);
|
||||
pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
|
||||
|
||||
@@ -857,6 +857,7 @@ public final class CachedAppOptimizerTest {
|
||||
.containsExactlyElementsIn(expected);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@Test
|
||||
public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception {
|
||||
// Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
|
||||
@@ -892,7 +893,7 @@ public final class CachedAppOptimizerTest {
|
||||
mProcessDependencies.setRss(rssBefore1);
|
||||
mProcessDependencies.setRssAfterCompaction(rssAfter1); //
|
||||
// WHEN we try to run compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS compacted.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
|
||||
@@ -907,7 +908,7 @@ public final class CachedAppOptimizerTest {
|
||||
processRecord.mOptRecord.setLastCompactTime(
|
||||
processRecord.mOptRecord.getLastCompactTime() - 10_000);
|
||||
// WHEN we try to run compaction.
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS NOT compacted - values after compaction for process 1 should remain the
|
||||
// same as from the last compaction.
|
||||
@@ -923,7 +924,7 @@ public final class CachedAppOptimizerTest {
|
||||
processRecord.mOptRecord.setLastCompactTime(
|
||||
processRecord.mOptRecord.getLastCompactTime() - 10_000);
|
||||
// WHEN we try to run compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS compacted - values after compaction for process 1 should be updated.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
|
||||
@@ -932,6 +933,7 @@ public final class CachedAppOptimizerTest {
|
||||
assertThat(valuesAfter).isEqualTo(rssAfter3);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@Test
|
||||
public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception {
|
||||
// Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
|
||||
@@ -963,7 +965,7 @@ public final class CachedAppOptimizerTest {
|
||||
mProcessDependencies.setRss(rssBelowThreshold);
|
||||
mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter);
|
||||
// WHEN we try to run compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS NOT compacted.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
|
||||
@@ -972,7 +974,7 @@ public final class CachedAppOptimizerTest {
|
||||
mProcessDependencies.setRss(rssAboveThreshold);
|
||||
mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter);
|
||||
// WHEN we try to run compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS compacted.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
|
||||
@@ -981,6 +983,7 @@ public final class CachedAppOptimizerTest {
|
||||
assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@Test
|
||||
public void processWithOomAdjTooSmall_notFullCompacted() throws Exception {
|
||||
// Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set Min and
|
||||
@@ -993,10 +996,11 @@ public final class CachedAppOptimizerTest {
|
||||
|
||||
// Simulate RSS memory for which compaction should occur.
|
||||
long[] rssBefore =
|
||||
new long[]{/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000,
|
||||
/*Swap*/ 10000};
|
||||
new long[]{/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000,
|
||||
/*Swap*/ 10000};
|
||||
long[] rssAfter =
|
||||
new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000};
|
||||
new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/
|
||||
5000};
|
||||
// Process that passes properties.
|
||||
int pid = 1;
|
||||
ProcessRecord processRecord =
|
||||
@@ -1010,7 +1014,7 @@ public final class CachedAppOptimizerTest {
|
||||
processRecord.mState.setSetAdj(899);
|
||||
processRecord.mState.setCurAdj(970);
|
||||
// WHEN we try to run compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS NOT compacted.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
|
||||
@@ -1019,16 +1023,71 @@ public final class CachedAppOptimizerTest {
|
||||
processRecord.mState.setSetAdj(910);
|
||||
processRecord.mState.setCurAdj(930);
|
||||
// WHEN we try to run compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// THEN process IS compacted.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
|
||||
long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats
|
||||
.get(pid)
|
||||
.getRssAfterCompaction();
|
||||
.get(pid)
|
||||
.getRssAfterCompaction();
|
||||
assertThat(valuesAfter).isEqualTo(rssAfter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@Test
|
||||
public void process_forceCompacted() throws Exception {
|
||||
mCachedAppOptimizerUnderTest.init();
|
||||
setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true);
|
||||
setFlag(CachedAppOptimizer.KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, Long.toString(920), true);
|
||||
setFlag(CachedAppOptimizer.KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, Long.toString(950), true);
|
||||
initActivityManagerService();
|
||||
|
||||
long[] rssBefore = new long[] {/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000,
|
||||
/*Swap*/ 10000};
|
||||
long[] rssAfter = new long[] {
|
||||
/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/ 5000};
|
||||
// Process that passes properties.
|
||||
int pid = 1;
|
||||
ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1");
|
||||
mProcessDependencies.setRss(rssBefore);
|
||||
mProcessDependencies.setRssAfterCompaction(rssAfter);
|
||||
|
||||
// Use an OOM Adjust value that usually avoids compaction
|
||||
processRecord.mState.setSetAdj(100);
|
||||
processRecord.mState.setCurAdj(100);
|
||||
|
||||
// Compact process full
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
|
||||
waitForHandler();
|
||||
// the process is not compacted
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
|
||||
|
||||
// Compact process some
|
||||
mCachedAppOptimizerUnderTest.compactAppSome(processRecord, false);
|
||||
waitForHandler();
|
||||
// the process is not compacted
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
|
||||
|
||||
processRecord.mState.setSetAdj(100);
|
||||
processRecord.mState.setCurAdj(100);
|
||||
|
||||
// We force a full compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppFull(processRecord, true);
|
||||
waitForHandler();
|
||||
// then process is compacted.
|
||||
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
|
||||
|
||||
mCachedAppOptimizerUnderTest.mLastCompactionStats.clear();
|
||||
|
||||
// We force a some compaction
|
||||
mCachedAppOptimizerUnderTest.compactAppSome(processRecord, true);
|
||||
waitForHandler();
|
||||
// then process is compacted.
|
||||
String executedCompactAction =
|
||||
compactActionIntToString(processRecord.mOptRecord.getLastCompactAction());
|
||||
assertThat(executedCompactAction)
|
||||
.isEqualTo(mCachedAppOptimizerUnderTest.mCompactActionSome);
|
||||
}
|
||||
|
||||
private void setFlag(String key, String value, boolean defaultValue) throws Exception {
|
||||
mCountDown = new CountDownLatch(1);
|
||||
|
||||
Reference in New Issue
Block a user