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

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

Merge changes I8db250d8,I34637a6f,Iea2d5342 into main

* changes:
  Fix typo in AppStartInfo monotonic time
  Keeping start info records sorted
  Cleanup startinfo old records
parents 670308c5 436088d1
Loading
Loading
Loading
Loading
+13 −13
Original line number Diff line number Diff line
@@ -231,9 +231,9 @@ public final class ApplicationStartInfo implements Parcelable {
    public static final int START_COMPONENT_OTHER = 5;

    /**
     * @see #getMonoticCreationTimeMs
     * @see #getMonotonicCreationTimeMs
     */
    private long mMonoticCreationTimeMs;
    private long mMonotonicCreationTimeMs;

    /**
     * @see #getStartupState
@@ -545,8 +545,8 @@ public final class ApplicationStartInfo implements Parcelable {
     *
     * @hide
     */
    public long getMonoticCreationTimeMs() {
        return mMonoticCreationTimeMs;
    public long getMonotonicCreationTimeMs() {
        return mMonotonicCreationTimeMs;
    }

    /**
@@ -751,14 +751,14 @@ public final class ApplicationStartInfo implements Parcelable {
        dest.writeParcelable(mStartIntent, flags);
        dest.writeInt(mLaunchMode);
        dest.writeBoolean(mWasForceStopped);
        dest.writeLong(mMonoticCreationTimeMs);
        dest.writeLong(mMonotonicCreationTimeMs);
        dest.writeInt(mStartComponent);
    }
    // LINT.ThenChange(:read_parcel)

    /** @hide */
    public ApplicationStartInfo(long monotonicCreationTimeMs) {
        mMonoticCreationTimeMs = monotonicCreationTimeMs;
        mMonotonicCreationTimeMs = monotonicCreationTimeMs;
    }

    /** @hide */
@@ -776,7 +776,7 @@ public final class ApplicationStartInfo implements Parcelable {
        mStartIntent = other.mStartIntent;
        mLaunchMode = other.mLaunchMode;
        mWasForceStopped = other.mWasForceStopped;
        mMonoticCreationTimeMs = other.mMonoticCreationTimeMs;
        mMonotonicCreationTimeMs = other.mMonotonicCreationTimeMs;
        mStartComponent = other.mStartComponent;
    }

@@ -803,7 +803,7 @@ public final class ApplicationStartInfo implements Parcelable {
                in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
        mLaunchMode = in.readInt();
        mWasForceStopped = in.readBoolean();
        mMonoticCreationTimeMs = in.readLong();
        mMonotonicCreationTimeMs = in.readLong();
        mStartComponent = in.readInt();
    }
    // LINT.ThenChange(:write_parcel)
@@ -887,7 +887,7 @@ public final class ApplicationStartInfo implements Parcelable {
        }
        proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
        proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
        proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs);
        proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonotonicCreationTimeMs);
        proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent);
        proto.end(token);
    }
@@ -980,7 +980,7 @@ public final class ApplicationStartInfo implements Parcelable {
                            ApplicationStartInfoProto.WAS_FORCE_STOPPED);
                    break;
                case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS:
                    mMonoticCreationTimeMs = proto.readLong(
                    mMonotonicCreationTimeMs = proto.readLong(
                            ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS);
                    break;
                case (int) ApplicationStartInfoProto.START_COMPONENT:
@@ -999,7 +999,7 @@ public final class ApplicationStartInfo implements Parcelable {
        sb.append(prefix)
                .append("ApplicationStartInfo ").append(seqSuffix).append(':')
                .append('\n')
                .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs)
                .append(" monotonicCreationTimeMs=").append(mMonotonicCreationTimeMs)
                .append('\n')
                .append(" pid=").append(mPid)
                .append(" realUid=").append(mRealUid)
@@ -1094,7 +1094,7 @@ public final class ApplicationStartInfo implements Parcelable {
                && TextUtils.equals(mProcessName, o.mProcessName)
                && timestampsEquals(o)
                && mWasForceStopped == o.mWasForceStopped
                && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs
                && mMonotonicCreationTimeMs == o.mMonotonicCreationTimeMs
                && mStartComponent == o.mStartComponent;
    }

@@ -1102,7 +1102,7 @@ public final class ApplicationStartInfo implements Parcelable {
    public int hashCode() {
        return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
                mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs,
                mMonoticCreationTimeMs, mStartComponent);
                mMonotonicCreationTimeMs, mStartComponent);
    }

    private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
+20 −0
Original line number Diff line number Diff line
@@ -170,3 +170,23 @@ flag {
    description: "Holdback study for jank_perceptible_narrow"
    bug: "304837972"
}

flag {
     namespace: "system_performance"
     name: "app_start_info_cleanup_old_records"
     description: "Cleanup old records to reduce size of in memory store."
     bug: "384539178"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
}

flag {
     namespace: "system_performance"
     name: "app_start_info_keep_records_sorted"
     description: "Ensure records are kept sorted to avoid extra work"
     bug: "384539178"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
}
+81 −36
Original line number Diff line number Diff line
@@ -98,6 +98,9 @@ public final class AppStartInfoTracker {

    @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;

    @VisibleForTesting
    static final long APP_START_INFO_HISTORY_LENGTH_MS = TimeUnit.DAYS.toMillis(14);

    /**
     * The max number of records that can be present in {@link mInProgressRecords}.
     *
@@ -120,9 +123,13 @@ public final class AppStartInfoTracker {
     * Monotonic clock which does not reset on reboot.
     *
     * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}.
     * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as
     * it's ok in this case to lose any time change past the last persist as records added since
     * then will be lost as well and the purpose of this clock is to keep records in order.
     * This does not currently follow the recommendation of {@link MonotonicClock} to persist on
     * shutdown as it's ok in this case to lose any time change past the last persist as records
     * added since then will be lost as well. Since this time is used for cleanup as well, the
     * potential old offset may result in the cleanup window being extended slightly beyond the
     * targeted 14 days.
     *
     * TODO: b/402794215 - Persist on shutdown once persist performance is sufficiently improved.
     */
    @VisibleForTesting MonotonicClock mMonotonicClock = null;

@@ -296,7 +303,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.setIntent(intent);
            start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
@@ -454,7 +461,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -484,7 +491,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -511,7 +518,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -533,7 +540,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -721,8 +728,8 @@ public final class AppStartInfoTracker {

                    Collections.sort(
                            list, (a, b) ->
                            Long.compare(b.getMonoticCreationTimeMs(),
                                    a.getMonoticCreationTimeMs()));
                            Long.compare(b.getMonotonicCreationTimeMs(),
                                    a.getMonotonicCreationTimeMs()));
                    int size = list.size();
                    if (maxNum > 0) {
                        size = Math.min(size, maxNum);
@@ -1098,7 +1105,7 @@ public final class AppStartInfoTracker {
                    mLastAppStartInfoPersistTimestamp = now;
                }
            }
            proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime());
            proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTimeMs());
            if (succeeded) {
                proto.flush();
                af.finishWrite(out);
@@ -1219,7 +1226,11 @@ public final class AppStartInfoTracker {
        }
    }

    private long getMonotonicTime() {
    /**
     * Monotonic time that doesn't change with reboot or device time change for ordering records.
     */
    @VisibleForTesting
    public long getMonotonicTimeMs() {
        if (mMonotonicClock == null) {
            // This should never happen. Return 0 to not interfere with past or future records.
            return 0;
@@ -1229,7 +1240,7 @@ public final class AppStartInfoTracker {

    /** A container class of (@link android.app.ApplicationStartInfo) */
    final class AppStartInfoContainer {
        private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp.
        private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by monotonic time.
        private int mMaxCapacity;
        private int mUid;
        private boolean mMonitoringModeEnabled = false;
@@ -1260,9 +1271,12 @@ public final class AppStartInfoTracker {
                return;
            }

            if (!android.app.Flags.appStartInfoKeepRecordsSorted()) {
                // Sort records so we can remove the least recent ones.
                Collections.sort(mInfos, (a, b) ->
                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
                        Long.compare(b.getMonotonicCreationTimeMs(),
                                a.getMonotonicCreationTimeMs()));
            }

            // Remove records and trim list object back to size.
            mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear();
@@ -1277,6 +1291,13 @@ public final class AppStartInfoTracker {

        @GuardedBy("mLock")
        void addStartInfoLocked(ApplicationStartInfo info) {
            if (android.app.Flags.appStartInfoKeepRecordsSorted()) {
                while (mInfos.size() >= getMaxCapacity()) {
                    // Expected to execute at most once.
                    mInfos.removeLast();
                }
                mInfos.addFirst(info);
            } else {
                int size = mInfos.size();
                if (size >= getMaxCapacity()) {
                    // Remove oldest record if size is over max capacity.
@@ -1284,8 +1305,8 @@ public final class AppStartInfoTracker {
                    long oldestTimeStamp = Long.MAX_VALUE;
                    for (int i = 0; i < size; i++) {
                        ApplicationStartInfo startInfo = mInfos.get(i);
                    if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) {
                        oldestTimeStamp = startInfo.getMonoticCreationTimeMs();
                        if (startInfo.getMonotonicCreationTimeMs() < oldestTimeStamp) {
                            oldestTimeStamp = startInfo.getMonotonicCreationTimeMs();
                            oldestIndex = i;
                        }
                    }
@@ -1295,7 +1316,9 @@ public final class AppStartInfoTracker {
                }
                mInfos.add(info);
                Collections.sort(mInfos, (a, b) ->
                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
                        Long.compare(b.getMonotonicCreationTimeMs(),
                                a.getMonotonicCreationTimeMs()));
            }
        }

        /**
@@ -1439,10 +1462,26 @@ public final class AppStartInfoTracker {
            long token = proto.start(fieldId);
            proto.write(AppsStartInfoProto.Package.User.UID, mUid);
            int size = mInfos.size();
            if (android.app.Flags.appStartInfoCleanupOldRecords()) {
                long removeOlderThan = getMonotonicTimeMs() - APP_START_INFO_HISTORY_LENGTH_MS;
                // Iterate backwards so we can remove old records as we go.
                for (int i = size - 1; i >= 0; i--) {
                    if (mInfos.get(i).getMonotonicCreationTimeMs() < removeOlderThan) {
                        // Remove the record.
                        mInfos.remove(i);
                    } else {
                        mInfos.get(i).writeToProto(
                                proto, AppsStartInfoProto.Package.User.APP_START_INFO,
                                byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
                    }
                }
            } else {
                for (int i = 0; i < size; i++) {
                mInfos.get(i).writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
                    mInfos.get(i).writeToProto(
                            proto, AppsStartInfoProto.Package.User.APP_START_INFO,
                            byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
                }
            }
            proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled);
            proto.end(token);
        }
@@ -1466,7 +1505,13 @@ public final class AppStartInfoTracker {
                        info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
                                byteArrayInputStream, objectInputStream, typedXmlPullParser);
                        info.setPackageName(packageName);
                        if (android.app.Flags.appStartInfoKeepRecordsSorted()) {
                            // Since the writes are done from oldest to newest, each additional
                            // record will be newer than the previous so use addFirst.
                            mInfos.addFirst(info);
                        } else {
                            mInfos.add(info);
                        }
                        break;
                    case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED:
                        mMonitoringModeEnabled = proto.readBoolean(
+61 −0
Original line number Diff line number Diff line
@@ -680,6 +680,67 @@ public class ApplicationStartInfoTest {
                ApplicationStartInfo.START_TIMESTAMP_FORK));
    }

    /**
     * Test that cleanup old records works as expected, removing records that are older than the max
     * retention length.
     */
    @Test
    @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_CLEANUP_OLD_RECORDS)
    public void testOldRecordsCleanup() throws Exception {
        // Use a different start timestamp for each record so we can identify which was removed.
        // This timestamp is not used for ordering and has no impact on removal.
        final long startTimeRecord1 = 123L;
        final long startTimeRecord2 = 456L;
        final long startTimeRecord3 = 789L;

        // Create a process record to use with all starts.
        ProcessRecord app = makeProcessRecord(
                APP_1_PID_1,                     // pid
                APP_1_UID,                       // uid
                APP_1_UID,                       // packageUid
                null,                            // definingUid
                APP_1_PROCESS_NAME,              // processName
                APP_1_PACKAGE_NAME);             // packageName

        // Set monotonic time to 1, and then trigger a start info record.
        doReturn(1L).when(mAppStartInfoTracker).getMonotonicTimeMs();
        mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord1, app,
                buildIntent(COMPONENT), false /* isAlarm */);

        // Set monotonic time to 2, and then trigger another start info record.
        doReturn(2L).when(mAppStartInfoTracker).getMonotonicTimeMs();
        mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord2, app,
                buildIntent(COMPONENT), false /* isAlarm */);

        // Set monotonic time to 3, then trigger another start info record.
        doReturn(3L).when(mAppStartInfoTracker).getMonotonicTimeMs();
        mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord3, app,
                buildIntent(COMPONENT), false /* isAlarm */);

        // Verify that all 3 records were added successfully.
        ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
        assertEquals(3, list.size());
        assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue());
        assertEquals(startTimeRecord2, list.get(1).getStartupTimestamps().get(0).longValue());
        assertEquals(startTimeRecord1, list.get(2).getStartupTimestamps().get(0).longValue());

        // Set monotonic time to max history length + 3 so that the older 2 records will be removed
        // but that newest 1 will remain.
        doReturn(AppStartInfoTracker.APP_START_INFO_HISTORY_LENGTH_MS + 3L)
                .when(mAppStartInfoTracker).getMonotonicTimeMs();

        // Trigger a persist which will trigger the cleanup of old records.
        mAppStartInfoTracker.persistProcessStartInfo();

        // Now verify that the records older than max were removed, and that the records not older
        // remain.
        list.clear();
        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
        assertEquals(1, list.size());
        assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue());
    }

    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
        try {
            Field field = clazz.getDeclaredField(fieldName);