| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base; |
| |
| import android.os.Process; |
| import android.os.StrictMode; |
| import android.os.SystemClock; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.annotations.NativeMethods; |
| import org.chromium.build.annotations.MainDex; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.annotation.concurrent.GuardedBy; |
| |
| /** |
| * Support for early tracing, before the native library is loaded. |
| * |
| * Note that arguments are not currently supported for early events, but could |
| * be added in the future. |
| * |
| * Events recorded here are buffered in Java until the native library is available, at which point |
| * they are flushed to the native side and regular java tracing (TraceEvent) takes over. |
| * |
| * Locking: This class is threadsafe. It is enabled when general tracing is, and then disabled when |
| * tracing is enabled from the native side. At this point, buffered events are flushed to |
| * the native side and then early tracing is permanently disabled after dumping the events. |
| * |
| * Like the TraceEvent, the event name of the trace events must be a string literal or a |static |
| * final String| class member. Otherwise NoDynamicStringsInTraceEventCheck error will be thrown. |
| */ |
| @JNINamespace("base::android") |
| @MainDex |
| public class EarlyTraceEvent { |
| /** Single trace event. */ |
| @VisibleForTesting |
| static final class Event { |
| final boolean mIsStart; |
| final boolean mIsToplevel; |
| final String mName; |
| final int mThreadId; |
| final long mTimeNanos; |
| final long mThreadTimeMillis; |
| |
| Event(String name, boolean isStart, boolean isToplevel) { |
| mIsStart = isStart; |
| mIsToplevel = isToplevel; |
| mName = name; |
| mThreadId = Process.myTid(); |
| mTimeNanos = System.nanoTime(); // Same timebase as TimeTicks::Now(). |
| mThreadTimeMillis = SystemClock.currentThreadTimeMillis(); |
| } |
| } |
| |
| @VisibleForTesting |
| static final class AsyncEvent { |
| final boolean mIsStart; |
| final String mName; |
| final long mId; |
| final long mTimeNanos; |
| |
| AsyncEvent(String name, long id, boolean isStart) { |
| mName = name; |
| mId = id; |
| mIsStart = isStart; |
| mTimeNanos = System.nanoTime(); // Same timebase as TimeTicks::Now(). |
| } |
| } |
| |
| // State transitions are: |
| // - enable(): DISABLED -> ENABLED |
| // - disable(): ENABLED -> FINISHED |
| @VisibleForTesting static final int STATE_DISABLED = 0; |
| @VisibleForTesting static final int STATE_ENABLED = 1; |
| @VisibleForTesting |
| static final int STATE_FINISHED = 2; |
| @VisibleForTesting |
| static volatile int sState = STATE_DISABLED; |
| |
| // In child processes the CommandLine is not available immediately, so early tracing is enabled |
| // unconditionally in Chrome. This flag allows not to enable early tracing twice in this case. |
| private static volatile boolean sEnabledInChildProcessBeforeCommandLine; |
| |
| private static final String BACKGROUND_STARTUP_TRACING_ENABLED_KEY = "bg_startup_tracing"; |
| private static boolean sCachedBackgroundStartupTracingFlag; |
| |
| // Early tracing can be enabled on browser start if the browser finds this file present. Must be |
| // kept in sync with the native kAndroidTraceConfigFile. |
| private static final String TRACE_CONFIG_FILENAME = "/data/local/chrome-trace-config.json"; |
| |
| // Early tracing can be enabled on browser start if the browser finds this command line switch. |
| // Must be kept in sync with switches::kTraceStartup. |
| private static final String TRACE_STARTUP_SWITCH = "trace-startup"; |
| |
| // Added to child process switches if tracing is enabled when the process is getting created. |
| // The flag is checked early in child process lifetime to have a solid guarantee that the early |
| // java tracing is not enabled forever. Native flags cannot be used for this purpose because the |
| // native library is not loaded at the moment. Cannot set --trace-startup for the child to avoid |
| // overriding the list of categories it may load from the config later. Also --trace-startup |
| // depends on other flags that early tracing should not know about. Public for use in |
| // ChildProcessLauncherHelperImpl. |
| public static final String TRACE_EARLY_JAVA_IN_CHILD_SWITCH = "trace-early-java-in-child"; |
| |
| // Protects the fields below. |
| @VisibleForTesting |
| static final Object sLock = new Object(); |
| |
| // Not final because in many configurations these objects are not used. |
| @GuardedBy("sLock") |
| @VisibleForTesting |
| static List<Event> sEvents; |
| @GuardedBy("sLock") |
| @VisibleForTesting |
| static List<AsyncEvent> sAsyncEvents; |
| |
| /** @see TraceEvent#maybeEnableEarlyTracing(long, boolean) */ |
| static void maybeEnableInBrowserProcess() { |
| ThreadUtils.assertOnUiThread(); |
| assert !sEnabledInChildProcessBeforeCommandLine |
| : "Should not have been initialized in a child process"; |
| if (sState != STATE_DISABLED) return; |
| boolean shouldEnable = false; |
| // Checking for the trace config filename touches the disk. |
| StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
| try { |
| if (CommandLine.getInstance().hasSwitch(TRACE_STARTUP_SWITCH)) { |
| shouldEnable = true; |
| } else { |
| try { |
| shouldEnable = new File(TRACE_CONFIG_FILENAME).exists(); |
| } catch (SecurityException e) { |
| // Access denied, not enabled. |
| } |
| } |
| if (ContextUtils.getAppSharedPreferences().getBoolean( |
| BACKGROUND_STARTUP_TRACING_ENABLED_KEY, false)) { |
| if (shouldEnable) { |
| // If user has enabled tracing, then force disable background tracing for this |
| // session. |
| setBackgroundStartupTracingFlag(false); |
| sCachedBackgroundStartupTracingFlag = false; |
| } else { |
| sCachedBackgroundStartupTracingFlag = true; |
| shouldEnable = true; |
| } |
| } |
| } finally { |
| StrictMode.setThreadPolicy(oldPolicy); |
| } |
| if (shouldEnable) enable(); |
| } |
| |
| /** |
| * Enables early tracing in child processes before CommandLine arrives there. |
| */ |
| public static void earlyEnableInChildWithoutCommandLine() { |
| sEnabledInChildProcessBeforeCommandLine = true; |
| assert sState == STATE_DISABLED; |
| enable(); |
| } |
| |
| /** |
| * Based on a command line switch from the process launcher, enables or resets early tracing. |
| * Should be called only in child processes and as soon as possible after the CommandLine is |
| * initialized. |
| */ |
| public static void onCommandLineAvailableInChildProcess() { |
| // Ignore early Java tracing in WebView and other startup configurations that did not start |
| // collecting events before the command line was available. |
| if (!sEnabledInChildProcessBeforeCommandLine) return; |
| synchronized (sLock) { |
| // Remove early trace events if the child process launcher did not ask for early |
| // tracing. |
| if (!CommandLine.getInstance().hasSwitch(TRACE_EARLY_JAVA_IN_CHILD_SWITCH)) { |
| reset(); |
| return; |
| } |
| // Otherwise continue with tracing enabled. |
| if (sState == STATE_DISABLED) enable(); |
| } |
| } |
| |
| static void enable() { |
| synchronized (sLock) { |
| if (sState != STATE_DISABLED) return; |
| sEvents = new ArrayList<Event>(); |
| sAsyncEvents = new ArrayList<AsyncEvent>(); |
| sState = STATE_ENABLED; |
| } |
| } |
| |
| /** |
| * Disables Early tracing and flushes buffered events to the native side. |
| * |
| * Once this is called, no new event will be registered. |
| */ |
| static void disable() { |
| synchronized (sLock) { |
| if (!enabled()) return; |
| |
| if (!sEvents.isEmpty()) { |
| dumpEvents(sEvents); |
| sEvents.clear(); |
| } |
| if (!sAsyncEvents.isEmpty()) { |
| dumpAsyncEvents(sAsyncEvents); |
| sAsyncEvents.clear(); |
| } |
| |
| sState = STATE_FINISHED; |
| sEvents = null; |
| sAsyncEvents = null; |
| } |
| } |
| |
| /** |
| * Stops early tracing without flushing the buffered events. |
| */ |
| @VisibleForTesting |
| static void reset() { |
| synchronized (sLock) { |
| sState = STATE_DISABLED; |
| sEvents = null; |
| sAsyncEvents = null; |
| } |
| } |
| |
| static boolean enabled() { |
| return sState == STATE_ENABLED; |
| } |
| |
| /** |
| * Sets the background startup tracing enabled in app preferences for next startup. |
| */ |
| @CalledByNative |
| static void setBackgroundStartupTracingFlag(boolean enabled) { |
| ContextUtils.getAppSharedPreferences() |
| .edit() |
| .putBoolean(BACKGROUND_STARTUP_TRACING_ENABLED_KEY, enabled) |
| .apply(); |
| } |
| |
| /** |
| * Returns true if the background startup tracing flag is set. |
| * |
| * This does not return the correct value if called before maybeEnable() was called. But that is |
| * called really early in startup. |
| */ |
| @CalledByNative |
| public static boolean getBackgroundStartupTracingFlag() { |
| return sCachedBackgroundStartupTracingFlag; |
| } |
| |
| /** @see TraceEvent#begin */ |
| public static void begin(String name, boolean isToplevel) { |
| // begin() and end() are going to be called once per TraceEvent, this avoids entering a |
| // synchronized block at each and every call. |
| if (!enabled()) return; |
| Event event = new Event(name, true /*isStart*/, isToplevel); |
| synchronized (sLock) { |
| if (!enabled()) return; |
| sEvents.add(event); |
| } |
| } |
| |
| /** @see TraceEvent#end */ |
| public static void end(String name, boolean isToplevel) { |
| if (!enabled()) return; |
| Event event = new Event(name, false /*isStart*/, isToplevel); |
| synchronized (sLock) { |
| if (!enabled()) return; |
| sEvents.add(event); |
| } |
| } |
| |
| /** @see TraceEvent#startAsync */ |
| public static void startAsync(String name, long id) { |
| if (!enabled()) return; |
| AsyncEvent event = new AsyncEvent(name, id, true /*isStart*/); |
| synchronized (sLock) { |
| if (!enabled()) return; |
| sAsyncEvents.add(event); |
| } |
| } |
| |
| /** @see TraceEvent#finishAsync */ |
| public static void finishAsync(String name, long id) { |
| if (!enabled()) return; |
| AsyncEvent event = new AsyncEvent(name, id, false /*isStart*/); |
| synchronized (sLock) { |
| if (!enabled()) return; |
| sAsyncEvents.add(event); |
| } |
| } |
| |
| @VisibleForTesting |
| static List<Event> getMatchingCompletedEventsForTesting(String eventName) { |
| synchronized (sLock) { |
| List<Event> matchingEvents = new ArrayList<Event>(); |
| for (Event evt : EarlyTraceEvent.sEvents) { |
| if (evt.mName.equals(eventName)) { |
| matchingEvents.add(evt); |
| } |
| } |
| return matchingEvents; |
| } |
| } |
| |
| private static void dumpEvents(List<Event> events) { |
| for (Event e : events) { |
| if (e.mIsStart) { |
| if (e.mIsToplevel) { |
| EarlyTraceEventJni.get().recordEarlyToplevelBeginEvent( |
| e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis); |
| } else { |
| EarlyTraceEventJni.get().recordEarlyBeginEvent( |
| e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis); |
| } |
| } else { |
| if (e.mIsToplevel) { |
| EarlyTraceEventJni.get().recordEarlyToplevelEndEvent( |
| e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis); |
| } else { |
| EarlyTraceEventJni.get().recordEarlyEndEvent( |
| e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis); |
| } |
| } |
| } |
| } |
| |
| private static void dumpAsyncEvents(List<AsyncEvent> events) { |
| for (AsyncEvent e : events) { |
| if (e.mIsStart) { |
| EarlyTraceEventJni.get().recordEarlyAsyncBeginEvent(e.mName, e.mId, e.mTimeNanos); |
| } else { |
| EarlyTraceEventJni.get().recordEarlyAsyncEndEvent(e.mName, e.mId, e.mTimeNanos); |
| } |
| } |
| } |
| |
| @NativeMethods |
| interface Natives { |
| void recordEarlyBeginEvent(String name, long timeNanos, int threadId, long threadMillis); |
| void recordEarlyEndEvent(String name, long timeNanos, int threadId, long threadMillis); |
| void recordEarlyToplevelBeginEvent( |
| String name, long timeNanos, int threadId, long threadMillis); |
| void recordEarlyToplevelEndEvent( |
| String name, long timeNanos, int threadId, long threadMillis); |
| void recordEarlyAsyncBeginEvent(String name, long id, long timeNanos); |
| void recordEarlyAsyncEndEvent(String name, long id, long timeNanos); |
| } |
| } |