Loading core/proto/android/server/activitymanagerservice.proto +1 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading services/core/java/com/android/server/am/ActivityManagerShellCommand.java +28 −0 Original line number Diff line number Diff line Loading @@ -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": Loading Loading @@ -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()"); Loading Loading @@ -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]"); Loading services/core/java/com/android/server/am/AppStartInfoTracker.java +124 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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, Loading Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); } Loading @@ -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); Loading Loading
core/proto/android/server/activitymanagerservice.proto +1 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/core/java/com/android/server/am/ActivityManagerShellCommand.java +28 −0 Original line number Diff line number Diff line Loading @@ -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": Loading Loading @@ -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()"); Loading Loading @@ -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]"); Loading
services/core/java/com/android/server/am/AppStartInfoTracker.java +124 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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, Loading Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); } Loading @@ -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); Loading