Add activitymanager debug option to freeze a process

This makes testing paths that rely on freezer such as compaction
etc doable in a simple way as well as allowing instrumentation
to triggering them.

Bug: 274177499
Test: am freeze [--sticky] <processname>
Test: am unfreeze [--sticky] <processname>
Change-Id: I832795b59d9a1078665a5474506732c3c7039abe
This commit is contained in:
Edgar Arriaga
2023-03-17 23:15:27 +00:00
parent 18929c87c9
commit 9dbe8e97f8
3 changed files with 153 additions and 31 deletions

View File

@@ -247,6 +247,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runSendBroadcast(pw);
case "compact":
return runCompact(pw);
case "freeze":
return runFreeze(pw);
case "unfreeze":
return runUnfreeze(pw);
case "instrument":
getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
return -1;
@@ -1074,20 +1078,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
boolean isFullCompact = op.equals("full");
boolean isSomeCompact = op.equals("some");
if (isFullCompact || isSomeCompact) {
String processName = getNextArgRequired();
synchronized (mInternal.mProcLock) {
// Default to current user
int userId = mInterface.getCurrentUserId();
String userOpt = getNextOption();
if (userOpt != null && "--user".equals(userOpt)) {
int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
if (inputUserId != UserHandle.USER_CURRENT) {
userId = inputUserId;
}
}
final int uid =
mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
app = mInternal.getProcessRecordLocked(processName, uid);
app = getProcessFromShell();
if (app == null) {
getErrPrintWriter().println("Error: could not find process");
return -1;
}
pw.println("Process record found pid: " + app.mPid);
if (isFullCompact) {
@@ -1143,6 +1137,93 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
@NeverCompile
int runFreeze(PrintWriter pw) throws RemoteException {
String freezerOpt = getNextOption();
boolean isSticky = false;
if (freezerOpt != null) {
isSticky = freezerOpt.equals("--sticky");
}
ProcessRecord app = getProcessFromShell();
if (app == null) {
getErrPrintWriter().println("Error: could not find process");
return -1;
}
pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky);
synchronized (mInternal) {
synchronized (mInternal.mProcLock) {
app.mOptRecord.setFreezeSticky(isSticky);
mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true);
}
}
return 0;
}
@NeverCompile
int runUnfreeze(PrintWriter pw) throws RemoteException {
String freezerOpt = getNextOption();
boolean isSticky = false;
if (freezerOpt != null) {
isSticky = freezerOpt.equals("--sticky");
}
ProcessRecord app = getProcessFromShell();
if (app == null) {
getErrPrintWriter().println("Error: could not find process");
return -1;
}
pw.println("Unfreezing pid: " + app.mPid);
synchronized (mInternal) {
synchronized (mInternal.mProcLock) {
synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) {
app.mOptRecord.setFreezeSticky(isSticky);
mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0,
false);
}
}
}
return 0;
}
/**
* Parses from the shell the process name and user id if provided and provides the corresponding
* {@link ProcessRecord)} If no user is provided, it will fallback to current user.
* Example usage: {@code <processname> --user current} or {@code <processname>}
* @return process record of process, null if none found.
* @throws RemoteException
*/
@NeverCompile
ProcessRecord getProcessFromShell() throws RemoteException {
ProcessRecord app;
String processName = getNextArgRequired();
synchronized (mInternal.mProcLock) {
// Default to current user
int userId = getUserIdFromShellOrFallback();
final int uid =
mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
app = mInternal.getProcessRecordLocked(processName, uid);
}
return app;
}
/**
* @return User id from command line provided in the form of
* {@code --user <userid|current|all>} and if the argument is not found it will fallback
* to current user.
* @throws RemoteException
*/
@NeverCompile
int getUserIdFromShellOrFallback() throws RemoteException {
int userId = mInterface.getCurrentUserId();
String userOpt = getNextOption();
if (userOpt != null && "--user".equals(userOpt)) {
int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
if (inputUserId != UserHandle.USER_CURRENT) {
userId = inputUserId;
}
}
return userId;
}
int runDumpHeap(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
boolean managed = true;
@@ -4061,6 +4142,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Perform a native compaction for process with <pid>.");
pw.println(" some: execute file compaction.");
pw.println(" full: execute anon + file compaction.");
pw.println(" freeze [--sticky] <processname> [--user <USER_ID>]");
pw.println(" Freeze a process.");
pw.println(" --sticky: persists the frozen state for the process lifetime or");
pw.println(" until an unfreeze is triggered via shell");
pw.println(" unfreeze [--sticky] <processname> [--user <USER_ID>]");
pw.println(" Unfreeze a process.");
pw.println(" --sticky: persists the unfrozen state for the process lifetime or");
pw.println(" until a freeze is triggered via shell");
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

@@ -289,7 +289,7 @@ public final class CachedAppOptimizer {
private final ActivityManagerGlobalLock mProcLock;
private final Object mFreezerLock = new Object();
public final Object mFreezerLock = new Object();
private final OnPropertiesChangedListener mOnFlagsChangedListener =
new OnPropertiesChangedListener() {
@@ -710,8 +710,9 @@ public final class CachedAppOptimizer {
pw.println(" Apps frozen: " + size);
for (int i = 0; i < size; i++) {
ProcessRecord app = mFrozenProcesses.valueAt(i);
pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime()
+ ": " + app.getPid() + " " + app.processName);
pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid()
+ " " + app.processName
+ (app.mOptRecord.isFreezeSticky() ? " (sticky)" : ""));
}
if (!mPendingCompactionProcesses.isEmpty()) {
@@ -1230,12 +1231,26 @@ public final class CachedAppOptimizer {
@GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncLSP(ProcessRecord app) {
freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false);
}
@GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
// Skip redundant DO_FREEZE message
return;
}
if (opt.isFreezeSticky() && !force) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM,
"Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " "
+ app.processName);
}
return;
}
if (mAm.mConstants.USE_MODERN_TRIM
&& app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
final IApplicationThread thread = app.getThread();
@@ -1248,9 +1263,8 @@ public final class CachedAppOptimizer {
}
}
mFreezeHandler.sendMessageDelayed(
mFreezeHandler.obtainMessage(
SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
mFreezerDebounceTimeout);
mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
delayMillis);
opt.setPendingFreeze(true);
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName);
@@ -1258,9 +1272,19 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason) {
void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
boolean sticky = opt.isFreezeSticky();
if (sticky && !force) {
// Sticky freezes will not change their state unless forced out of it.
if (DEBUG_FREEZER) {
Slog.d(TAG_AM,
"Skip unfreezing because frozen state is sticky pid=" + pid + " "
+ app.processName);
}
return;
}
if (opt.isPendingFreeze()) {
// Remove pending DO_FREEZE message
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
@@ -1354,7 +1378,7 @@ public final class CachedAppOptimizer {
@GuardedBy({"mAm", "mProcLock"})
void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
synchronized (mFreezerLock) {
unfreezeAppInternalLSP(app, reason);
unfreezeAppInternalLSP(app, reason, false);
}
}
@@ -2012,15 +2036,6 @@ public final class CachedAppOptimizer {
synchronized (mProcLock) {
pid = proc.getPid();
if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
|| opt.shouldNotFreeze()) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Skipping freeze for process " + pid
+ " " + name + " curAdj = " + proc.mState.getCurAdj()
+ ", shouldNotFreeze = " + opt.shouldNotFreeze());
}
return;
}
if (mFreezerOverride) {
opt.setFreezerOverride(true);

View File

@@ -72,6 +72,15 @@ final class ProcessCachedOptimizerRecord {
@GuardedBy("mProcLock")
private boolean mFrozen;
/**
* If set to true it will make the (un)freeze decision sticky which means that the freezer
* decision will remain the same unless a freeze is forced via {@link #mForceFreezeOps}.
* This property is usually set to true when external user wants to maintain a (un)frozen state
* after being applied.
*/
@GuardedBy("mProcLock")
private boolean mFreezeSticky;
/**
* Set to false after the process has been frozen.
* Set to true after we have collected PSS for the frozen process.
@@ -193,6 +202,15 @@ final class ProcessCachedOptimizerRecord {
void setFrozen(boolean frozen) {
mFrozen = frozen;
}
@GuardedBy("mProcLock")
void setFreezeSticky(boolean sticky) {
mFreezeSticky = sticky;
}
@GuardedBy("mProcLock")
boolean isFreezeSticky() {
return mFreezeSticky;
}
boolean skipPSSCollectionBecauseFrozen() {
boolean collected = mHasCollectedFrozenPSS;