1 // Copyright 2012 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.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_EVENT_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 Looper. 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 BasicLooperMonitor 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 since 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.tags.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.SystemProperties"); 186 sSystemPropertiesGetLongMethod = systemPropertiesClass.getDeclaredMethod( 187 "getLong", String.class, Long.TYPE); 188 Method addChangeCallbackMethod = systemPropertiesClass.getDeclaredMethod( 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 == true. 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 out 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 out 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 parameters. 344 * The name of the event will be derived from the class and function name that 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 parameters. 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().getStackTrace(); 397 398 // Commented out to avoid excess call overhead, but these lines can be useful 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 arg); 417 } 418