diff options
3 files changed, 153 insertions, 2 deletions
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index d31baf33eaf7..e3a438da5abc 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -1059,6 +1059,7 @@ message AppsStartInfoProto { optional int32 uid = 1; repeated .android.app.ApplicationStartInfoProto app_start_info = 2; + optional bool monitoring_enabled = 3; } repeated User users = 2; } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index bf4f34fd799f..9f2bc06ae8ae 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -281,6 +281,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runClearWatchHeap(pw); case "clear-start-info": return runClearStartInfo(pw); + case "start-info-detailed-monitoring": + return runStartInfoDetailedMonitoring(pw); case "clear-exit-info": return runClearExitInfo(pw); case "bug-report": @@ -1415,6 +1417,29 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runStartInfoDetailedMonitoring(PrintWriter pw) throws RemoteException { + String opt; + int userId = UserHandle.USER_CURRENT; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + if (userId == UserHandle.USER_CURRENT) { + UserInfo user = mInterface.getCurrentUser(); + if (user == null) { + return -1; + } + userId = user.id; + } + mInternal.mProcessList.getAppStartInfoTracker() + .configureDetailedMonitoring(pw, getNextArg(), userId); + return 0; + } + int runClearExitInfo(PrintWriter pw) throws RemoteException { mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, "runClearExitInfo()"); @@ -4358,6 +4383,9 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Clear the previously set-watch-heap."); pw.println(" clear-start-info [--user <USER_ID> | all | current] [package]"); pw.println(" Clear the process start-info for given package"); + pw.println(" start-info-detailed-monitoring [--user <USER_ID> | all | current] <PACKAGE>"); + pw.println(" Enable application start info detailed monitoring for the given package."); + pw.println(" Disable if no package is supplied."); pw.println(" clear-exit-info [--user <USER_ID> | all | current] [package]"); pw.println(" Clear the process exit-info for given package"); pw.println(" bug-report [--progress | --telephony]"); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 2be1fe26628e..376f65438e64 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -65,6 +65,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -82,8 +83,12 @@ public final class AppStartInfoTracker { private static final int FOREACH_ACTION_REMOVE_ITEM = 1; private static final int FOREACH_ACTION_STOP_ITERATION = 2; + private static final String MONITORING_MODE_EMPTY_TEXT = "No records"; + @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; + private static final int APP_START_INFO_MONITORING_MODE_LIST_SIZE = 100; + @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore"; @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo"; @@ -426,6 +431,40 @@ public final class AppStartInfoTracker { ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE); } + /** + * Helper functions for monitoring shell command. + * > adb shell am start-info-detailed-monitoring [package-name] + */ + void configureDetailedMonitoring(PrintWriter pw, String packageName, int userId) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + + forEachPackageLocked((name, records) -> { + for (int i = 0; i < records.size(); i++) { + records.valueAt(i).disableAppMonitoringMode(); + } + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + + if (TextUtils.isEmpty(packageName)) { + pw.println("ActivityManager AppStartInfo detailed monitoring disabled"); + } else { + SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName); + if (array != null) { + for (int i = 0; i < array.size(); i++) { + array.valueAt(i).enableAppMonitoringModeForUser(userId); + } + pw.println("ActivityManager AppStartInfo detailed monitoring enabled for " + + packageName); + } else { + pw.println("Package " + packageName + " not found"); + } + } + } + } + /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */ public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) { addTimestampToStart(app, timeNs, @@ -1011,15 +1050,46 @@ public final class AppStartInfoTracker { /** A container class of (@link android.app.ApplicationStartInfo) */ final class AppStartInfoContainer { - private List<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp. + private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp. private int mMaxCapacity; private int mUid; + private boolean mMonitoringModeEnabled = false; AppStartInfoContainer(final int maxCapacity) { mInfos = new ArrayList<ApplicationStartInfo>(); mMaxCapacity = maxCapacity; } + int getMaxCapacity() { + return mMonitoringModeEnabled ? APP_START_INFO_MONITORING_MODE_LIST_SIZE : mMaxCapacity; + } + + @GuardedBy("mLock") + void enableAppMonitoringModeForUser(int userId) { + if (UserHandle.getUserId(mUid) == userId) { + mMonitoringModeEnabled = true; + } + } + + @GuardedBy("mLock") + void disableAppMonitoringMode() { + mMonitoringModeEnabled = false; + + // Capacity is reduced by turning off monitoring mode. Check if array size is within + // new lower limits and trim extraneous records if it is not. + if (mInfos.size() <= getMaxCapacity()) { + return; + } + + // Sort records so we can remove the least recent ones. + Collections.sort(mInfos, (a, b) -> + Long.compare(getStartTimestamp(b), getStartTimestamp(a))); + + // Remove records and trim list object back to size. + mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear(); + mInfos.trimToSize(); + } + @GuardedBy("mLock") void getStartInfoLocked( final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results) { @@ -1029,7 +1099,7 @@ public final class AppStartInfoTracker { @GuardedBy("mLock") void addStartInfoLocked(ApplicationStartInfo info) { int size = mInfos.size(); - if (size >= mMaxCapacity) { + if (size >= getMaxCapacity()) { // Remove oldest record if size is over max capacity. int oldestIndex = -1; long oldestTimeStamp = Long.MAX_VALUE; @@ -1061,12 +1131,59 @@ public final class AppStartInfoTracker { @GuardedBy("mLock") void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { + if (mMonitoringModeEnabled) { + // For monitoring mode, calculate the average start time for each start state to + // add to output. + List<Long> coldStartTimes = new ArrayList<>(); + List<Long> warmStartTimes = new ArrayList<>(); + List<Long> hotStartTimes = new ArrayList<>(); + + for (int i = 0; i < mInfos.size(); i++) { + ApplicationStartInfo startInfo = mInfos.get(i); + Map<Integer, Long> timestamps = startInfo.getStartupTimestamps(); + + // Confirm required timestamps exist. + if (timestamps.containsKey(ApplicationStartInfo.START_TIMESTAMP_LAUNCH) + && timestamps.containsKey( + ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME)) { + // Add timestamp to correct collection. + long time = timestamps.get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME) + - timestamps.get(ApplicationStartInfo.START_TIMESTAMP_LAUNCH); + switch (startInfo.getStartType()) { + case ApplicationStartInfo.START_TYPE_COLD: + coldStartTimes.add(time); + break; + case ApplicationStartInfo.START_TYPE_WARM: + warmStartTimes.add(time); + break; + case ApplicationStartInfo.START_TYPE_HOT: + hotStartTimes.add(time); + break; + } + } + } + + pw.println(prefix + " Average Start Time in ns for Cold Starts: " + + (coldStartTimes.isEmpty() ? MONITORING_MODE_EMPTY_TEXT + : calculateAverage(coldStartTimes))); + pw.println(prefix + " Average Start Time in ns for Warm Starts: " + + (warmStartTimes.isEmpty() ? MONITORING_MODE_EMPTY_TEXT + : calculateAverage(warmStartTimes))); + pw.println(prefix + " Average Start Time in ns for Hot Starts: " + + (hotStartTimes.isEmpty() ? MONITORING_MODE_EMPTY_TEXT + : calculateAverage(hotStartTimes))); + } + int size = mInfos.size(); for (int i = 0; i < size; i++) { mInfos.get(i).dump(pw, prefix + " ", "#" + i, sdf); } } + private long calculateAverage(List<Long> vals) { + return (long) vals.stream().mapToDouble(a -> a).average().orElse(0.0); + } + @GuardedBy("mLock") void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { long token = proto.start(fieldId); @@ -1076,6 +1193,7 @@ public final class AppStartInfoTracker { mInfos.get(i) .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); } + proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled); proto.end(token); } @@ -1094,6 +1212,10 @@ public final class AppStartInfoTracker { info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); mInfos.add(info); break; + case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED: + mMonitoringModeEnabled = proto.readBoolean( + AppsStartInfoProto.Package.User.MONITORING_ENABLED); + break; } } proto.end(token); |