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

Commit 4f7bc4a3 authored by Yisroel Forta's avatar Yisroel Forta Committed by Android (Google) Code Review
Browse files

Merge "AppStartInfo monitoring mode" into main

parents 1e93df06 42945dc8
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);