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
5package org.chromium.base;
6
7import android.os.Looper;
8import android.os.MessageQueue;
9import android.os.SystemClock;
10import android.util.Log;
11import android.util.Printer;
12
13import org.chromium.base.annotations.CalledByNative;
14import org.chromium.base.annotations.JNINamespace;
15/**
16 * Java mirror of Chrome trace event API. See base/trace_event/trace_event.h. Unlike the native
17 * version, Java does not have stack objects, so a TRACE_EVENT() which does both TRACE_EVENT_BEGIN()
18 * and TRACE_EVENT_END() in ctor/dtor is not possible.
19 * It is OK to use tracing before the native library has loaded, but such traces will
20 * be ignored. (Perhaps we could devise to buffer them up in future?).
21 */
22@JNINamespace("base::android")
23public class TraceEvent {
24
25    private static volatile boolean sEnabled = false;
26    private static volatile boolean sATraceEnabled = false; // True when taking an Android systrace.
27
28    private static class BasicLooperMonitor implements Printer {
29        @Override
30        public void println(final String line) {
31            if (line.startsWith(">")) {
32                beginHandling(line);
33            } else {
34                assert line.startsWith("<");
35                endHandling(line);
36            }
37        }
38
39        void beginHandling(final String line) {
40            if (sEnabled) nativeBeginToplevel();
41        }
42
43        void endHandling(final String line) {
44            if (sEnabled) nativeEndToplevel();
45        }
46    }
47
48    /**
49     * A class that records, traces and logs statistics about the UI thead's Looper.
50     * The output of this class can be used in a number of interesting ways:
51     * <p>
52     * <ol><li>
53     * When using chrometrace, there will be a near-continuous line of
54     * measurements showing both event dispatches as well as idles;
55     * </li><li>
56     * Logging messages are output for events that run too long on the
57     * event dispatcher, making it easy to identify problematic areas;
58     * </li><li>
59     * Statistics are output whenever there is an idle after a non-trivial
60     * amount of activity, allowing information to be gathered about task
61     * density and execution cadence on the Looper;
62     * </li></ol>
63     * <p>
64     * The class attaches itself as an idle handler to the main Looper, and
65     * monitors the execution of events and idle notifications. Task counters
66     * accumulate between idle notifications and get reset when a new idle
67     * notification is received.
68     */
69    private static final class IdleTracingLooperMonitor extends BasicLooperMonitor
70            implements MessageQueue.IdleHandler {
71        // Tags for dumping to logcat or TraceEvent
72        private static final String TAG = "TraceEvent.LooperMonitor";
73        private static final String IDLE_EVENT_NAME = "Looper.queueIdle";
74
75        // Calculation constants
76        private static final long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS
77        // A reasonable threshold for defining a Looper event as "long running"
78        private static final long MIN_INTERESTING_DURATION_MILLIS =
79                FRAME_DURATION_MILLIS;
80        // A reasonable threshold for a "burst" of tasks on the Looper
81        private static final long MIN_INTERESTING_BURST_DURATION_MILLIS =
82                MIN_INTERESTING_DURATION_MILLIS * 3;
83
84        // Stats tracking
85        private long mLastIdleStartedAt = 0L;
86        private long mLastWorkStartedAt = 0L;
87        private int mNumTasksSeen = 0;
88        private int mNumIdlesSeen = 0;
89        private int mNumTasksSinceLastIdle = 0;
90
91        // State
92        private boolean mIdleMonitorAttached = false;
93
94        // Called from within the begin/end methods only.
95        // This method can only execute on the looper thread, because that is
96        // the only thread that is permitted to call Looper.myqueue().
97        private final void syncIdleMonitoring() {
98            if (sEnabled && !mIdleMonitorAttached) {
99                // approximate start time for computational purposes
100                mLastIdleStartedAt = SystemClock.elapsedRealtime();
101                Looper.myQueue().addIdleHandler(this);
102                mIdleMonitorAttached = true;
103                Log.v(TAG, "attached idle handler");
104            } else if (mIdleMonitorAttached && !sEnabled) {
105                Looper.myQueue().removeIdleHandler(this);
106                mIdleMonitorAttached = false;
107                Log.v(TAG, "detached idle handler");
108            }
109        }
110
111        @Override
112        final void beginHandling(final String line) {
113            // Close-out any prior 'idle' period before starting new task.
114            if (mNumTasksSinceLastIdle == 0) {
115                TraceEvent.end(IDLE_EVENT_NAME);
116            }
117            mLastWorkStartedAt = SystemClock.elapsedRealtime();
118            syncIdleMonitoring();
119            super.beginHandling(line);
120        }
121
122        @Override
123        final void endHandling(final String line) {
124            final long elapsed = SystemClock.elapsedRealtime()
125                    - mLastWorkStartedAt;
126            if (elapsed > MIN_INTERESTING_DURATION_MILLIS) {
127                traceAndLog(Log.WARN, "observed a task that took "
128                        + elapsed + "ms: " + line);
129            }
130            super.endHandling(line);
131            syncIdleMonitoring();
132            mNumTasksSeen++;
133            mNumTasksSinceLastIdle++;
134        }
135
136        private static void traceAndLog(int level, String message) {
137            TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message);
138            Log.println(level, TAG, message);
139        }
140
141        @Override
142        public final boolean queueIdle() {
143            final long now =  SystemClock.elapsedRealtime();
144            if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now;
145            final long elapsed = now - mLastIdleStartedAt;
146            mNumIdlesSeen++;
147            TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks since last idle.");
148            if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) {
149                // Dump stats
150                String statsString = mNumTasksSeen + " tasks and "
151                        + mNumIdlesSeen + " idles processed so far, "
152                        + mNumTasksSinceLastIdle + " tasks bursted and "
153                        + elapsed + "ms elapsed since last idle";
154                traceAndLog(Log.DEBUG, statsString);
155            }
156            mLastIdleStartedAt = now;
157            mNumTasksSinceLastIdle = 0;
158            return true; // stay installed
159        }
160    }
161
162    // Holder for monitor avoids unnecessary construction on non-debug runs
163    private static final class LooperMonitorHolder {
164        private static final BasicLooperMonitor sInstance =
165                CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_IDLE_TRACING)
166                ? new IdleTracingLooperMonitor() : new BasicLooperMonitor();
167    }
168
169
170    /**
171     * Register an enabled observer, such that java traces are always enabled with native.
172     */
173    public static void registerNativeEnabledObserver() {
174        nativeRegisterEnabledObserver();
175    }
176
177    /**
178     * Notification from native that tracing is enabled/disabled.
179     */
180    @CalledByNative
181    public static void setEnabled(boolean enabled) {
182        sEnabled = enabled;
183        // Android M+ systrace logs this on its own. Only log it if not writing to Android systrace.
184        if (sATraceEnabled) return;
185        ThreadUtils.getUiThreadLooper().setMessageLogging(
186                enabled ? LooperMonitorHolder.sInstance : null);
187    }
188
189    /**
190     * Enables or disabled Android systrace path of Chrome tracing. If enabled, all Chrome
191     * traces will be also output to Android systrace. Because of the overhead of Android
192     * systrace, this is for WebView only.
193     */
194    public static void setATraceEnabled(boolean enabled) {
195        if (sATraceEnabled == enabled) return;
196        sATraceEnabled = enabled;
197        if (enabled) {
198            // Calls TraceEvent.setEnabled(true) via
199            // TraceLog::EnabledStateObserver::OnTraceLogEnabled
200            nativeStartATrace();
201        } else {
202            // Calls TraceEvent.setEnabled(false) via
203            // TraceLog::EnabledStateObserver::OnTraceLogDisabled
204            nativeStopATrace();
205        }
206    }
207
208    /**
209     * @return True if tracing is enabled, false otherwise.
210     * It is safe to call trace methods without checking if TraceEvent
211     * is enabled.
212     */
213    public static boolean enabled() {
214        return sEnabled;
215    }
216
217    /**
218     * Triggers the 'instant' native trace event with no arguments.
219     * @param name The name of the event.
220     */
221    public static void instant(String name) {
222        if (sEnabled) nativeInstant(name, null);
223    }
224
225    /**
226     * Triggers the 'instant' native trace event.
227     * @param name The name of the event.
228     * @param arg  The arguments of the event.
229     */
230    public static void instant(String name, String arg) {
231        if (sEnabled) nativeInstant(name, arg);
232    }
233
234    /**
235     * Triggers the 'start' native trace event with no arguments.
236     * @param name The name of the event.
237     * @param id   The id of the asynchronous event.
238     */
239    public static void startAsync(String name, long id) {
240        if (sEnabled) nativeStartAsync(name, id);
241    }
242
243    /**
244     * Triggers the 'finish' native trace event with no arguments.
245     * @param name The name of the event.
246     * @param id   The id of the asynchronous event.
247     */
248    public static void finishAsync(String name, long id) {
249        if (sEnabled) nativeFinishAsync(name, id);
250    }
251
252    /**
253     * Triggers the 'begin' native trace event with no arguments.
254     * @param name The name of the event.
255     */
256    public static void begin(String name) {
257        if (sEnabled) nativeBegin(name, null);
258    }
259
260    /**
261     * Triggers the 'begin' native trace event.
262     * @param name The name of the event.
263     * @param arg  The arguments of the event.
264     */
265    public static void begin(String name, String arg) {
266        if (sEnabled) nativeBegin(name, arg);
267    }
268
269    /**
270     * Triggers the 'end' native trace event with no arguments.
271     * @param name The name of the event.
272     */
273    public static void end(String name) {
274        if (sEnabled) nativeEnd(name, null);
275    }
276
277    /**
278     * Triggers the 'end' native trace event.
279     * @param name The name of the event.
280     * @param arg  The arguments of the event.
281     */
282    public static void end(String name, String arg) {
283        if (sEnabled) nativeEnd(name, arg);
284    }
285
286    private static native void nativeRegisterEnabledObserver();
287    private static native void nativeStartATrace();
288    private static native void nativeStopATrace();
289    private static native void nativeInstant(String name, String arg);
290    private static native void nativeBegin(String name, String arg);
291    private static native void nativeEnd(String name, String arg);
292    private static native void nativeBeginToplevel();
293    private static native void nativeEndToplevel();
294    private static native void nativeStartAsync(String name, long id);
295    private static native void nativeFinishAsync(String name, long id);
296}
297