• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base;
6 
7 import android.os.Process;
8 import android.os.StrictMode;
9 import android.os.SystemClock;
10 
11 import androidx.annotation.VisibleForTesting;
12 
13 import org.chromium.base.annotations.CalledByNative;
14 import org.chromium.base.annotations.JNINamespace;
15 import org.chromium.base.annotations.NativeMethods;
16 import org.chromium.build.annotations.MainDex;
17 
18 import java.io.File;
19 import java.util.ArrayList;
20 import java.util.List;
21 
22 import javax.annotation.concurrent.GuardedBy;
23 
24 /**
25  * Support for early tracing, before the native library is loaded.
26  *
27  * Note that arguments are not currently supported for early events, but could
28  * be added in the future.
29  *
30  * Events recorded here are buffered in Java until the native library is available, at which point
31  * they are flushed to the native side and regular java tracing (TraceEvent) takes over.
32  *
33  * Locking: This class is threadsafe. It is enabled when general tracing is, and then disabled when
34  *          tracing is enabled from the native side. At this point, buffered events are flushed to
35  *          the native side and then early tracing is permanently disabled after dumping the events.
36  *
37  * Like the TraceEvent, the event name of the trace events must be a string literal or a |static
38  * final String| class member. Otherwise NoDynamicStringsInTraceEventCheck error will be thrown.
39  */
40 @JNINamespace("base::android")
41 @MainDex
42 public class EarlyTraceEvent {
43     /** Single trace event. */
44     @VisibleForTesting
45     static final class Event {
46         final boolean mIsStart;
47         final boolean mIsToplevel;
48         final String mName;
49         final int mThreadId;
50         final long mTimeNanos;
51         final long mThreadTimeMillis;
52 
Event(String name, boolean isStart, boolean isToplevel)53         Event(String name, boolean isStart, boolean isToplevel) {
54             mIsStart = isStart;
55             mIsToplevel = isToplevel;
56             mName = name;
57             mThreadId = Process.myTid();
58             mTimeNanos = System.nanoTime(); // Same timebase as TimeTicks::Now().
59             mThreadTimeMillis = SystemClock.currentThreadTimeMillis();
60         }
61     }
62 
63     @VisibleForTesting
64     static final class AsyncEvent {
65         final boolean mIsStart;
66         final String mName;
67         final long mId;
68         final long mTimeNanos;
69 
AsyncEvent(String name, long id, boolean isStart)70         AsyncEvent(String name, long id, boolean isStart) {
71             mName = name;
72             mId = id;
73             mIsStart = isStart;
74             mTimeNanos = System.nanoTime(); // Same timebase as TimeTicks::Now().
75         }
76     }
77 
78     // State transitions are:
79     // - enable(): DISABLED -> ENABLED
80     // - disable(): ENABLED -> FINISHED
81     @VisibleForTesting static final int STATE_DISABLED = 0;
82     @VisibleForTesting static final int STATE_ENABLED = 1;
83     @VisibleForTesting
84     static final int STATE_FINISHED = 2;
85     @VisibleForTesting
86     static volatile int sState = STATE_DISABLED;
87 
88     // In child processes the CommandLine is not available immediately, so early tracing is enabled
89     // unconditionally in Chrome. This flag allows not to enable early tracing twice in this case.
90     private static volatile boolean sEnabledInChildProcessBeforeCommandLine;
91 
92     private static final String BACKGROUND_STARTUP_TRACING_ENABLED_KEY = "bg_startup_tracing";
93     private static boolean sCachedBackgroundStartupTracingFlag;
94 
95     // Early tracing can be enabled on browser start if the browser finds this file present. Must be
96     // kept in sync with the native kAndroidTraceConfigFile.
97     private static final String TRACE_CONFIG_FILENAME = "/data/local/chrome-trace-config.json";
98 
99     // Early tracing can be enabled on browser start if the browser finds this command line switch.
100     // Must be kept in sync with switches::kTraceStartup.
101     private static final String TRACE_STARTUP_SWITCH = "trace-startup";
102 
103     // Added to child process switches if tracing is enabled when the process is getting created.
104     // The flag is checked early in child process lifetime to have a solid guarantee that the early
105     // java tracing is not enabled forever. Native flags cannot be used for this purpose because the
106     // native library is not loaded at the moment. Cannot set --trace-startup for the child to avoid
107     // overriding the list of categories it may load from the config later. Also --trace-startup
108     // depends on other flags that early tracing should not know about. Public for use in
109     // ChildProcessLauncherHelperImpl.
110     public static final String TRACE_EARLY_JAVA_IN_CHILD_SWITCH = "trace-early-java-in-child";
111 
112     // Protects the fields below.
113     @VisibleForTesting
114     static final Object sLock = new Object();
115 
116     // Not final because in many configurations these objects are not used.
117     @GuardedBy("sLock")
118     @VisibleForTesting
119     static List<Event> sEvents;
120     @GuardedBy("sLock")
121     @VisibleForTesting
122     static List<AsyncEvent> sAsyncEvents;
123 
124     /** @see TraceEvent#maybeEnableEarlyTracing(boolean) */
maybeEnableInBrowserProcess()125     static void maybeEnableInBrowserProcess() {
126         ThreadUtils.assertOnUiThread();
127         assert !sEnabledInChildProcessBeforeCommandLine
128             : "Should not have been initialized in a child process";
129         if (sState != STATE_DISABLED) return;
130         boolean shouldEnable = false;
131         // Checking for the trace config filename touches the disk.
132         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
133         try {
134             if (CommandLine.getInstance().hasSwitch(TRACE_STARTUP_SWITCH)) {
135                 shouldEnable = true;
136             } else {
137                 try {
138                     shouldEnable = new File(TRACE_CONFIG_FILENAME).exists();
139                 } catch (SecurityException e) {
140                     // Access denied, not enabled.
141                 }
142             }
143             if (ContextUtils.getAppSharedPreferences().getBoolean(
144                         BACKGROUND_STARTUP_TRACING_ENABLED_KEY, false)) {
145                 if (shouldEnable) {
146                     // If user has enabled tracing, then force disable background tracing for this
147                     // session.
148                     setBackgroundStartupTracingFlag(false);
149                     sCachedBackgroundStartupTracingFlag = false;
150                 } else {
151                     sCachedBackgroundStartupTracingFlag = true;
152                     shouldEnable = true;
153                 }
154             }
155         } finally {
156             StrictMode.setThreadPolicy(oldPolicy);
157         }
158         if (shouldEnable) enable();
159     }
160 
161     /**
162      * Enables early tracing in child processes before CommandLine arrives there.
163      */
earlyEnableInChildWithoutCommandLine()164     public static void earlyEnableInChildWithoutCommandLine() {
165         sEnabledInChildProcessBeforeCommandLine = true;
166         assert sState == STATE_DISABLED;
167         enable();
168     }
169 
170     /**
171      * Based on a command line switch from the process launcher, enables or resets early tracing.
172      * Should be called only in child processes and as soon as possible after the CommandLine is
173      * initialized.
174      */
onCommandLineAvailableInChildProcess()175     public static void onCommandLineAvailableInChildProcess() {
176         // Ignore early Java tracing in WebView and other startup configurations that did not start
177         // collecting events before the command line was available.
178         if (!sEnabledInChildProcessBeforeCommandLine) return;
179         synchronized (sLock) {
180             // Remove early trace events if the child process launcher did not ask for early
181             // tracing.
182             if (!CommandLine.getInstance().hasSwitch(TRACE_EARLY_JAVA_IN_CHILD_SWITCH)) {
183                 reset();
184                 return;
185             }
186             // Otherwise continue with tracing enabled.
187             if (sState == STATE_DISABLED) enable();
188         }
189     }
190 
enable()191     static void enable() {
192         synchronized (sLock) {
193             if (sState != STATE_DISABLED) return;
194             sEvents = new ArrayList<Event>();
195             sAsyncEvents = new ArrayList<AsyncEvent>();
196             sState = STATE_ENABLED;
197         }
198     }
199 
200     /**
201      * Disables Early tracing and flushes buffered events to the native side.
202      *
203      * Once this is called, no new event will be registered.
204      */
disable()205     static void disable() {
206         synchronized (sLock) {
207             if (!enabled()) return;
208 
209             if (!sEvents.isEmpty()) {
210                 dumpEvents(sEvents);
211                 sEvents.clear();
212             }
213             if (!sAsyncEvents.isEmpty()) {
214                 dumpAsyncEvents(sAsyncEvents);
215                 sAsyncEvents.clear();
216             }
217 
218             sState = STATE_FINISHED;
219             sEvents = null;
220             sAsyncEvents = null;
221         }
222     }
223 
224     /**
225      * Stops early tracing without flushing the buffered events.
226      */
227     @VisibleForTesting
reset()228     static void reset() {
229         synchronized (sLock) {
230             sState = STATE_DISABLED;
231             sEvents = null;
232             sAsyncEvents = null;
233         }
234     }
235 
enabled()236     static boolean enabled() {
237         return sState == STATE_ENABLED;
238     }
239 
240     /**
241      * Sets the background startup tracing enabled in app preferences for next startup.
242      */
243     @CalledByNative
setBackgroundStartupTracingFlag(boolean enabled)244     static void setBackgroundStartupTracingFlag(boolean enabled) {
245         ContextUtils.getAppSharedPreferences()
246                 .edit()
247                 .putBoolean(BACKGROUND_STARTUP_TRACING_ENABLED_KEY, enabled)
248                 .apply();
249     }
250 
251     /**
252      * Returns true if the background startup tracing flag is set.
253      *
254      * This does not return the correct value if called before maybeEnable() was called. But that is
255      * called really early in startup.
256      */
257     @CalledByNative
getBackgroundStartupTracingFlag()258     public static boolean getBackgroundStartupTracingFlag() {
259         return sCachedBackgroundStartupTracingFlag;
260     }
261 
262     /** @see TraceEvent#begin */
begin(String name, boolean isToplevel)263     public static void begin(String name, boolean isToplevel) {
264         // begin() and end() are going to be called once per TraceEvent, this avoids entering a
265         // synchronized block at each and every call.
266         if (!enabled()) return;
267         Event event = new Event(name, true /*isStart*/, isToplevel);
268         synchronized (sLock) {
269             if (!enabled()) return;
270             sEvents.add(event);
271         }
272     }
273 
274     /** @see TraceEvent#end */
end(String name, boolean isToplevel)275     public static void end(String name, boolean isToplevel) {
276         if (!enabled()) return;
277         Event event = new Event(name, false /*isStart*/, isToplevel);
278         synchronized (sLock) {
279             if (!enabled()) return;
280             sEvents.add(event);
281         }
282     }
283 
284     /** @see TraceEvent#startAsync */
startAsync(String name, long id)285     public static void startAsync(String name, long id) {
286         if (!enabled()) return;
287         AsyncEvent event = new AsyncEvent(name, id, true /*isStart*/);
288         synchronized (sLock) {
289             if (!enabled()) return;
290             sAsyncEvents.add(event);
291         }
292     }
293 
294     /** @see TraceEvent#finishAsync */
finishAsync(String name, long id)295     public static void finishAsync(String name, long id) {
296         if (!enabled()) return;
297         AsyncEvent event = new AsyncEvent(name, id, false /*isStart*/);
298         synchronized (sLock) {
299             if (!enabled()) return;
300             sAsyncEvents.add(event);
301         }
302     }
303 
304     @VisibleForTesting
getMatchingCompletedEventsForTesting(String eventName)305     static List<Event> getMatchingCompletedEventsForTesting(String eventName) {
306         synchronized (sLock) {
307             List<Event> matchingEvents = new ArrayList<Event>();
308             for (Event evt : EarlyTraceEvent.sEvents) {
309                 if (evt.mName.equals(eventName)) {
310                     matchingEvents.add(evt);
311                 }
312             }
313             return matchingEvents;
314         }
315     }
316 
dumpEvents(List<Event> events)317     private static void dumpEvents(List<Event> events) {
318         for (Event e : events) {
319             if (e.mIsStart) {
320                 if (e.mIsToplevel) {
321                     EarlyTraceEventJni.get().recordEarlyToplevelBeginEvent(
322                             e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis);
323                 } else {
324                     EarlyTraceEventJni.get().recordEarlyBeginEvent(
325                             e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis);
326                 }
327             } else {
328                 if (e.mIsToplevel) {
329                     EarlyTraceEventJni.get().recordEarlyToplevelEndEvent(
330                             e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis);
331                 } else {
332                     EarlyTraceEventJni.get().recordEarlyEndEvent(
333                             e.mName, e.mTimeNanos, e.mThreadId, e.mThreadTimeMillis);
334                 }
335             }
336         }
337     }
338 
dumpAsyncEvents(List<AsyncEvent> events)339     private static void dumpAsyncEvents(List<AsyncEvent> events) {
340         for (AsyncEvent e : events) {
341             if (e.mIsStart) {
342                 EarlyTraceEventJni.get().recordEarlyAsyncBeginEvent(e.mName, e.mId, e.mTimeNanos);
343             } else {
344                 EarlyTraceEventJni.get().recordEarlyAsyncEndEvent(e.mId, e.mTimeNanos);
345             }
346         }
347     }
348 
349     @NativeMethods
350     interface Natives {
recordEarlyBeginEvent(String name, long timeNanos, int threadId, long threadMillis)351         void recordEarlyBeginEvent(String name, long timeNanos, int threadId, long threadMillis);
recordEarlyEndEvent(String name, long timeNanos, int threadId, long threadMillis)352         void recordEarlyEndEvent(String name, long timeNanos, int threadId, long threadMillis);
recordEarlyToplevelBeginEvent( String name, long timeNanos, int threadId, long threadMillis)353         void recordEarlyToplevelBeginEvent(
354                 String name, long timeNanos, int threadId, long threadMillis);
recordEarlyToplevelEndEvent( String name, long timeNanos, int threadId, long threadMillis)355         void recordEarlyToplevelEndEvent(
356                 String name, long timeNanos, int threadId, long threadMillis);
recordEarlyAsyncBeginEvent(String name, long id, long timeNanos)357         void recordEarlyAsyncBeginEvent(String name, long id, long timeNanos);
recordEarlyAsyncEndEvent(long id, long timeNanos)358         void recordEarlyAsyncEndEvent(long id, long timeNanos);
359     }
360 }
361