diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 71ae92aecbcfe..27facccfaf42d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -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 [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 ] [-p ] [-w]"); pw.println(" [--user | current]"); pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 792b697e380e7..e91b2b32f930e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -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 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); } diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index a86ba016eeff7..a613729b441f2 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -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); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index bf46f555004c9..2baa1ec6cdc24 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -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);