• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
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.Looper;
8 import android.os.MessageQueue;
9 import android.os.SystemClock;
10 import android.util.Log;
11 import android.util.Printer;
12 
13 import org.chromium.base.annotations.CalledByNative;
14 import org.chromium.base.annotations.JNINamespace;
15 import org.chromium.base.annotations.MainDex;
16 /**
17  * Java mirror of Chrome trace event API. See base/trace_event/trace_event.h.
18  *
19  * To get scoped trace events, use the "try with resource" construct, for instance:
20  * <pre>{@code
21  * try (TraceEvent e = TraceEvent.scoped("MyTraceEvent")) {
22  *   // code.
23  * }
24  * }</pre>
25  *
26  * It is OK to use tracing before the native library has loaded, in a slightly restricted fashion.
27  * @see EarlyTraceEvent for details.
28  */
29 @JNINamespace("base::android")
30 @MainDex
31 public class TraceEvent implements AutoCloseable {
32     private static volatile boolean sEnabled;
33     private static volatile boolean sATraceEnabled; // True when taking an Android systrace.
34 
35     private static class BasicLooperMonitor implements Printer {
36         private static final String EARLY_TOPLEVEL_TASK_NAME = "Looper.dispatchMessage: ";
37 
38         @Override
println(final String line)39         public void println(final String line) {
40             if (line.startsWith(">")) {
41                 beginHandling(line);
42             } else {
43                 assert line.startsWith("<");
44                 endHandling(line);
45             }
46         }
47 
beginHandling(final String line)48         void beginHandling(final String line) {
49             // May return an out-of-date value. this is not an issue as EarlyTraceEvent#begin()
50             // will filter the event in this case.
51             boolean earlyTracingActive = EarlyTraceEvent.isActive();
52             if (sEnabled || earlyTracingActive) {
53                 String target = getTarget(line);
54                 if (sEnabled) {
55                     nativeBeginToplevel(target);
56                 } else if (earlyTracingActive) {
57                     // Synthesize a task name instead of using a parameter, as early tracing doesn't
58                     // support parameters.
59                     EarlyTraceEvent.begin(EARLY_TOPLEVEL_TASK_NAME + target);
60                 }
61             }
62         }
63 
endHandling(final String line)64         void endHandling(final String line) {
65             if (EarlyTraceEvent.isActive()) {
66                 EarlyTraceEvent.end(EARLY_TOPLEVEL_TASK_NAME + getTarget(line));
67             }
68             if (sEnabled) nativeEndToplevel();
69         }
70 
71         /**
72          * Android Looper formats |line| as ">>>>> Dispatching to (TARGET) [...]" since at least
73          * 2009 (Donut). Extracts the TARGET part of the message.
74          */
getTarget(String logLine)75         private static String getTarget(String logLine) {
76             int start = logLine.indexOf('(', 21); // strlen(">>>>> Dispatching to ")
77             int end = start == -1 ? -1 : logLine.indexOf(')', start);
78             return end != -1 ? logLine.substring(start + 1, end) : "";
79         }
80     }
81 
82     /**
83      * A class that records, traces and logs statistics about the UI thead's Looper.
84      * The output of this class can be used in a number of interesting ways:
85      * <p>
86      * <ol><li>
87      * When using chrometrace, there will be a near-continuous line of
88      * measurements showing both event dispatches as well as idles;
89      * </li><li>
90      * Logging messages are output for events that run too long on the
91      * event dispatcher, making it easy to identify problematic areas;
92      * </li><li>
93      * Statistics are output whenever there is an idle after a non-trivial
94      * amount of activity, allowing information to be gathered about task
95      * density and execution cadence on the Looper;
96      * </li></ol>
97      * <p>
98      * The class attaches itself as an idle handler to the main Looper, and
99      * monitors the execution of events and idle notifications. Task counters
100      * accumulate between idle notifications and get reset when a new idle
101      * notification is received.
102      */
103     private static final class IdleTracingLooperMonitor extends BasicLooperMonitor
104             implements MessageQueue.IdleHandler {
105         // Tags for dumping to logcat or TraceEvent
106         private static final String TAG = "TraceEvent.LooperMonitor";
107         private static final String IDLE_EVENT_NAME = "Looper.queueIdle";
108 
109         // Calculation constants
110         private static final long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS
111         // A reasonable threshold for defining a Looper event as "long running"
112         private static final long MIN_INTERESTING_DURATION_MILLIS =
113                 FRAME_DURATION_MILLIS;
114         // A reasonable threshold for a "burst" of tasks on the Looper
115         private static final long MIN_INTERESTING_BURST_DURATION_MILLIS =
116                 MIN_INTERESTING_DURATION_MILLIS * 3;
117 
118         // Stats tracking
119         private long mLastIdleStartedAt;
120         private long mLastWorkStartedAt;
121         private int mNumTasksSeen;
122         private int mNumIdlesSeen;
123         private int mNumTasksSinceLastIdle;
124 
125         // State
126         private boolean mIdleMonitorAttached;
127 
128         // Called from within the begin/end methods only.
129         // This method can only execute on the looper thread, because that is
130         // the only thread that is permitted to call Looper.myqueue().
syncIdleMonitoring()131         private final void syncIdleMonitoring() {
132             if (sEnabled && !mIdleMonitorAttached) {
133                 // approximate start time for computational purposes
134                 mLastIdleStartedAt = SystemClock.elapsedRealtime();
135                 Looper.myQueue().addIdleHandler(this);
136                 mIdleMonitorAttached = true;
137                 Log.v(TAG, "attached idle handler");
138             } else if (mIdleMonitorAttached && !sEnabled) {
139                 Looper.myQueue().removeIdleHandler(this);
140                 mIdleMonitorAttached = false;
141                 Log.v(TAG, "detached idle handler");
142             }
143         }
144 
145         @Override
beginHandling(final String line)146         final void beginHandling(final String line) {
147             // Close-out any prior 'idle' period before starting new task.
148             if (mNumTasksSinceLastIdle == 0) {
149                 TraceEvent.end(IDLE_EVENT_NAME);
150             }
151             mLastWorkStartedAt = SystemClock.elapsedRealtime();
152             syncIdleMonitoring();
153             super.beginHandling(line);
154         }
155 
156         @Override
endHandling(final String line)157         final void endHandling(final String line) {
158             final long elapsed = SystemClock.elapsedRealtime()
159                     - mLastWorkStartedAt;
160             if (elapsed > MIN_INTERESTING_DURATION_MILLIS) {
161                 traceAndLog(Log.WARN, "observed a task that took "
162                         + elapsed + "ms: " + line);
163             }
164             super.endHandling(line);
165             syncIdleMonitoring();
166             mNumTasksSeen++;
167             mNumTasksSinceLastIdle++;
168         }
169 
traceAndLog(int level, String message)170         private static void traceAndLog(int level, String message) {
171             TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message);
172             Log.println(level, TAG, message);
173         }
174 
175         @Override
queueIdle()176         public final boolean queueIdle() {
177             final long now =  SystemClock.elapsedRealtime();
178             if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now;
179             final long elapsed = now - mLastIdleStartedAt;
180             mNumIdlesSeen++;
181             TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks since last idle.");
182             if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) {
183                 // Dump stats
184                 String statsString = mNumTasksSeen + " tasks and "
185                         + mNumIdlesSeen + " idles processed so far, "
186                         + mNumTasksSinceLastIdle + " tasks bursted and "
187                         + elapsed + "ms elapsed since last idle";
188                 traceAndLog(Log.DEBUG, statsString);
189             }
190             mLastIdleStartedAt = now;
191             mNumTasksSinceLastIdle = 0;
192             return true; // stay installed
193         }
194     }
195 
196     // Holder for monitor avoids unnecessary construction on non-debug runs
197     private static final class LooperMonitorHolder {
198         private static final BasicLooperMonitor sInstance =
199                 CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_IDLE_TRACING)
200                 ? new IdleTracingLooperMonitor() : new BasicLooperMonitor();
201     }
202 
203     private final String mName;
204 
205     /**
206      * Constructor used to support the "try with resource" construct.
207      */
TraceEvent(String name, String arg)208     private TraceEvent(String name, String arg) {
209         mName = name;
210         begin(name, arg);
211     }
212 
213     @Override
close()214     public void close() {
215         end(mName);
216     }
217 
218     /**
219      * Factory used to support the "try with resource" construct.
220      *
221      * Note that if tracing is not enabled, this will not result in allocating an object.
222      *
223      * @param name Trace event name.
224      * @param name The arguments of the event.
225      * @return a TraceEvent, or null if tracing is not enabled.
226      */
scoped(String name, String arg)227     public static TraceEvent scoped(String name, String arg) {
228         if (!(EarlyTraceEvent.enabled() || enabled())) return null;
229         return new TraceEvent(name, arg);
230     }
231 
232     /**
233      * Similar to {@link #scoped(String, String arg)}, but uses null for |arg|.
234      */
scoped(String name)235     public static TraceEvent scoped(String name) {
236         return scoped(name, null);
237     }
238 
239     /**
240      * Register an enabled observer, such that java traces are always enabled with native.
241      */
registerNativeEnabledObserver()242     public static void registerNativeEnabledObserver() {
243         nativeRegisterEnabledObserver();
244     }
245 
246     /**
247      * Notification from native that tracing is enabled/disabled.
248      */
249     @CalledByNative
setEnabled(boolean enabled)250     public static void setEnabled(boolean enabled) {
251         if (enabled) EarlyTraceEvent.disable();
252         // Only disable logging if Chromium enabled it originally, so as to not disrupt logging done
253         // by other applications
254         if (sEnabled != enabled) {
255             sEnabled = enabled;
256             // Android M+ systrace logs this on its own. Only log it if not writing to Android
257             // systrace.
258             if (sATraceEnabled) return;
259             ThreadUtils.getUiThreadLooper().setMessageLogging(
260                     enabled ? LooperMonitorHolder.sInstance : null);
261         }
262     }
263 
264     /**
265      * May enable early tracing depending on the environment.
266      *
267      * Must be called after the command-line has been read.
268      */
maybeEnableEarlyTracing()269     public static void maybeEnableEarlyTracing() {
270         EarlyTraceEvent.maybeEnable();
271         if (EarlyTraceEvent.isActive()) {
272             ThreadUtils.getUiThreadLooper().setMessageLogging(LooperMonitorHolder.sInstance);
273         }
274     }
275 
276     /**
277      * Enables or disabled Android systrace path of Chrome tracing. If enabled, all Chrome
278      * traces will be also output to Android systrace. Because of the overhead of Android
279      * systrace, this is for WebView only.
280      */
setATraceEnabled(boolean enabled)281     public static void setATraceEnabled(boolean enabled) {
282         if (sATraceEnabled == enabled) return;
283         sATraceEnabled = enabled;
284         if (enabled) {
285             // Calls TraceEvent.setEnabled(true) via
286             // TraceLog::EnabledStateObserver::OnTraceLogEnabled
287             nativeStartATrace();
288         } else {
289             // Calls TraceEvent.setEnabled(false) via
290             // TraceLog::EnabledStateObserver::OnTraceLogDisabled
291             nativeStopATrace();
292         }
293     }
294 
295     /**
296      * @return True if tracing is enabled, false otherwise.
297      * It is safe to call trace methods without checking if TraceEvent
298      * is enabled.
299      */
enabled()300     public static boolean enabled() {
301         return sEnabled;
302     }
303 
304     /**
305      * Triggers the 'instant' native trace event with no arguments.
306      * @param name The name of the event.
307      */
instant(String name)308     public static void instant(String name) {
309         if (sEnabled) nativeInstant(name, null);
310     }
311 
312     /**
313      * Triggers the 'instant' native trace event.
314      * @param name The name of the event.
315      * @param arg  The arguments of the event.
316      */
instant(String name, String arg)317     public static void instant(String name, String arg) {
318         if (sEnabled) nativeInstant(name, arg);
319     }
320 
321     /**
322      * Triggers the 'start' native trace event with no arguments.
323      * @param name The name of the event.
324      * @param id   The id of the asynchronous event.
325      */
startAsync(String name, long id)326     public static void startAsync(String name, long id) {
327         EarlyTraceEvent.startAsync(name, id);
328         if (sEnabled) nativeStartAsync(name, id);
329     }
330 
331     /**
332      * Triggers the 'finish' native trace event with no arguments.
333      * @param name The name of the event.
334      * @param id   The id of the asynchronous event.
335      */
finishAsync(String name, long id)336     public static void finishAsync(String name, long id) {
337         EarlyTraceEvent.finishAsync(name, id);
338         if (sEnabled) nativeFinishAsync(name, id);
339     }
340 
341     /**
342      * Triggers the 'begin' native trace event with no arguments.
343      * @param name The name of the event.
344      */
begin(String name)345     public static void begin(String name) {
346         begin(name, null);
347     }
348 
349     /**
350      * Triggers the 'begin' native trace event.
351      * @param name The name of the event.
352      * @param arg  The arguments of the event.
353      */
begin(String name, String arg)354     public static void begin(String name, String arg) {
355         EarlyTraceEvent.begin(name);
356         if (sEnabled) nativeBegin(name, arg);
357     }
358 
359     /**
360      * Triggers the 'end' native trace event with no arguments.
361      * @param name The name of the event.
362      */
end(String name)363     public static void end(String name) {
364         end(name, null);
365     }
366 
367     /**
368      * Triggers the 'end' native trace event.
369      * @param name The name of the event.
370      * @param arg  The arguments of the event.
371      */
end(String name, String arg)372     public static void end(String name, String arg) {
373         EarlyTraceEvent.end(name);
374         if (sEnabled) nativeEnd(name, arg);
375     }
376 
nativeRegisterEnabledObserver()377     private static native void nativeRegisterEnabledObserver();
nativeStartATrace()378     private static native void nativeStartATrace();
nativeStopATrace()379     private static native void nativeStopATrace();
nativeInstant(String name, String arg)380     private static native void nativeInstant(String name, String arg);
nativeBegin(String name, String arg)381     private static native void nativeBegin(String name, String arg);
nativeEnd(String name, String arg)382     private static native void nativeEnd(String name, String arg);
nativeBeginToplevel(String target)383     private static native void nativeBeginToplevel(String target);
nativeEndToplevel()384     private static native void nativeEndToplevel();
nativeStartAsync(String name, long id)385     private static native void nativeStartAsync(String name, long id);
nativeFinishAsync(String name, long id)386     private static native void nativeFinishAsync(String name, long id);
387 }
388