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.
5 package org.chromium.base;
7 import android.os.Build;
8 import android.os.Looper;
9 import android.os.MessageQueue;
10 import android.os.SystemClock;
11 import android.util.Log;
12 import android.util.Printer;
14 import java.lang.reflect.InvocationTargetException;
15 import java.lang.reflect.Method;
17 * Java mirror of Chrome trace event API. See base/debug/trace_event.h. Unlike the native version,
18 * Java does not have stack objects, so a TRACE_EVENT() which does both TRACE_EVENT_BEGIN() and
19 * TRACE_EVENT_END() in ctor/dtor is not possible.
20 * It is OK to use tracing before the native library has loaded, but such traces will
21 * be ignored. (Perhaps we could devise to buffer them up in future?).
23 @JNINamespace("base::android")
24 public class TraceEvent {
26 private static volatile boolean sEnabled = false;
28 private static class BasicLooperMonitor implements Printer {
29 private static final String DISPATCH_EVENT_NAME =
30 "Looper.dispatchMessage";
33 public void println(final String line) {
34 if (line.startsWith(">")) {
37 assert line.startsWith("<");
42 void beginHandling(final String line) {
43 TraceEvent.begin(DISPATCH_EVENT_NAME, line);
46 void endHandling(final String line) {
47 TraceEvent.end(DISPATCH_EVENT_NAME);
52 * A class that records, traces and logs statistics about the UI thead's Looper.
53 * The output of this class can be used in a number of interesting ways:
56 * When using chrometrace, there will be a near-continuous line of
57 * measurements showing both event dispatches as well as idles;
59 * Logging messages are output for events that run too long on the
60 * event dispatcher, making it easy to identify problematic areas;
62 * Statistics are output whenever there is an idle after a non-trivial
63 * amount of activity, allowing information to be gathered about task
64 * density and execution cadence on the Looper;
67 * The class attaches itself as an idle handler to the main Looper, and
68 * monitors the execution of events and idle notifications. Task counters
69 * accumulate between idle notifications and get reset when a new idle
70 * notification is received.
72 private static final class IdleTracingLooperMonitor extends BasicLooperMonitor
73 implements MessageQueue.IdleHandler {
74 // Tags for dumping to logcat or TraceEvent
75 private static final String TAG = "TraceEvent.LooperMonitor";
76 private static final String IDLE_EVENT_NAME = "Looper.queueIdle";
78 // Calculation constants
79 private static final long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS
80 // A reasonable threshold for defining a Looper event as "long running"
81 private static final long MIN_INTERESTING_DURATION_MILLIS =
82 FRAME_DURATION_MILLIS;
83 // A reasonable threshold for a "burst" of tasks on the Looper
84 private static final long MIN_INTERESTING_BURST_DURATION_MILLIS =
85 MIN_INTERESTING_DURATION_MILLIS * 3;
88 private long mLastIdleStartedAt = 0L;
89 private long mLastWorkStartedAt = 0L;
90 private int mNumTasksSeen = 0;
91 private int mNumIdlesSeen = 0;
92 private int mNumTasksSinceLastIdle = 0;
95 private boolean mIdleMonitorAttached = false;
97 // Called from within the begin/end methods only.
98 // This method can only execute on the looper thread, because that is
99 // the only thread that is permitted to call Looper.myqueue().
100 private final void syncIdleMonitoring() {
101 if (sEnabled && !mIdleMonitorAttached) {
102 // approximate start time for computational purposes
103 mLastIdleStartedAt = SystemClock.elapsedRealtime();
104 Looper.myQueue().addIdleHandler(this);
105 mIdleMonitorAttached = true;
106 Log.v(TAG, "attached idle handler");
107 } else if (mIdleMonitorAttached && !sEnabled) {
108 Looper.myQueue().removeIdleHandler(this);
109 mIdleMonitorAttached = false;
110 Log.v(TAG, "detached idle handler");
115 final void beginHandling(final String line) {
116 // Close-out any prior 'idle' period before starting new task.
117 if (mNumTasksSinceLastIdle == 0) {
118 TraceEvent.end(IDLE_EVENT_NAME);
120 mLastWorkStartedAt = SystemClock.elapsedRealtime();
121 syncIdleMonitoring();
122 super.beginHandling(line);
126 final void endHandling(final String line) {
127 final long elapsed = SystemClock.elapsedRealtime()
128 - mLastWorkStartedAt;
129 if (elapsed > MIN_INTERESTING_DURATION_MILLIS) {
130 traceAndLog(Log.WARN, "observed a task that took "
131 + elapsed + "ms: " + line);
133 super.endHandling(line);
134 syncIdleMonitoring();
136 mNumTasksSinceLastIdle++;
139 private static void traceAndLog(int level, String message) {
140 TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message);
141 Log.println(level, TAG, message);
145 public final boolean queueIdle() {
146 final long now = SystemClock.elapsedRealtime();
147 if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now;
148 final long elapsed = now - mLastIdleStartedAt;
150 TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks since last idle.");
151 if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) {
153 String statsString = mNumTasksSeen + " tasks and "
154 + mNumIdlesSeen + " idles processed so far, "
155 + mNumTasksSinceLastIdle + " tasks bursted and "
156 + elapsed + "ms elapsed since last idle";
157 traceAndLog(Log.DEBUG, statsString);
159 mLastIdleStartedAt = now;
160 mNumTasksSinceLastIdle = 0;
161 return true; // stay installed
165 // Holder for monitor avoids unnecessary construction on non-debug runs
166 private static final class LooperMonitorHolder {
167 private static final BasicLooperMonitor sInstance =
168 CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_IDLE_TRACING) ?
169 new IdleTracingLooperMonitor() : new BasicLooperMonitor();
172 private static long sTraceTagView;
173 private static Method sSystemPropertiesGetLongMethod;
174 private static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags";
177 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
179 Class<?> traceClass = Class.forName("android.os.Trace");
180 sTraceTagView = traceClass.getField("TRACE_TAG_WEBVIEW").getLong(null);
182 Class<?> systemPropertiesClass = Class.forName("android.os.SystemProperties");
183 sSystemPropertiesGetLongMethod = systemPropertiesClass.getDeclaredMethod(
184 "getLong", String.class, Long.TYPE);
185 Method addChangeCallbackMethod = systemPropertiesClass.getDeclaredMethod(
186 "addChangeCallback", Runnable.class);
188 // Won't reach here if any of the above reflect lookups fail.
189 addChangeCallbackMethod.invoke(null, new Runnable() {
192 setEnabledToMatchNative();
195 } catch (ClassNotFoundException e) {
196 Log.e("TraceEvent", "init", e);
197 } catch (NoSuchMethodException e) {
198 Log.e("TraceEvent", "init", e);
199 } catch (IllegalArgumentException e) {
200 Log.e("TraceEvent", "init", e);
201 } catch (IllegalAccessException e) {
202 Log.e("TraceEvent", "init", e);
203 } catch (InvocationTargetException e) {
204 Log.e("TraceEvent", "init", e);
205 } catch (NoSuchFieldException e) {
206 Log.e("TraceEvent", "init", e);
212 * Calling this will cause enabled() to be updated to match that set on the native side.
213 * The native library must be loaded before calling this method.
215 public static void setEnabledToMatchNative() {
216 boolean enabled = nativeTraceEnabled();
218 if (sSystemPropertiesGetLongMethod != null) {
220 long enabledFlags = (Long) sSystemPropertiesGetLongMethod.invoke(
221 null, PROPERTY_TRACE_TAG_ENABLEFLAGS, 0);
222 if ((enabledFlags & sTraceTagView) != 0) {
228 } catch (IllegalArgumentException e) {
229 Log.e("TraceEvent", "setEnabledToMatchNative", e);
230 } catch (IllegalAccessException e) {
231 Log.e("TraceEvent", "setEnabledToMatchNative", e);
232 } catch (InvocationTargetException e) {
233 Log.e("TraceEvent", "setEnabledToMatchNative", e);
241 * Enables or disables tracing.
242 * The native library must be loaded before the first call with enabled == true.
244 public static synchronized void setEnabled(boolean enabled) {
245 if (sEnabled == enabled) return;
247 ThreadUtils.getUiThreadLooper().setMessageLogging(
248 enabled ? LooperMonitorHolder.sInstance : null);
252 * @return True if tracing is enabled, false otherwise.
253 * It is safe to call trace methods without checking if TraceEvent
256 public static boolean enabled() {
261 * Triggers the 'instant' native trace event with no arguments.
262 * @param name The name of the event.
264 public static void instant(String name) {
265 if (sEnabled) nativeInstant(name, null);
269 * Triggers the 'instant' native trace event.
270 * @param name The name of the event.
271 * @param arg The arguments of the event.
273 public static void instant(String name, String arg) {
274 if (sEnabled) nativeInstant(name, arg);
278 * Convenience wrapper around the versions of startAsync() that take string parameters.
279 * @param id The id of the asynchronous event.
282 public static void startAsync(long id) {
283 if (sEnabled) nativeStartAsync(getCallerName(), id, null);
287 * Triggers the 'start' native trace event with no arguments.
288 * @param name The name of the event.
289 * @param id The id of the asynchronous event.
291 public static void startAsync(String name, long id) {
292 if (sEnabled) nativeStartAsync(name, id, null);
296 * Triggers the 'start' native trace event.
297 * @param name The name of the event.
298 * @param id The id of the asynchronous event.
299 * @param arg The arguments of the event.
301 public static void startAsync(String name, long id, String arg) {
302 if (sEnabled) nativeStartAsync(name, id, arg);
306 * Convenience wrapper around the versions of finishAsync() that take string parameters.
307 * @param id The id of the asynchronous event.
310 public static void finishAsync(long id) {
311 if (sEnabled) nativeFinishAsync(getCallerName(), id, null);
315 * Triggers the 'finish' native trace event with no arguments.
316 * @param name The name of the event.
317 * @param id The id of the asynchronous event.
319 public static void finishAsync(String name, long id) {
320 if (sEnabled) nativeFinishAsync(name, id, null);
324 * Triggers the 'finish' native trace event.
325 * @param name The name of the event.
326 * @param id The id of the asynchronous event.
327 * @param arg The arguments of the event.
329 public static void finishAsync(String name, long id, String arg) {
330 if (sEnabled) nativeFinishAsync(name, id, arg);
334 * Convenience wrapper around the versions of begin() that take string parameters.
335 * The name of the event will be derived from the class and function name that call this.
336 * IMPORTANT: if using this version, ensure end() (no parameters) is always called from the
337 * same calling context.
339 * Note that the overhead is at ms or sub-ms order. Don't use this when millisecond accuracy
342 public static void begin() {
343 if (sEnabled) nativeBegin(getCallerName(), null);
347 * Triggers the 'begin' native trace event with no arguments.
348 * @param name The name of the event.
350 public static void begin(String name) {
351 if (sEnabled) nativeBegin(name, null);
355 * Triggers the 'begin' native trace event.
356 * @param name The name of the event.
357 * @param arg The arguments of the event.
359 public static void begin(String name, String arg) {
360 if (sEnabled) nativeBegin(name, arg);
364 * Convenience wrapper around the versions of end() that take string parameters.
367 public static void end() {
368 if (sEnabled) nativeEnd(getCallerName(), null);
372 * Triggers the 'end' native trace event with no arguments.
373 * @param name The name of the event.
375 public static void end(String name) {
376 if (sEnabled) nativeEnd(name, null);
380 * Triggers the 'end' native trace event.
381 * @param name The name of the event.
382 * @param arg The arguments of the event.
384 public static void end(String name, String arg) {
385 if (sEnabled) nativeEnd(name, arg);
388 private static String getCallerName() {
389 // This was measured to take about 1ms on Trygon device.
390 StackTraceElement[] stack = java.lang.Thread.currentThread().getStackTrace();
392 // Commented out to avoid excess call overhead, but these lines can be useful to debug
393 // exactly where the TraceEvent's client is on the callstack.
395 // while (!stack[index].getClassName().equals(TraceEvent.class.getName())) ++index;
396 // while (stack[index].getClassName().equals(TraceEvent.class.getName())) ++index;
397 // System.logW("TraceEvent caller is at stack index " + index);
399 // '4' Was derived using the above commented out code snippet.
400 return stack[4].getClassName() + "." + stack[4].getMethodName();
403 private static native boolean nativeTraceEnabled();
404 private static native void nativeStartATrace();
405 private static native void nativeStopATrace();
406 private static native void nativeInstant(String name, String arg);
407 private static native void nativeBegin(String name, String arg);
408 private static native void nativeEnd(String name, String arg);
409 private static native void nativeStartAsync(String name, long id, String arg);
410 private static native void nativeFinishAsync(String name, long id, String arg);