OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
bulach
2014/01/09 14:54:50
deleting this file would break downstream.
two opt
aberent
2014/01/09 17:35:29
Done. Option 2 (although the downstream patch is a
| |
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.content.common; | |
6 | |
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; | |
13 | |
14 import org.chromium.base.CommandLine; | |
15 import org.chromium.base.ThreadUtils; | |
16 | |
17 import java.lang.reflect.InvocationTargetException; | |
18 import java.lang.reflect.Method; | |
19 | |
20 /** | |
21 * Java mirror of Chrome trace event API. See base/debug/trace_event.h. Unlike the native version, | |
22 * Java does not have stack objects, so a TRACE_EVENT() which does both TRACE_EV ENT_BEGIN() and | |
23 * TRACE_EVENT_END() in ctor/dtor is not possible. | |
24 * It is OK to use tracing before the native library has loaded, but such traces will | |
25 * be ignored. (Perhaps we could devise to buffer them up in future?). | |
26 */ | |
27 public class TraceEvent { | |
28 | |
29 private static volatile boolean sEnabled = false; | |
30 | |
31 private static class BasicLooperMonitor implements Printer { | |
32 private static final String DISPATCH_EVENT_NAME = | |
33 "Looper.dispatchMessage"; | |
34 | |
35 @Override | |
36 public void println(final String line) { | |
37 if (line.startsWith(">")) { | |
38 beginHandling(line); | |
39 } else { | |
40 assert line.startsWith("<"); | |
41 endHandling(line); | |
42 } | |
43 } | |
44 | |
45 void beginHandling(final String line) { | |
46 TraceEvent.begin(DISPATCH_EVENT_NAME, line); | |
47 } | |
48 | |
49 void endHandling(final String line) { | |
50 TraceEvent.end(DISPATCH_EVENT_NAME); | |
51 } | |
52 } | |
53 | |
54 /** | |
55 * A class that records, traces and logs statistics about the UI thead's Loo per. | |
56 * The output of this class can be used in a number of interesting ways: | |
57 * <p> | |
58 * <ol><li> | |
59 * When using chrometrace, there will be a near-continuous line of | |
60 * measurements showing both event dispatches as well as idles; | |
61 * </li><li> | |
62 * Logging messages are output for events that run too long on the | |
63 * event dispatcher, making it easy to identify problematic areas; | |
64 * </li><li> | |
65 * Statistics are output whenever there is an idle after a non-trivial | |
66 * amount of activity, allowing information to be gathered about task | |
67 * density and execution cadence on the Looper; | |
68 * </li></ol> | |
69 * <p> | |
70 * The class attaches itself as an idle handler to the main Looper, and | |
71 * monitors the execution of events and idle notifications. Task counters | |
72 * accumulate between idle notifications and get reset when a new idle | |
73 * notification is received. | |
74 */ | |
75 private static final class IdleTracingLooperMonitor extends BasicLooperMonit or | |
76 implements MessageQueue.IdleHandler { | |
77 // Tags for dumping to logcat or TraceEvent | |
78 private static final String TAG = "TraceEvent.LooperMonitor"; | |
79 private static final String IDLE_EVENT_NAME = "Looper.queueIdle"; | |
80 | |
81 // Calculation constants | |
82 private static final long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS | |
83 // A reasonable threshold for defining a Looper event as "long running" | |
84 private static final long MIN_INTERESTING_DURATION_MILLIS = | |
85 FRAME_DURATION_MILLIS; | |
86 // A reasonable threshold for a "burst" of tasks on the Looper | |
87 private static final long MIN_INTERESTING_BURST_DURATION_MILLIS = | |
88 MIN_INTERESTING_DURATION_MILLIS * 3; | |
89 | |
90 // Stats tracking | |
91 private long mLastIdleStartedAt = 0L; | |
92 private long mLastWorkStartedAt = 0L; | |
93 private int mNumTasksSeen = 0; | |
94 private int mNumIdlesSeen = 0; | |
95 private int mNumTasksSinceLastIdle = 0; | |
96 | |
97 // State | |
98 private boolean mIdleMonitorAttached = false; | |
99 | |
100 // Called from within the begin/end methods only. | |
101 // This method can only execute on the looper thread, because that is | |
102 // the only thread that is permitted to call Looper.myqueue(). | |
103 private final void syncIdleMonitoring() { | |
104 if (sEnabled && !mIdleMonitorAttached) { | |
105 // approximate start time for computational purposes | |
106 mLastIdleStartedAt = SystemClock.elapsedRealtime(); | |
107 Looper.myQueue().addIdleHandler(this); | |
108 mIdleMonitorAttached = true; | |
109 Log.v(TAG, "attached idle handler"); | |
110 } else if (mIdleMonitorAttached && !sEnabled) { | |
111 Looper.myQueue().removeIdleHandler(this); | |
112 mIdleMonitorAttached = false; | |
113 Log.v(TAG, "detached idle handler"); | |
114 } | |
115 } | |
116 | |
117 @Override | |
118 final void beginHandling(final String line) { | |
119 // Close-out any prior 'idle' period before starting new task. | |
120 if (mNumTasksSinceLastIdle == 0) { | |
121 TraceEvent.end(IDLE_EVENT_NAME); | |
122 } | |
123 mLastWorkStartedAt = SystemClock.elapsedRealtime(); | |
124 syncIdleMonitoring(); | |
125 super.beginHandling(line); | |
126 } | |
127 | |
128 @Override | |
129 final void endHandling(final String line) { | |
130 final long elapsed = SystemClock.elapsedRealtime() | |
131 - mLastWorkStartedAt; | |
132 if (elapsed > MIN_INTERESTING_DURATION_MILLIS) { | |
133 traceAndLog(Log.WARN, "observed a task that took " | |
134 + elapsed + "ms: " + line); | |
135 } | |
136 super.endHandling(line); | |
137 syncIdleMonitoring(); | |
138 mNumTasksSeen++; | |
139 mNumTasksSinceLastIdle++; | |
140 } | |
141 | |
142 private static void traceAndLog(int level, String message) { | |
143 TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message); | |
144 Log.println(level, TAG, message); | |
145 } | |
146 | |
147 @Override | |
148 public final boolean queueIdle() { | |
149 final long now = SystemClock.elapsedRealtime(); | |
150 if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now; | |
151 final long elapsed = now - mLastIdleStartedAt; | |
152 mNumIdlesSeen++; | |
153 TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks s ince last idle."); | |
154 if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) { | |
155 // Dump stats | |
156 String statsString = mNumTasksSeen + " tasks and " | |
157 + mNumIdlesSeen + " idles processed so far, " | |
158 + mNumTasksSinceLastIdle + " tasks bursted and " | |
159 + elapsed + "ms elapsed since last idle"; | |
160 traceAndLog(Log.DEBUG, statsString); | |
161 } | |
162 mLastIdleStartedAt = now; | |
163 mNumTasksSinceLastIdle = 0; | |
164 return true; // stay installed | |
165 } | |
166 } | |
167 | |
168 // Holder for monitor avoids unnecessary construction on non-debug runs | |
169 private static final class LooperMonitorHolder { | |
170 private static final BasicLooperMonitor sInstance = | |
171 CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_IDLE_ TRACING) ? | |
172 new IdleTracingLooperMonitor() : new BasicLooperMonitor( ); | |
173 } | |
174 | |
175 private static long sTraceTagView; | |
176 private static Method sSystemPropertiesGetLongMethod; | |
177 private static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.t ags.enableflags"; | |
178 | |
179 static { | |
180 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | |
181 try { | |
182 Class<?> traceClass = Class.forName("android.os.Trace"); | |
183 sTraceTagView = traceClass.getField("TRACE_TAG_WEBVIEW").getLong (null); | |
184 | |
185 Class<?> systemPropertiesClass = Class.forName("android.os.Syste mProperties"); | |
186 sSystemPropertiesGetLongMethod = systemPropertiesClass.getDeclar edMethod( | |
187 "getLong", String.class, Long.TYPE); | |
188 Method addChangeCallbackMethod = systemPropertiesClass.getDeclar edMethod( | |
189 "addChangeCallback", Runnable.class); | |
190 | |
191 // Won't reach here if any of the above reflect lookups fail. | |
192 addChangeCallbackMethod.invoke(null, new Runnable() { | |
193 @Override | |
194 public void run() { | |
195 setEnabledToMatchNative(); | |
196 } | |
197 }); | |
198 } catch (ClassNotFoundException e) { | |
199 Log.e("TraceEvent", "init", e); | |
200 } catch (NoSuchMethodException e) { | |
201 Log.e("TraceEvent", "init", e); | |
202 } catch (IllegalArgumentException e) { | |
203 Log.e("TraceEvent", "init", e); | |
204 } catch (IllegalAccessException e) { | |
205 Log.e("TraceEvent", "init", e); | |
206 } catch (InvocationTargetException e) { | |
207 Log.e("TraceEvent", "init", e); | |
208 } catch (NoSuchFieldException e) { | |
209 Log.e("TraceEvent", "init", e); | |
210 } | |
211 } | |
212 } | |
213 | |
214 /** | |
215 * Calling this will cause enabled() to be updated to match that set on the native side. | |
216 * The native library must be loaded before calling this method. | |
217 */ | |
218 public static void setEnabledToMatchNative() { | |
219 boolean enabled = nativeTraceEnabled(); | |
220 | |
221 if (sSystemPropertiesGetLongMethod != null) { | |
222 try { | |
223 long enabledFlags = (Long) sSystemPropertiesGetLongMethod.invoke ( | |
224 null, PROPERTY_TRACE_TAG_ENABLEFLAGS, 0); | |
225 if ((enabledFlags & sTraceTagView) != 0) { | |
226 nativeStartATrace(); | |
227 enabled = true; | |
228 } else { | |
229 nativeStopATrace(); | |
230 } | |
231 } catch (IllegalArgumentException e) { | |
232 Log.e("TraceEvent", "setEnabledToMatchNative", e); | |
233 } catch (IllegalAccessException e) { | |
234 Log.e("TraceEvent", "setEnabledToMatchNative", e); | |
235 } catch (InvocationTargetException e) { | |
236 Log.e("TraceEvent", "setEnabledToMatchNative", e); | |
237 } | |
238 } | |
239 | |
240 setEnabled(enabled); | |
241 } | |
242 | |
243 /** | |
244 * Enables or disables tracing. | |
245 * The native library must be loaded before the first call with enabled == t rue. | |
246 */ | |
247 public static synchronized void setEnabled(boolean enabled) { | |
248 if (sEnabled == enabled) return; | |
249 sEnabled = enabled; | |
250 ThreadUtils.getUiThreadLooper().setMessageLogging( | |
251 enabled ? LooperMonitorHolder.sInstance : null); | |
252 } | |
253 | |
254 /** | |
255 * @return True if tracing is enabled, false otherwise. | |
256 * It is safe to call trace methods without checking if TraceEvent | |
257 * is enabled. | |
258 */ | |
259 public static boolean enabled() { | |
260 return sEnabled; | |
261 } | |
262 | |
263 /** | |
264 * Triggers the 'instant' native trace event with no arguments. | |
265 * @param name The name of the event. | |
266 */ | |
267 public static void instant(String name) { | |
268 if (sEnabled) nativeInstant(name, null); | |
269 } | |
270 | |
271 /** | |
272 * Triggers the 'instant' native trace event. | |
273 * @param name The name of the event. | |
274 * @param arg The arguments of the event. | |
275 */ | |
276 public static void instant(String name, String arg) { | |
277 if (sEnabled) nativeInstant(name, arg); | |
278 } | |
279 | |
280 /** | |
281 * Convenience wrapper around the versions of startAsync() that take string parameters. | |
282 * @param id The id of the asynchronous event. Will automatically figure ou t the name from | |
283 * calling {@link #getCallerName()}. | |
284 * @see #begin() | |
285 */ | |
286 public static void startAsync(long id) { | |
287 if (sEnabled) nativeStartAsync(getCallerName(), id, null); | |
288 } | |
289 | |
290 /** | |
291 * Triggers the 'start' native trace event with no arguments. | |
292 * @param name The name of the event. | |
293 * @param id The id of the asynchronous event. | |
294 * @see #begin() | |
295 */ | |
296 public static void startAsync(String name, long id) { | |
297 if (sEnabled) nativeStartAsync(name, id, null); | |
298 } | |
299 | |
300 /** | |
301 * Triggers the 'start' native trace event. | |
302 * @param name The name of the event. | |
303 * @param id The id of the asynchronous event. | |
304 * @param arg The arguments of the event. | |
305 * @see #begin() | |
306 */ | |
307 public static void startAsync(String name, long id, String arg) { | |
308 if (sEnabled) nativeStartAsync(name, id, arg); | |
309 } | |
310 | |
311 /** | |
312 * Convenience wrapper around the versions of finishAsync() that take string parameters. | |
313 * @param id The id of the asynchronous event. Will automatically figure ou t the name from | |
314 * calling {@link #getCallerName()}. | |
315 * @see #finish() | |
316 */ | |
317 public static void finishAsync(long id) { | |
318 if (sEnabled) nativeFinishAsync(getCallerName(), id, null); | |
319 } | |
320 | |
321 /** | |
322 * Triggers the 'finish' native trace event with no arguments. | |
323 * @param name The name of the event. | |
324 * @param id The id of the asynchronous event. | |
325 * @see #begin() | |
326 */ | |
327 public static void finishAsync(String name, long id) { | |
328 if (sEnabled) nativeFinishAsync(name, id, null); | |
329 } | |
330 | |
331 /** | |
332 * Triggers the 'finish' native trace event. | |
333 * @param name The name of the event. | |
334 * @param id The id of the asynchronous event. | |
335 * @param arg The arguments of the event. | |
336 * @see #begin() | |
337 */ | |
338 public static void finishAsync(String name, long id, String arg) { | |
339 if (sEnabled) nativeFinishAsync(name, id, arg); | |
340 } | |
341 | |
342 /** | |
343 * Convenience wrapper around the versions of begin() that take string param eters. | |
344 * The name of the event will be derived from the class and function name th at call this. | |
345 * IMPORTANT: if using this version, ensure end() (no parameters) is always called from the | |
346 * same calling context. | |
347 */ | |
348 public static void begin() { | |
349 if (sEnabled) nativeBegin(getCallerName(), null); | |
350 } | |
351 | |
352 /** | |
353 * Triggers the 'begin' native trace event with no arguments. | |
354 * @param name The name of the event. | |
355 */ | |
356 public static void begin(String name) { | |
357 if (sEnabled) nativeBegin(name, null); | |
358 } | |
359 | |
360 /** | |
361 * Triggers the 'begin' native trace event. | |
362 * @param name The name of the event. | |
363 * @param arg The arguments of the event. | |
364 */ | |
365 public static void begin(String name, String arg) { | |
366 if (sEnabled) nativeBegin(name, arg); | |
367 } | |
368 | |
369 /** | |
370 * Convenience wrapper around the versions of end() that take string paramet ers. See begin() | |
371 * for more information. | |
372 */ | |
373 public static void end() { | |
374 if (sEnabled) nativeEnd(getCallerName(), null); | |
375 } | |
376 | |
377 /** | |
378 * Triggers the 'end' native trace event with no arguments. | |
379 * @param name The name of the event. | |
380 */ | |
381 public static void end(String name) { | |
382 if (sEnabled) nativeEnd(name, null); | |
383 } | |
384 | |
385 /** | |
386 * Triggers the 'end' native trace event. | |
387 * @param name The name of the event. | |
388 * @param arg The arguments of the event. | |
389 */ | |
390 public static void end(String name, String arg) { | |
391 if (sEnabled) nativeEnd(name, arg); | |
392 } | |
393 | |
394 private static String getCallerName() { | |
395 // This was measured to take about 1ms on Trygon device. | |
396 StackTraceElement[] stack = java.lang.Thread.currentThread().getStackTra ce(); | |
397 | |
398 // Commented out to avoid excess call overhead, but these lines can be u seful to debug | |
399 // exactly where the TraceEvent's client is on the callstack. | |
400 // int index = 0; | |
401 // while (!stack[index].getClassName().equals(TraceEvent.class.getName( ))) ++index; | |
402 // while (stack[index].getClassName().equals(TraceEvent.class.getName() )) ++index; | |
403 // System.logW("TraceEvent caller is at stack index " + index); | |
404 | |
405 // '4' Was derived using the above commented out code snippet. | |
406 return stack[4].getClassName() + "." + stack[4].getMethodName(); | |
407 } | |
408 | |
409 private static native boolean nativeTraceEnabled(); | |
410 private static native void nativeStartATrace(); | |
411 private static native void nativeStopATrace(); | |
412 private static native void nativeInstant(String name, String arg); | |
413 private static native void nativeBegin(String name, String arg); | |
414 private static native void nativeEnd(String name, String arg); | |
415 private static native void nativeStartAsync(String name, long id, String arg ); | |
416 private static native void nativeFinishAsync(String name, long id, String ar g); | |
417 } | |
OLD | NEW |