Home | History | Annotate | Download | only in common
      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