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

Commit 76fa9734 authored by Mohamad Mahmoud's avatar Mohamad Mahmoud Committed by Android (Google) Code Review
Browse files

Merge changes I0a8b3518,I545e2724 into main

* changes:
  Enable pre-ANR method tracing via AnrTimer split point notifications
  Introduce LongMethodTracer: signal-based method tracing utility
parents 4ceb4847 f7fc099c
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -785,13 +785,17 @@ public final class ActiveServices {
        this.mActiveServiceAnrTimer = new ProcessAnrTimer(service,
                ActivityManagerService.SERVICE_TIMEOUT_MSG,
                "SERVICE_TIMEOUT",
                new AnrTimer.Args());
                new AnrTimer.Args().longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
        this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
                ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
                "SHORT_FGS_TIMEOUT");
                "SHORT_FGS_TIMEOUT",
                new AnrTimer.Args().longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
        this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                "SERVICE_FOREGROUND_TIMEOUT", new AnrTimer.Args().extend(true));
                "SERVICE_FOREGROUND_TIMEOUT",
                new AnrTimer.Args()
                            .extend(true)
                            .longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
    }

    void systemServicesReady() {
+3 −1
Original line number Diff line number Diff line
@@ -1312,7 +1312,9 @@ class BroadcastQueueImpl extends BroadcastQueue {
        BroadcastAnrTimer(@NonNull Handler handler) {
            super(Objects.requireNonNull(handler),
                    MSG_DELIVERY_TIMEOUT, "BROADCAST_TIMEOUT",
                    new AnrTimer.Args().extend(true));
                    new AnrTimer.Args()
                            .extend(true)
                            .longMethodTracing(Flags.enableLongMethodTracingOnAnrTimer()));
        }

        @Override
+8 −0
Original line number Diff line number Diff line
@@ -251,3 +251,11 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "enable_long_method_tracing_on_anr_timer"
    namespace: "stability"
    description: "Enable Long Method Tracing for ANR Timer"
    bug: "419753987"
    is_fixed_read_only: true
}
+127 −27
Original line number Diff line number Diff line
@@ -18,13 +18,14 @@ package com.android.server.utils;

import static android.text.TextUtils.formatSimple;

import static java.util.Comparator.comparingInt;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.os.Trace;
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.CloseGuard;
@@ -39,12 +40,14 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.RingBuffer;

import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.StringJoiner;
import java.util.TreeSet;

/**
 * This class managers AnrTimers.  An AnrTimer is a substitute for a delayed Message.  In legacy
@@ -146,11 +149,28 @@ public class AnrTimer<V> implements AutoCloseable {
    /** The default injector. */
    private static final Injector sDefaultInjector = new Injector();

    /**
     * Token for Long Method Tracing notifications.
     * This token is used in early notifications to trigger long method tracing.
     */
    private static final int TOKEN_LONG_METHOD_TRACING = 0x4d54;

    /** Minimum duration to trace long methods */
    private static final int MIN_LMT_DURATION_MS = 1000;

    /**
     * This class provides build-style arguments to an AnrTimer constructor.  This simplifies the
     * number of AnrTimer constructors needed, especially as new options are added.
     */
    public static class Args {

        /** Represents a point in time (as percent of total) and an associated token. */
        private record SplitPoint(int percent, int token) {}

        /** Split point for long method tracing, at 50% elapsed time. */
        private static final SplitPoint sLongMethodTracingPoint =
                new SplitPoint(50, TOKEN_LONG_METHOD_TRACING);

        /** The Injector (used only for testing). */
        private Injector mInjector = AnrTimer.sDefaultInjector;

@@ -160,6 +180,19 @@ public class AnrTimer<V> implements AutoCloseable {
        /** Grant timer extensions when the system is heavily loaded. */
        private boolean mExtend = false;

        /**
         * All split points, each specifying a percent threshold and an associated token.
         *
         * This set is sorted by percent first so the collection is ordered the way we
         * want, then token second so tow split points with the same percent do not collide.
         *
         * A TreeSet is used to maintain this sorted order, and the uniqueness of split points.
         *
         */
        private final SortedSet<SplitPoint> mSplitPoints =
                new TreeSet<>(comparingInt(SplitPoint::percent)
                        .thenComparingInt(SplitPoint::token));

        // This is only used for testing, so it is limited to package visibility.
        Args injector(@NonNull Injector injector) {
            mInjector = injector;
@@ -175,6 +208,62 @@ public class AnrTimer<V> implements AutoCloseable {
            mExtend = flag;
            return this;
        }

        /**
         * Enables or disables long method tracing.
         * When enabled, the timer will trigger long method tracing if it reaches 50%
         * of its timeout duration.
         *
         * @param enabled {@code true} to enable long method tracing; {@code false} to disable it.
         * @return this {@link Args} instance for chaining.
         */
        public Args longMethodTracing(boolean enabled) {
            final int percent = 50;
            if (enabled) {
                mSplitPoints.add(sLongMethodTracingPoint);
            } else {
                mSplitPoints.remove(sLongMethodTracingPoint);
            }
            return this;

        }

        /**
         * Extracts the percent values from all {@code SplitPoint} objects into an array.
         * <p>
         * This method creates an integer array containing the percent value
         * from each {@code SplitPoint} in the same order they appear in the original list.
         *
         * @return A new {@code int[]} array of all percent values. Never returns null.
         * @see SplitPoint#percent()
         */
        public int[] getSplitPercentArray() {
            int[] percents = new int[mSplitPoints.size()];
            int i = 0;
            for (SplitPoint sp : mSplitPoints) {
                percents[i++] = sp.percent();
            }
            return percents;
        }

        /**
         * Extracts the token values from all {@code SplitPoint} objects into an array.
         * <p>
         * This method creates an integer array containing the token value
         * from each {@code SplitPoint} in the same order they appear in the original list.
         *
         * @return A new {@code int[]} array of all token values. Never returns null.
         * @see SplitPoint#token()
         */
        public int[] getSplitTokenArray() {
            int[] tokens = new int[mSplitPoints.size()];
            int i = 0;
            for (SplitPoint sp : mSplitPoints) {
                tokens[i++] = sp.token();
            }
            return tokens;
        }

    }

    /**
@@ -384,30 +473,15 @@ public class AnrTimer<V> implements AutoCloseable {
    }

    /**
     * Generate a trace point with full timer information.  The meaning of milliseconds depends on
     * the caller.
     * Emit a trace instant event with an arbitrary list of arguments.
     * Arguments are comma-joined and wrapped in <code>op(...)</code>.
     */
    private void trace(String op, int timerId, int pid, int uid, long milliseconds) {
        final String label =
                formatSimple("%s(%d,%d,%d,%s,%d)", op, timerId, pid, uid, mLabel, milliseconds);
        Trace.instantForTrack(TRACE_TAG, TRACK, label);
        if (DEBUG) Log.i(TAG, label);
    }
    private static void trace(String op, Object... args) {
        StringJoiner joiner = new StringJoiner(",", op + "(", ")");

    /**
     * Generate a trace point with just the timer ID.
     */
    private void trace(String op, int timerId) {
        final String label = formatSimple("%s(%d)", op, timerId);
        Trace.instantForTrack(TRACE_TAG, TRACK, label);
        if (DEBUG) Log.i(TAG, label);
    }
        for (Object arg : args) joiner.add(String.valueOf(arg));

    /**
     * Generate a trace point with a pid and uid but no timer ID.
     */
    private static void trace(String op, int pid, int uid) {
        final String label = formatSimple("%s(%d,%d)", op, pid, uid);
        final String label = joiner.toString();
        Trace.instantForTrack(TRACE_TAG, TRACK, label);
        if (DEBUG) Log.i(TAG, label);
    }
@@ -514,7 +588,8 @@ public class AnrTimer<V> implements AutoCloseable {

        /** Create the native AnrTimerService that will host all timers from this instance. */
        FeatureEnabled() {
            mNative = nativeAnrTimerCreate(mLabel, mArgs.mExtend);
            mNative = nativeAnrTimerCreate(mLabel, mArgs.mExtend, mArgs.getSplitPercentArray(),
                    mArgs.getSplitTokenArray());
            if (mNative == 0) throw new IllegalArgumentException("unable to create native timer");
            synchronized (sAnrTimerList) {
                sAnrTimerList.put(mNative, new WeakReference(AnrTimer.this));
@@ -761,7 +836,7 @@ public class AnrTimer<V> implements AutoCloseable {
     */
    @Keep
    private boolean expire(int timerId, int pid, int uid, long elapsedMs) {
        trace("expired", timerId, pid, uid, elapsedMs);
        trace("expired", timerId, pid, uid, mLabel, elapsedMs);
        V arg = null;
        synchronized (mLock) {
            arg = mTimerArgMap.get(timerId);
@@ -777,6 +852,30 @@ public class AnrTimer<V> implements AutoCloseable {
        return true;
    }

    /**
     * Called when a timer reaches an early notification split point.
     * This allows for proactive actions before the timer fully expires.
     *
     * @param timerId the timer ID
     * @param pid pid of the timed operation
     * @param uid uid of the timed operation
     * @param elapsedMs milliseconds elapsed since timer start
     * @param token identifies the type of early notification
     */
    @Keep
    private void notifyEarly(int timerId, int pid, int uid,
                            long elapsedMs, int token) {
        trace("notifyEarly", timerId, pid, uid, mLabel, elapsedMs, token);
        switch(token) {
            case TOKEN_LONG_METHOD_TRACING:
                LongMethodTracer.trigger(pid,
                        (int) Math.max(MIN_LMT_DURATION_MS, elapsedMs * 1.5));
                break;
            default:
                Log.w(TAG, "Received a notification with an unknown token: " + token);
        }
    }

    /**
     * Close the object and free any native resources.
     */
@@ -939,7 +1038,8 @@ public class AnrTimer<V> implements AutoCloseable {
     * Unlike the other methods, this is an instance method: the "this" parameter is passed into
     * the native layer.
     */
    private native long nativeAnrTimerCreate(String name, boolean extend);
    private native long nativeAnrTimerCreate(String name, boolean extend,
            int[] splitPercent, int[] splitToken);

    /** Release the native resources.  No further operations are premitted. */
    private static native int nativeAnrTimerClose(long service);
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.utils;

import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;

import android.os.Trace;
import android.util.Slog;

import com.android.internal.annotations.Keep;

/**
 * Triggers long method tracing in a process for a fixed duration.
 *
 * <p>This uses a native signal-based mechanism to request tracing in the target process. The actual
 * signal and delivery mechanism are abstracted away.
 *
 * <p>The collected tracing information currently appears in the ANR report if the traced process
 * encounters an ANR during or after the tracing window.
 *
 * @hide
 */
public class LongMethodTracer {
    private static final String TAG = "LongMethodTracer";

    /**
     * Requests method tracing in the given process.
     *
     * @param pid The target process ID to trace.
     * @param durationMs The tracing duration in milliseconds.
     * @return true if the request was successfully sent; false otherwise.
     */
    @Keep
    public static boolean trigger(int pid, int durationMs) {
        if (!Flags.longMethodTrace()) {
            return false;
        }

        if (pid <= 0) {
            throw new IllegalArgumentException("Invalid PID: " + pid);
        }
        if (durationMs <= 0) {
            throw new IllegalArgumentException("Duration must be positive: " + durationMs);
        }

        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
                "LongMethodTracer#trigger()");
        Slog.i(TAG, "Triggering long method tracing for pid:" + pid);

        boolean result = nativeTrigger(pid, durationMs);
        if (!result) {
            Slog.w(TAG, "Failed to trigger long method tracing for pid " + pid);
        }
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
        return result;
    }

    private static native boolean nativeTrigger(int pid, int durationMs);

}
Loading