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