summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/proto/android/server/activitymanagerservice.proto1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java28
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java126
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);