|
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 #ifdef MOZ_WIDGET_GONK |
|
69 #include "nsThreadUtils.h" |
|
70 #include "nsIObserverService.h" |
|
71 #include "mozilla/Services.h" |
|
72 #endif |
|
73 |
|
74 using mozilla::TimeDuration; |
|
75 using mozilla::TimeStamp; |
|
76 using mozilla::FireAndWaitForTracerEvent; |
|
77 |
|
78 namespace { |
|
79 |
|
80 PRThread* sTracerThread = nullptr; |
|
81 bool sExit = false; |
|
82 |
|
83 struct TracerStartClosure { |
|
84 bool mLogTracing; |
|
85 int32_t mThresholdInterval; |
|
86 }; |
|
87 |
|
88 #ifdef MOZ_WIDGET_GONK |
|
89 class EventLoopLagDispatcher : public nsRunnable |
|
90 { |
|
91 public: |
|
92 explicit EventLoopLagDispatcher(int aLag) |
|
93 : mLag(aLag) {} |
|
94 |
|
95 NS_IMETHODIMP Run() |
|
96 { |
|
97 nsCOMPtr<nsIObserverService> obsService = |
|
98 mozilla::services::GetObserverService(); |
|
99 if (!obsService) { |
|
100 return NS_ERROR_FAILURE; |
|
101 } |
|
102 |
|
103 nsAutoString value; |
|
104 value.AppendInt(mLag); |
|
105 return obsService->NotifyObservers(nullptr, "event-loop-lag", value.get()); |
|
106 } |
|
107 |
|
108 private: |
|
109 int mLag; |
|
110 }; |
|
111 #endif |
|
112 |
|
113 /* |
|
114 * The tracer thread fires events at the native event loop roughly |
|
115 * every kMeasureInterval. It will sleep to attempt not to send them |
|
116 * more quickly, but if the response time is longer than kMeasureInterval |
|
117 * it will not send another event until the previous response is received. |
|
118 * |
|
119 * The output defaults to stdout, but can be redirected to a file by |
|
120 * settting the environment variable MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT |
|
121 * to the name of a file to use. |
|
122 */ |
|
123 void TracerThread(void *arg) |
|
124 { |
|
125 PR_SetCurrentThreadName("Event Tracer"); |
|
126 |
|
127 TracerStartClosure* threadArgs = static_cast<TracerStartClosure*>(arg); |
|
128 |
|
129 // These are the defaults. They can be overridden by environment vars. |
|
130 // This should be set to the maximum latency we'd like to allow |
|
131 // for responsiveness. |
|
132 int32_t thresholdInterval = threadArgs->mThresholdInterval; |
|
133 PRIntervalTime threshold = PR_MillisecondsToInterval(thresholdInterval); |
|
134 // This is the sampling interval. |
|
135 PRIntervalTime interval = PR_MillisecondsToInterval(thresholdInterval / 2); |
|
136 |
|
137 sExit = false; |
|
138 FILE* log = nullptr; |
|
139 char* envfile = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT"); |
|
140 if (envfile) { |
|
141 log = fopen(envfile, "w"); |
|
142 } |
|
143 if (log == nullptr) |
|
144 log = stdout; |
|
145 |
|
146 char* thresholdenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD"); |
|
147 if (thresholdenv && *thresholdenv) { |
|
148 int val = atoi(thresholdenv); |
|
149 if (val != 0 && val != INT_MAX && val != INT_MIN) { |
|
150 threshold = PR_MillisecondsToInterval(val); |
|
151 } |
|
152 } |
|
153 |
|
154 char* intervalenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL"); |
|
155 if (intervalenv && *intervalenv) { |
|
156 int val = atoi(intervalenv); |
|
157 if (val != 0 && val != INT_MAX && val != INT_MIN) { |
|
158 interval = PR_MillisecondsToInterval(val); |
|
159 } |
|
160 } |
|
161 |
|
162 if (threadArgs->mLogTracing) { |
|
163 long long now = PR_Now() / PR_USEC_PER_MSEC; |
|
164 fprintf(log, "MOZ_EVENT_TRACE start %llu\n", now); |
|
165 } |
|
166 |
|
167 while (!sExit) { |
|
168 TimeStamp start(TimeStamp::Now()); |
|
169 profiler_responsiveness(start); |
|
170 PRIntervalTime next_sleep = interval; |
|
171 |
|
172 //TODO: only wait up to a maximum of interval; return |
|
173 // early if that threshold is exceeded and dump a stack trace |
|
174 // or do something else useful. |
|
175 if (FireAndWaitForTracerEvent()) { |
|
176 TimeDuration duration = TimeStamp::Now() - start; |
|
177 // Only report samples that exceed our measurement threshold. |
|
178 long long now = PR_Now() / PR_USEC_PER_MSEC; |
|
179 if (threadArgs->mLogTracing && duration.ToMilliseconds() > threshold) { |
|
180 fprintf(log, "MOZ_EVENT_TRACE sample %llu %lf\n", |
|
181 now, |
|
182 duration.ToMilliseconds()); |
|
183 #ifdef MOZ_WIDGET_GONK |
|
184 NS_DispatchToMainThread( |
|
185 new EventLoopLagDispatcher(int(duration.ToSecondsSigDigits() * 1000))); |
|
186 #endif |
|
187 } |
|
188 |
|
189 if (next_sleep > duration.ToMilliseconds()) { |
|
190 next_sleep -= int(duration.ToMilliseconds()); |
|
191 } |
|
192 else { |
|
193 // Don't sleep at all if this event took longer than the measure |
|
194 // interval to deliver. |
|
195 next_sleep = 0; |
|
196 } |
|
197 } |
|
198 |
|
199 if (next_sleep != 0 && !sExit) { |
|
200 PR_Sleep(next_sleep); |
|
201 } |
|
202 } |
|
203 |
|
204 if (threadArgs->mLogTracing) { |
|
205 long long now = PR_Now() / PR_USEC_PER_MSEC; |
|
206 fprintf(log, "MOZ_EVENT_TRACE stop %llu\n", now); |
|
207 } |
|
208 |
|
209 if (log != stdout) |
|
210 fclose(log); |
|
211 |
|
212 delete threadArgs; |
|
213 } |
|
214 |
|
215 } // namespace |
|
216 |
|
217 namespace mozilla { |
|
218 |
|
219 bool InitEventTracing(bool aLog) |
|
220 { |
|
221 if (sTracerThread) |
|
222 return true; |
|
223 |
|
224 // Initialize the widget backend. |
|
225 if (!InitWidgetTracing()) |
|
226 return false; |
|
227 |
|
228 // The tracer thread owns the object and will delete it. |
|
229 TracerStartClosure* args = new TracerStartClosure(); |
|
230 args->mLogTracing = aLog; |
|
231 |
|
232 // Pass the default threshold interval. |
|
233 int32_t thresholdInterval = 20; |
|
234 Preferences::GetInt("devtools.eventlooplag.threshold", &thresholdInterval); |
|
235 args->mThresholdInterval = thresholdInterval; |
|
236 |
|
237 // Create a thread that will fire events back at the |
|
238 // main thread to measure responsiveness. |
|
239 NS_ABORT_IF_FALSE(!sTracerThread, "Event tracing already initialized!"); |
|
240 sTracerThread = PR_CreateThread(PR_USER_THREAD, |
|
241 TracerThread, |
|
242 args, |
|
243 PR_PRIORITY_NORMAL, |
|
244 PR_GLOBAL_THREAD, |
|
245 PR_JOINABLE_THREAD, |
|
246 0); |
|
247 return sTracerThread != nullptr; |
|
248 } |
|
249 |
|
250 void ShutdownEventTracing() |
|
251 { |
|
252 if (!sTracerThread) |
|
253 return; |
|
254 |
|
255 sExit = true; |
|
256 // Ensure that the tracer thread doesn't hang. |
|
257 SignalTracerThread(); |
|
258 |
|
259 if (sTracerThread) |
|
260 PR_JoinThread(sTracerThread); |
|
261 sTracerThread = nullptr; |
|
262 |
|
263 // Allow the widget backend to clean up. |
|
264 CleanUpWidgetTracing(); |
|
265 } |
|
266 |
|
267 } // namespace mozilla |