Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 42945dc8 authored by Yisroel Forta's avatar Yisroel Forta
Browse files

AppStartInfo monitoring mode

Monitoring mode allows developers to set a single app which will be given higher AppStartInfo record limits as well as more detailed output in adb command.

Test: set app to monitoring, start many times, trigger adb cmd and see extended output

Bug: 281763273
Change-Id: Icdddc79f62b11369b1857abd0670618667295947
parent 39af13d2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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;
    }
+28 −0
Original line number Diff line number Diff line
@@ -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]");
+124 −2
Original line number Diff line number Diff line
@@ -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);