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:
Edgar Arriaga
2022-03-28 16:47:48 -07:00
parent 720a6a5a21
commit b0ef1944fb
4 changed files with 414 additions and 194 deletions

View File

@@ -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]]");

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);