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

Commit 6c42dde4 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: implement per-process tracing.

To help investigate and debug our new per-process queues, this change
adds "track" based trace events, with both a general "BroadcastQueue"
track and a separate track for each runnable slot.

Note that to view any "tracks" you'll need to enable the "canary"
version of the Perfetto UI.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: I80c3b0aadf30250999738ebbfc83a7028a0d68e9
parent dd35750c
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.server.am.BroadcastQueue.checkState;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.os.Trace;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;

@@ -77,6 +78,11 @@ class BroadcastProcessQueue {
     */
    @Nullable ProcessRecord app;

    /**
     * Track name to use for {@link Trace} events.
     */
    @Nullable String traceTrackName;

    /**
     * Ordered collection of broadcasts that are waiting to be dispatched to
     * this process, as a pair of {@link BroadcastRecord} and the index into
@@ -219,9 +225,41 @@ class BroadcastProcessQueue {
        mActiveCountSinceIdle = 0;
    }

    public void traceStartingBegin() {
        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                traceTrackName, toShortString() + " starting", hashCode());
    }

    public void traceRunningBegin() {
        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                traceTrackName, toShortString() + " running", hashCode());
    }

    public void traceEnd() {
        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                traceTrackName, hashCode());
    }

    public void setActiveDeliveryState(int deliveryState) {
        checkState(isActive(), "isActive");
        mActive.setDeliveryState(mActiveIndex, deliveryState);

        // Emit tracing events for the broadcast we're dispatching; the cookie
        // here is unique within the track
        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
        switch (deliveryState) {
            case BroadcastRecord.DELIVERY_SCHEDULED:
                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        traceTrackName, mActive.toShortString() + " scheduled", cookie);
                break;
            case BroadcastRecord.DELIVERY_DELIVERED:
            case BroadcastRecord.DELIVERY_SKIPPED:
            case BroadcastRecord.DELIVERY_TIMEOUT:
            case BroadcastRecord.DELIVERY_FAILURE:
                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        traceTrackName, cookie);
                break;
        }
    }

    public @NonNull BroadcastRecord getActive() {
+73 −21
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -89,7 +90,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    // TODO: add support for replacing pending broadcasts
    // TODO: add support for merging pending broadcasts

    // TODO: add trace points for debugging broadcast flows
    // TODO: record broadcast state change timing statistics
    // TODO: record historical broadcast statistics

@@ -136,11 +136,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private BroadcastProcessQueue mRunnableHead = null;

    /**
     * Collection of queues which are "running". This will never be larger than
     * {@link #MAX_RUNNING_PROCESS_QUEUES}.
     * Array of queues which are currently "running", which may have gaps that
     * are {@code null}.
     *
     * @see #getRunningSize
     * @see #getRunningIndexOf
     */
    @GuardedBy("mService")
    private final ArrayList<BroadcastProcessQueue> mRunning = new ArrayList<>();
    private final BroadcastProcessQueue[] mRunning =
            new BroadcastProcessQueue[MAX_RUNNING_PROCESS_QUEUES];

    /**
     * Single queue which is "running" but is awaiting a cold start to be
@@ -188,6 +192,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        return false;
    };

    /**
     * Return the total number of active queues contained inside
     * {@link #mRunning}.
     */
    private int getRunningSize() {
        int size = 0;
        for (int i = 0; i < mRunning.length; i++) {
            if (mRunning[i] != null) size++;
        }
        return size;
    }

    /**
     * Return the first index of the given value contained inside
     * {@link #mRunning}, otherwise {@code -1}.
     */
    private int getRunningIndexOf(@Nullable BroadcastProcessQueue test) {
        for (int i = 0; i < mRunning.length; i++) {
            if (mRunning[i] == test) return i;
        }
        return -1;
    }

    /**
     * Consider updating the list of "runnable" queues, specifically with
     * relation to the given queue.
@@ -198,7 +225,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
     */
    @GuardedBy("mService")
    private void updateRunnableList(@NonNull BroadcastProcessQueue queue) {
        if (mRunning.contains(queue)) {
        if (getRunningIndexOf(queue) >= 0) {
            // Already running; they'll be reinserted into the runnable list
            // once they finish running, so no need to update them now
            return;
@@ -215,9 +242,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                        ? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true;
                final boolean nextHigher = (queue.runnableAtNext != null)
                        ? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true;
                if (prevLower && nextHigher) {
                    return;
                } else {
                if (!prevLower || !nextHigher) {
                    mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
                    mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
                }
@@ -238,9 +263,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
     */
    @GuardedBy("mService")
    private void updateRunningList() {
        int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
        int avail = MAX_RUNNING_PROCESS_QUEUES - getRunningSize();
        if (avail == 0) return;

        final int cookie = traceBegin(TAG, "updateRunningList");
        final long now = SystemClock.uptimeMillis();

        // If someone is waiting to go idle, everything is runnable now
@@ -285,19 +311,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                    + " from runnable to running; process is " + queue.app);

            // Allocate this available permit and start running!
            mRunning.add(queue);
            final int index = getRunningIndexOf(null);
            mRunning[index] = queue;
            avail--;

            // Remove ourselves from linked list of runnable things
            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);

            queue.makeActiveNextPending();
            // Emit all trace events for this process into a consistent track
            queue.traceTrackName = TAG + ".mRunning[" + index + "]";

            // If we're already warm, schedule it; otherwise we'll wait for the
            // cold start to circle back around
            queue.makeActiveNextPending();
            if (processWarm) {
                queue.traceRunningBegin();
                scheduleReceiverWarmLocked(queue);
            } else {
                queue.traceStartingBegin();
                scheduleReceiverColdLocked(queue);
            }

@@ -316,6 +347,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            mWaitingForIdle.forEach((latch) -> latch.countDown());
            mWaitingForIdle.clear();
        }

        traceEnd(TAG, cookie);
    }

    @Override
@@ -324,9 +357,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
            // We've been waiting for this app to cold start, and it's ready
            // now; dispatch its next broadcast and clear the slot
            scheduleReceiverWarmLocked(mRunningColdStart);
            final BroadcastProcessQueue queue = mRunningColdStart;
            mRunningColdStart = null;

            queue.traceEnd();
            queue.traceRunningBegin();
            scheduleReceiverWarmLocked(queue);

            // We might be willing to kick off another cold start
            enqueueUpdateRunningList();
            didSomething = true;
@@ -374,7 +411,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    @Override
    public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) {
        final BroadcastProcessQueue queue = getProcessQueue(app);
        if ((queue != null) && mRunning.contains(queue)) {
        if ((queue != null) && getRunningIndexOf(queue) >= 0) {
            return queue.getPreferredSchedulingGroupLocked();
        }
        return ProcessList.SCHED_GROUP_UNDEFINED;
@@ -429,6 +466,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {

    private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
        checkState(queue.isActive(), "isActive");
        queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);

        final ProcessRecord app = queue.app;
        final BroadcastRecord r = queue.getActive();
@@ -458,7 +496,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        final IApplicationThread thread = app.getThread();
        if (thread != null) {
            try {
                queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
                if (receiver instanceof BroadcastFilter) {
                    thread.scheduleRegisteredReceiver(
                            ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
@@ -497,7 +534,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
            @DeliveryState int deliveryState) {
        checkState(queue.isActive(), "isActive");

        queue.setActiveDeliveryState(deliveryState);

        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
@@ -529,12 +565,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        } else {
            // We've drained running broadcasts; maybe move back to runnable
            queue.makeActiveIdle();
            mRunning.remove(queue);
            queue.traceEnd();

            final int index = getRunningIndexOf(queue);
            mRunning[index] = null;
            updateRunnableList(queue);
            enqueueUpdateRunningList();

            // App is no longer running a broadcast, so update its OOM
            // adjust during our next pass; no need for an immediate update
            mService.enqueueOomAdjTargetLocked(queue.app);
            updateRunnableList(queue);
            enqueueUpdateRunningList();
            return false;
        }
    }
@@ -569,7 +609,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {

    @Override
    public boolean isIdleLocked() {
        return (mRunnableHead == null) && mRunning.isEmpty();
        return (mRunnableHead == null) && (getRunningSize() == 0);
    }

    @Override
@@ -594,7 +634,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {

    @Override
    public String describeStateLocked() {
        return mRunning.size() + " running";
        return getRunningSize() + " running";
    }

    @Override
@@ -608,6 +648,18 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        // TODO: implement
    }

    private int traceBegin(String trackName, String methodName) {
        final int cookie = methodName.hashCode();
        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                trackName, methodName, cookie);
        return cookie;
    }

    private void traceEnd(String trackName, int cookie) {
        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                trackName, cookie);
    }

    private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
        if (!queue.isProcessWarm()) {
            queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
@@ -703,7 +755,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        ipw.println();
        ipw.println("🏃 Running:");
        ipw.increaseIndent();
        if (mRunning.isEmpty()) {
        if (getRunningSize() == 0) {
            ipw.println("(none)");
        } else {
            for (BroadcastProcessQueue queue : mRunning) {