public final class

Trace

extends java.lang.Object

 java.lang.Object

↳androidx.tracing.Trace

Gradle dependencies

compile group: 'androidx.tracing', name: 'tracing', version: '1.1.0'

  • groupId: androidx.tracing
  • artifactId: tracing
  • version: 1.1.0

Artifact androidx.tracing:tracing:1.1.0 it located at Google repository (https://maven.google.com/)

Overview

Writes trace events to the system trace buffer.

These trace events can be collected and visualized using the Android Studio System Trace, Perfetto, and Systrace tools.

Tracing should generally be performed in a non-debuggable app for more accurate measurements, representative of real user experience. In a non-debuggable app, tracing is enabled if a trace is currently being captured, as well as one of the following:

  • Android 12 (API 31) or greater: On by default, unless
    <profileable enabled=false/>
    or
    <profileable shell=false/>
    is set in the manifest.
  • Android 10 or 11 (API 29 or 30):
    <profileable shell=true/>
    is set in the manifest, or Trace.forceEnableAppTracing() has been called
  • JellyBean through Android 11 (API 18 through API 28): Trace.forceEnableAppTracing() has been called

This tracing mechanism is independent of the method tracing mechanism offered by . In particular, it enables tracing of events that occur across multiple processes.

For information see Overview of system tracing.

Summary

Methods
public static voidbeginAsyncSection(java.lang.String methodName, int cookie)

Writes a trace message to indicate that a given section of code has begun.

public static voidbeginSection(java.lang.String label)

Writes a trace message to indicate that a given section of code has begun.

public static voidendAsyncSection(java.lang.String methodName, int cookie)

Writes a trace message to indicate that the current method has ended.

public static voidendSection()

Writes a trace message to indicate that a given section of code has ended.

public static voidforceEnableAppTracing()

Enables the app tracing tag in a non-debuggable process.

public static booleanisEnabled()

Checks whether or not tracing is currently enabled.

public static voidsetCounter(java.lang.String counterName, int counterValue)

Writes trace message to indicate the value of a given counter.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static boolean isEnabled()

Checks whether or not tracing is currently enabled.

This is useful to avoid intermediate string creation for trace sections that require formatting. It is not necessary to guard all Trace method calls as they internally already check this. However it is recommended to use this to prevent creating any temporary objects that would then be passed to those methods to reduce runtime cost when tracing isn't enabled.

Returns:

true if tracing is currently enabled, false otherwise

public static void forceEnableAppTracing()

Enables the app tracing tag in a non-debuggable process. Beginning in Android 12 (API 31), app tracing - custom tracing performed by app code via this class or android.os.Trace - is always enabled in all apps. Prior to this, app tracing was only enabled in debuggable apps (as well as profileable apps, on API 29/30). Calling this method enables the app to record custom trace content without debuggable=true on any platform version that supports tracing. Tracing of non-debuggable apps is highly recommended, to ensure accurate performance measurements. As app tracing is always enabled on Android 12 (API 31) and above, this does nothing after API 31.

public static void beginSection(java.lang.String label)

Writes a trace message to indicate that a given section of code has begun.

This call must be followed by a corresponding call to Trace.endSection() on the same thread.

At this time the vertical bar character '|', newline character '\n', and null character '\0' are used internally by the tracing mechanism. If sectionName contains these characters they will be replaced with a space character in the trace.

Parameters:

label: The name of the code section to appear in the trace.

public static void endSection()

Writes a trace message to indicate that a given section of code has ended.

This call must be preceded by a corresponding call to Trace.beginSection(String). Calling this method will mark the end of the most recently begun section of code, so care must be taken to ensure that beginSection / endSection pairs are properly nested and called from the same thread.

public static void beginAsyncSection(java.lang.String methodName, int cookie)

Writes a trace message to indicate that a given section of code has begun.

Must be followed by a call to Trace.endAsyncSection(String, int) with the same methodName and cookie. Unlike Trace.beginSection(String) and Trace.endSection(), asynchronous events do not need to be nested. The name and cookie used to begin an event must be used to end it. The cookie must be unique to any overlapping events. If events don't overlap, you can simply always pass the same integer (e.g. `0`). If they do overlap, the cookie is used to disambiguate between overlapping events, like the following scenario:

 [==========================]
           [=====================================]
                                      [====]
 
Without unique cookies, these start/stop timestamps could be misinterpreted by the trace display like the following, to show very different ranges:
 [=========================================]
           [================]
                                      [==========]
 

Parameters:

methodName: The method name to appear in the trace.
cookie: Unique identifier for distinguishing simultaneous events with the same methodName

See also: Trace.endAsyncSection(String, int)

public static void endAsyncSection(java.lang.String methodName, int cookie)

Writes a trace message to indicate that the current method has ended.

Must be called exactly once for each call to Trace.beginAsyncSection(String, int) using the same name and cookie.

Parameters:

methodName: The method name to appear in the trace.
cookie: Unique identifier for distinguishing simultaneous events with the same methodName

See also: Trace.beginAsyncSection(String, int)

public static void setCounter(java.lang.String counterName, int counterValue)

Writes trace message to indicate the value of a given counter.

Parameters:

counterName: The counter name to appear in the trace.
counterValue: The counter value.

Source

/*
 * Copyright 2020 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 androidx.tracing;

import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Writes trace events to the system trace buffer.
 *
 * <p>These trace events can be collected and visualized using the Android Studio System
 * Trace, Perfetto, and Systrace tools.
 *
 * <p>Tracing should generally be performed in a non-debuggable app for more accurate
 * measurements, representative of real user experience. In a non-debuggable app, tracing is
 * {@link #isEnabled() enabled} if a trace is currently being captured, as well as one of the
 * following:
 * <ul>
 *   <li>Android 12 (API 31) or greater: On by default, unless
 *     <pre>&lt;profileable enabled=false/&gt;</pre>
 *     or <pre>&lt;profileable shell=false/&gt;</pre> is set in the manifest.</li>
 *   <li>Android 10 or 11 (API 29 or 30): <pre>&lt;profileable shell=true/&gt;</pre> is set in the
 *     manifest, or {@link #forceEnableAppTracing()} has been called</li>
 *   <li>JellyBean through Android 11 (API 18 through API 28): {@link #forceEnableAppTracing()} has
 *     been called</li>
 * </ul>
 *
 * <p>This tracing mechanism is independent of the method tracing mechanism offered by
 * {@link android.os.Debug#startMethodTracing}.  In particular, it enables tracing of events that
 * occur across multiple processes.
 *
 * <p>For information see
 * <a href="{@docRoot}studio/profile/systrace/">Overview of system tracing</a>.
 */
public final class Trace {
    static final String TAG = "Trace";

    private static long sTraceTagApp;
    private static Method sIsTagEnabledMethod;
    private static Method sAsyncTraceBeginMethod;
    private static Method sAsyncTraceEndMethod;
    private static Method sTraceCounterMethod;
    private static boolean sHasAppTracingEnabled;

    /**
     * Checks whether or not tracing is currently enabled.
     *
     * <p>This is useful to avoid intermediate string creation for trace sections that require
     * formatting. It is not necessary to guard all Trace method calls as they internally already
     * check this. However it is recommended to use this to prevent creating any temporary
     * objects that would then be passed to those methods to reduce runtime cost when tracing
     * isn't enabled.
     *
     * @return true if tracing is currently enabled, false otherwise
     */
    public static boolean isEnabled() {
        if (Build.VERSION.SDK_INT >= 29) {
            return TraceApi29Impl.isEnabled();
        }
        return isEnabledFallback();
    }

    /**
     * Enables the app tracing tag in a non-debuggable process.
     *
     * Beginning in Android 12 (API 31), app tracing - custom tracing performed by app code via
     * this class or android.os.Trace - is always enabled in all apps. Prior to this, app tracing
     * was only enabled in debuggable apps (as well as profileable apps, on API 29/30).
     *
     * Calling this method enables the app to record custom trace content without debuggable=true
     * on any platform version that supports tracing. Tracing of non-debuggable apps is highly
     * recommended, to ensure accurate performance measurements.
     *
     * As app tracing is always enabled on Android 12 (API 31) and above, this does nothing after
     * API 31.
     */
    public static void forceEnableAppTracing() {
        if (Build.VERSION.SDK_INT >= 18 && Build.VERSION.SDK_INT < 31) {
            try {
                if (!sHasAppTracingEnabled) {
                    sHasAppTracingEnabled = true; // only attempt once
                    @SuppressWarnings("JavaReflectionMemberAccess")
                    Method setAppTracingAllowed = android.os.Trace.class.getMethod(
                            "setAppTracingAllowed",
                            boolean.class
                    );
                    setAppTracingAllowed.invoke(null, true);
                }
            } catch (Exception exception) {
                handleException("setAppTracingAllowed", exception);
            }
        }
    }

    /**
     * Writes a trace message to indicate that a given section of code has begun.
     *
     * <p>This call must be followed by a corresponding call to {@link #endSection()} on the same
     * thread.
     *
     * <p class="note"> At this time the vertical bar character '|', newline character '\n', and
     * null character '\0' are used internally by the tracing mechanism.  If sectionName contains
     * these characters they will be replaced with a space character in the trace.
     *
     * @param label The name of the code section to appear in the trace.
     */
    public static void beginSection(@NonNull String label) {
        if (Build.VERSION.SDK_INT >= 18) {
            TraceApi18Impl.beginSection(label);
        }
    }

    /**
     * Writes a trace message to indicate that a given section of code has ended.
     *
     * <p>This call must be preceded by a corresponding call to {@link #beginSection(String)}.
     * Calling this method will mark the end of the most recently begun section of code, so care
     * must be taken to ensure that beginSection / endSection pairs are properly nested and
     * called from the same thread.
     */
    public static void endSection() {
        if (Build.VERSION.SDK_INT >= 18) {
            TraceApi18Impl.endSection();
        }
    }

    /**
     * Writes a trace message to indicate that a given section of code has begun.
     *
     * <p>Must be followed by a call to {@link #endAsyncSection(String, int)} with the same
     * methodName and cookie. Unlike {@link #beginSection(String)} and {@link #endSection()},
     * asynchronous events do not need to be nested. The name and cookie used to begin an event
     * must be used to end it.
     *
     * The cookie must be unique to any overlapping events. If events don't overlap, you can
     * simply always pass the same integer (e.g. `0`). If they do overlap, the cookie is used to
     * disambiguate between overlapping events, like the following scenario:
     * <pre>
     * [==========================]
     *           [=====================================]
     *                                      [====]
     * </pre>
     * Without unique cookies, these start/stop timestamps could be misinterpreted by the trace
     * display like the following, to show very different ranges:
     * <pre>
     * [=========================================]
     *           [================]
     *                                      [==========]
     * </pre>
     *
     * @param methodName The method name to appear in the trace.
     * @param cookie     Unique identifier for distinguishing simultaneous events with the same
     *                   methodName
     * @see #endAsyncSection
     */
    public static void beginAsyncSection(@NonNull String methodName, int cookie) {
        if (Build.VERSION.SDK_INT >= 29) {
            TraceApi29Impl.beginAsyncSection(methodName, cookie);
        } else {
            beginAsyncSectionFallback(methodName, cookie);
        }
    }

    /**
     * Writes a trace message to indicate that the current method has ended.
     *
     * <p>Must be called exactly once for each call to {@link #beginAsyncSection(String, int)}
     * using the same name and cookie.
     *
     * @param methodName The method name to appear in the trace.
     * @param cookie     Unique identifier for distinguishing simultaneous events with the same
     *                   methodName
     * @see #beginAsyncSection
     */
    public static void endAsyncSection(@NonNull String methodName, int cookie) {
        if (Build.VERSION.SDK_INT >= 29) {
            TraceApi29Impl.endAsyncSection(methodName, cookie);
        } else {
            endAsyncSectionFallback(methodName, cookie);
        }
    }

    /**
     * Writes trace message to indicate the value of a given counter.
     *
     * @param counterName  The counter name to appear in the trace.
     * @param counterValue The counter value.
     */
    public static void setCounter(@NonNull String counterName, int counterValue) {
        if (Build.VERSION.SDK_INT >= 29) {
            TraceApi29Impl.setCounter(counterName, counterValue);
        } else {
            setCounterFallback(counterName, counterValue);
        }
    }

    @SuppressWarnings({"JavaReflectionMemberAccess", "ConstantConditions"})
    private static boolean isEnabledFallback() {
        if (Build.VERSION.SDK_INT >= 18) {
            try {
                if (sIsTagEnabledMethod == null) {
                    Field traceTagAppField = android.os.Trace.class.getField("TRACE_TAG_APP");
                    sTraceTagApp = traceTagAppField.getLong(null);
                    sIsTagEnabledMethod =
                            android.os.Trace.class.getMethod("isTagEnabled", long.class);
                }
                return (boolean) sIsTagEnabledMethod.invoke(null, sTraceTagApp);
            } catch (Exception exception) {
                handleException("isTagEnabled", exception);
            }
        }
        // Never enabled on < API 18
        return false;
    }

    @SuppressWarnings("JavaReflectionMemberAccess")
    private static void beginAsyncSectionFallback(@NonNull String methodName, int cookie) {
        if (Build.VERSION.SDK_INT >= 18) {
            try {
                if (sAsyncTraceBeginMethod == null) {
                    sAsyncTraceBeginMethod = android.os.Trace.class.getMethod(
                            "asyncTraceBegin",
                            long.class,
                            String.class, int.class
                    );
                }
                sAsyncTraceBeginMethod.invoke(null, sTraceTagApp, methodName, cookie);
            } catch (Exception exception) {
                handleException("asyncTraceBegin", exception);
            }
        }
    }

    @SuppressWarnings("JavaReflectionMemberAccess")
    private static void endAsyncSectionFallback(@NonNull String methodName, int cookie) {
        if (Build.VERSION.SDK_INT >= 18) {
            try {
                if (sAsyncTraceEndMethod == null) {
                    sAsyncTraceEndMethod = android.os.Trace.class.getMethod(
                            "asyncTraceEnd",
                            long.class,
                            String.class, int.class
                    );
                }
                sAsyncTraceEndMethod.invoke(null, sTraceTagApp, methodName, cookie);
            } catch (Exception exception) {
                handleException("asyncTraceEnd", exception);
            }
        }
    }

    @SuppressWarnings("JavaReflectionMemberAccess")
    private static void setCounterFallback(@NonNull String counterName, int counterValue) {
        if (Build.VERSION.SDK_INT >= 18) {
            try {
                if (sTraceCounterMethod == null) {
                    sTraceCounterMethod = android.os.Trace.class.getMethod(
                            "traceCounter",
                            long.class,
                            String.class,
                            int.class
                    );
                }
                sTraceCounterMethod.invoke(null, sTraceTagApp, counterName, counterValue);
            } catch (Exception exception) {
                handleException("traceCounter", exception);
            }
        }
    }

    private static void handleException(@NonNull String methodName, @NonNull Exception exception) {
        if (exception instanceof InvocationTargetException) {
            Throwable cause = exception.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else {
                throw new RuntimeException(cause);
            }
        }
        Log.v(TAG, "Unable to call " + methodName + " via reflection", exception);
    }

    private Trace() {
    }
}