/src/mozilla-central/toolkit/xre/EventTracer.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | /* |
6 | | * Event loop instrumentation. This code attempts to measure the |
7 | | * latency of the UI-thread event loop by firing native events at it from |
8 | | * a background thread, and measuring how long it takes for them |
9 | | * to be serviced. The measurement interval (kMeasureInterval, below) |
10 | | * is also used as the upper bound of acceptable response time. |
11 | | * When an event takes longer than that interval to be serviced, |
12 | | * a sample will be written to the log. |
13 | | * |
14 | | * Usage: |
15 | | * |
16 | | * Set MOZ_INSTRUMENT_EVENT_LOOP=1 in the environment to enable |
17 | | * this instrumentation. Currently only the UI process is instrumented. |
18 | | * |
19 | | * Set MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT in the environment to a |
20 | | * file path to contain the log output, the default is to log to stdout. |
21 | | * |
22 | | * Set MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD in the environment to an |
23 | | * integer number of milliseconds to change the threshold for reporting. |
24 | | * The default is 20 milliseconds. Unresponsive periods shorter than this |
25 | | * threshold will not be reported. |
26 | | * |
27 | | * Set MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL in the environment to an |
28 | | * integer number of milliseconds to change the maximum sampling frequency. |
29 | | * This variable controls how often events will be sent to the main |
30 | | * thread's event loop to sample responsiveness. The sampler will not |
31 | | * send events twice within LOOP_INTERVAL milliseconds. |
32 | | * The default is 10 milliseconds. |
33 | | * |
34 | | * All logged output lines start with MOZ_EVENT_TRACE. All timestamps |
35 | | * output are milliseconds since the epoch (PRTime / 1000). |
36 | | * |
37 | | * On startup, a line of the form: |
38 | | * MOZ_EVENT_TRACE start <timestamp> |
39 | | * will be output. |
40 | | * |
41 | | * On shutdown, a line of the form: |
42 | | * MOZ_EVENT_TRACE stop <timestamp> |
43 | | * will be output. |
44 | | * |
45 | | * When an event servicing time exceeds the threshold, a line of the form: |
46 | | * MOZ_EVENT_TRACE sample <timestamp> <duration> |
47 | | * will be output, where <duration> is the number of milliseconds that |
48 | | * it took for the event to be serviced. Duration may contain a fractional |
49 | | * component. |
50 | | */ |
51 | | |
52 | | #include "GeckoProfiler.h" |
53 | | |
54 | | #include "EventTracer.h" |
55 | | |
56 | | #include <stdio.h> |
57 | | |
58 | | #include "mozilla/Preferences.h" |
59 | | #include "mozilla/TimeStamp.h" |
60 | | #include "mozilla/WidgetTraceEvent.h" |
61 | | #include "nsDebug.h" |
62 | | #include <limits.h> |
63 | | #include <prenv.h> |
64 | | #include <prinrval.h> |
65 | | #include <prthread.h> |
66 | | #include <prtime.h> |
67 | | |
68 | | #include "nsThreadUtils.h" |
69 | | |
70 | | using mozilla::TimeDuration; |
71 | | using mozilla::TimeStamp; |
72 | | using mozilla::FireAndWaitForTracerEvent; |
73 | | |
74 | | namespace { |
75 | | |
76 | | PRThread* sTracerThread = nullptr; |
77 | | bool sExit = false; |
78 | | |
79 | | struct TracerStartClosure { |
80 | | bool mLogTracing; |
81 | | int32_t mThresholdInterval; |
82 | | }; |
83 | | |
84 | | /* |
85 | | * The tracer thread fires events at the native event loop roughly |
86 | | * every kMeasureInterval. It will sleep to attempt not to send them |
87 | | * more quickly, but if the response time is longer than kMeasureInterval |
88 | | * it will not send another event until the previous response is received. |
89 | | * |
90 | | * The output defaults to stdout, but can be redirected to a file by |
91 | | * settting the environment variable MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT |
92 | | * to the name of a file to use. |
93 | | */ |
94 | | void TracerThread(void *arg) |
95 | 0 | { |
96 | 0 | AUTO_PROFILER_REGISTER_THREAD("Event Tracer"); |
97 | 0 | NS_SetCurrentThreadName("Event Tracer"); |
98 | 0 |
|
99 | 0 | TracerStartClosure* threadArgs = static_cast<TracerStartClosure*>(arg); |
100 | 0 |
|
101 | 0 | // These are the defaults. They can be overridden by environment vars. |
102 | 0 | // This should be set to the maximum latency we'd like to allow |
103 | 0 | // for responsiveness. |
104 | 0 | int32_t thresholdInterval = threadArgs->mThresholdInterval; |
105 | 0 | PRIntervalTime threshold = PR_MillisecondsToInterval(thresholdInterval); |
106 | 0 | // This is the sampling interval. |
107 | 0 | PRIntervalTime interval = PR_MillisecondsToInterval(thresholdInterval / 2); |
108 | 0 |
|
109 | 0 | sExit = false; |
110 | 0 | FILE* log = nullptr; |
111 | 0 | char* envfile = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT"); |
112 | 0 | if (envfile) { |
113 | 0 | log = fopen(envfile, "w"); |
114 | 0 | } |
115 | 0 | if (log == nullptr) |
116 | 0 | log = stdout; |
117 | 0 |
|
118 | 0 | char* thresholdenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD"); |
119 | 0 | if (thresholdenv && *thresholdenv) { |
120 | 0 | int val = atoi(thresholdenv); |
121 | 0 | if (val != 0 && val != INT_MAX && val != INT_MIN) { |
122 | 0 | threshold = PR_MillisecondsToInterval(val); |
123 | 0 | } |
124 | 0 | } |
125 | 0 |
|
126 | 0 | char* intervalenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL"); |
127 | 0 | if (intervalenv && *intervalenv) { |
128 | 0 | int val = atoi(intervalenv); |
129 | 0 | if (val != 0 && val != INT_MAX && val != INT_MIN) { |
130 | 0 | interval = PR_MillisecondsToInterval(val); |
131 | 0 | } |
132 | 0 | } |
133 | 0 |
|
134 | 0 | if (threadArgs->mLogTracing) { |
135 | 0 | long long now = PR_Now() / PR_USEC_PER_MSEC; |
136 | 0 | fprintf(log, "MOZ_EVENT_TRACE start %llu\n", now); |
137 | 0 | } |
138 | 0 |
|
139 | 0 | while (!sExit) { |
140 | 0 | TimeStamp start(TimeStamp::Now()); |
141 | 0 | PRIntervalTime next_sleep = interval; |
142 | 0 |
|
143 | 0 | //TODO: only wait up to a maximum of interval; return |
144 | 0 | // early if that threshold is exceeded and dump a stack trace |
145 | 0 | // or do something else useful. |
146 | 0 | if (FireAndWaitForTracerEvent()) { |
147 | 0 | TimeDuration duration = TimeStamp::Now() - start; |
148 | 0 | // Only report samples that exceed our measurement threshold. |
149 | 0 | long long now = PR_Now() / PR_USEC_PER_MSEC; |
150 | 0 | if (threadArgs->mLogTracing && duration.ToMilliseconds() > threshold) { |
151 | 0 | fprintf(log, "MOZ_EVENT_TRACE sample %llu %lf\n", |
152 | 0 | now, |
153 | 0 | duration.ToMilliseconds()); |
154 | 0 | } |
155 | 0 |
|
156 | 0 | if (next_sleep > duration.ToMilliseconds()) { |
157 | 0 | next_sleep -= int(duration.ToMilliseconds()); |
158 | 0 | } |
159 | 0 | else { |
160 | 0 | // Don't sleep at all if this event took longer than the measure |
161 | 0 | // interval to deliver. |
162 | 0 | next_sleep = 0; |
163 | 0 | } |
164 | 0 | } |
165 | 0 |
|
166 | 0 | if (next_sleep != 0 && !sExit) { |
167 | 0 | PR_Sleep(next_sleep); |
168 | 0 | } |
169 | 0 | } |
170 | 0 |
|
171 | 0 | if (threadArgs->mLogTracing) { |
172 | 0 | long long now = PR_Now() / PR_USEC_PER_MSEC; |
173 | 0 | fprintf(log, "MOZ_EVENT_TRACE stop %llu\n", now); |
174 | 0 | } |
175 | 0 |
|
176 | 0 | if (log != stdout) |
177 | 0 | fclose(log); |
178 | 0 |
|
179 | 0 | delete threadArgs; |
180 | 0 | } |
181 | | |
182 | | } // namespace |
183 | | |
184 | | namespace mozilla { |
185 | | |
186 | | bool InitEventTracing(bool aLog) |
187 | 0 | { |
188 | 0 | if (sTracerThread) |
189 | 0 | return true; |
190 | 0 | |
191 | 0 | // Initialize the widget backend. |
192 | 0 | if (!InitWidgetTracing()) |
193 | 0 | return false; |
194 | 0 | |
195 | 0 | // The tracer thread owns the object and will delete it. |
196 | 0 | TracerStartClosure* args = new TracerStartClosure(); |
197 | 0 | args->mLogTracing = aLog; |
198 | 0 |
|
199 | 0 | // Pass the default threshold interval. |
200 | 0 | int32_t thresholdInterval = 20; |
201 | 0 | Preferences::GetInt("devtools.eventlooplag.threshold", &thresholdInterval); |
202 | 0 | args->mThresholdInterval = thresholdInterval; |
203 | 0 |
|
204 | 0 | // Create a thread that will fire events back at the |
205 | 0 | // main thread to measure responsiveness. |
206 | 0 | MOZ_ASSERT(!sTracerThread, "Event tracing already initialized!"); |
207 | 0 | sTracerThread = PR_CreateThread(PR_USER_THREAD, |
208 | 0 | TracerThread, |
209 | 0 | args, |
210 | 0 | PR_PRIORITY_NORMAL, |
211 | 0 | PR_GLOBAL_THREAD, |
212 | 0 | PR_JOINABLE_THREAD, |
213 | 0 | 0); |
214 | 0 | return sTracerThread != nullptr; |
215 | 0 | } |
216 | | |
217 | | void ShutdownEventTracing() |
218 | 0 | { |
219 | 0 | if (!sTracerThread) |
220 | 0 | return; |
221 | 0 | |
222 | 0 | sExit = true; |
223 | 0 | // Ensure that the tracer thread doesn't hang. |
224 | 0 | SignalTracerThread(); |
225 | 0 |
|
226 | 0 | if (sTracerThread) |
227 | 0 | PR_JoinThread(sTracerThread); |
228 | 0 | sTracerThread = nullptr; |
229 | 0 |
|
230 | 0 | // Allow the widget backend to clean up. |
231 | 0 | CleanUpWidgetTracing(); |
232 | 0 | } |
233 | | |
234 | | } // namespace mozilla |