Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef vm_Stopwatch_h
8 : #define vm_Stopwatch_h
9 :
10 : #include "mozilla/RefPtr.h"
11 : #include "mozilla/Vector.h"
12 :
13 : #include "jsapi.h"
14 :
15 : /*
16 : An API for following in real-time the amount of CPU spent executing
17 : webpages, add-ons, etc.
18 : */
19 :
20 : namespace js {
21 :
22 : /**
23 : * A container for performance groups.
24 : *
25 : * Performance monitoring deals with the execution duration of code
26 : * that belongs to components, for a notion of components defined by
27 : * the embedding. Typically, in a web browser, a component may be a
28 : * webpage and/or a frame and/or a module and/or an add-on and/or a
29 : * sandbox and/or a process etc.
30 : *
31 : * A PerformanceGroupHolder is owned y a JSCompartment and maps that
32 : * compartment to all the components to which it belongs.
33 : */
34 : struct PerformanceGroupHolder {
35 :
36 : /**
37 : * Get the groups to which this compartment belongs.
38 : *
39 : * Pre-condition: Execution must have entered the compartment.
40 : *
41 : * May return `nullptr` if the embedding has not initialized
42 : * support for performance groups.
43 : */
44 : const PerformanceGroupVector* getGroups(JSContext*);
45 :
46 315 : explicit PerformanceGroupHolder(JSRuntime* runtime)
47 315 : : runtime_(runtime)
48 315 : , initialized_(false)
49 315 : { }
50 : ~PerformanceGroupHolder();
51 : void unlink();
52 : private:
53 : JSRuntime* runtime_;
54 :
55 : // `true` once a call to `getGroups` has succeeded.
56 : bool initialized_;
57 :
58 : // The groups to which this compartment belongs. Filled if and only
59 : // if `initialized_` is `true`.
60 : PerformanceGroupVector groups_;
61 : };
62 :
63 : /**
64 : * Container class for everything related to performance monitoring.
65 : */
66 0 : struct PerformanceMonitoring {
67 : /**
68 : * The number of the current iteration of the event loop.
69 : */
70 17386 : uint64_t iteration() {
71 17386 : return iteration_;
72 : }
73 :
74 4 : explicit PerformanceMonitoring(JSRuntime* runtime)
75 4 : : totalCPOWTime(0)
76 : , stopwatchStartCallback(nullptr)
77 : , stopwatchStartClosure(nullptr)
78 : , stopwatchCommitCallback(nullptr)
79 : , stopwatchCommitClosure(nullptr)
80 : , getGroupsCallback(nullptr)
81 : , getGroupsClosure(nullptr)
82 : , isMonitoringJank_(false)
83 : , isMonitoringCPOW_(false)
84 : , iteration_(0)
85 : , startedAtIteration_(0)
86 4 : , highestTimestampCounter_(0)
87 4 : { }
88 :
89 : /**
90 : * Reset the stopwatch.
91 : *
92 : * This method is meant to be called whenever we start
93 : * processing an event, to ensure that we stop any ongoing
94 : * measurement that would otherwise provide irrelevant
95 : * results.
96 : */
97 : void reset();
98 :
99 : /**
100 : * Start the stopwatch.
101 : *
102 : * This method is meant to be called once we know that the
103 : * current event contains JavaScript code to execute. Calling
104 : * this several times during the same iteration is idempotent.
105 : */
106 : void start();
107 :
108 : /**
109 : * Commit the performance data collected since the last call
110 : * to `start()`, unless `reset()` has been called since then.
111 : */
112 : bool commit();
113 :
114 : /**
115 : * Liberate memory and references.
116 : */
117 : void dispose(JSRuntime* rtx);
118 :
119 : /**
120 : * Activate/deactivate stopwatch measurement of jank.
121 : *
122 : * Noop if `value` is `true` and the stopwatch is already
123 : * measuring jank, or if `value` is `false` and the stopwatch
124 : * is not measuring jank.
125 : *
126 : * Otherwise, any pending measurements are dropped, but previous
127 : * measurements remain stored.
128 : *
129 : * May return `false` if the underlying hashtable cannot be allocated.
130 : */
131 0 : bool setIsMonitoringJank(bool value) {
132 0 : if (isMonitoringJank_ != value)
133 0 : reset();
134 :
135 0 : isMonitoringJank_ = value;
136 0 : return true;
137 : }
138 0 : bool isMonitoringJank() const {
139 0 : return isMonitoringJank_;
140 : }
141 :
142 : /**
143 : * Mark that a group has been used in this iteration.
144 : */
145 : bool addRecentGroup(PerformanceGroup* group);
146 :
147 : /**
148 : * Activate/deactivate stopwatch measurement of CPOW.
149 : *
150 : * Noop if `value` is `true` and the stopwatch is already
151 : * measuring CPOW, or if `value` is `false` and the stopwatch
152 : * is not measuring CPOW.
153 : *
154 : * Otherwise, any pending measurements are dropped, but previous
155 : * measurements remain stored.
156 : *
157 : * May return `false` if the underlying hashtable cannot be allocated.
158 : */
159 0 : bool setIsMonitoringCPOW(bool value) {
160 0 : if (isMonitoringCPOW_ != value)
161 0 : reset();
162 :
163 0 : isMonitoringCPOW_ = value;
164 0 : return true;
165 : }
166 :
167 0 : bool isMonitoringCPOW() const {
168 0 : return isMonitoringCPOW_;
169 : }
170 :
171 : /**
172 : * Callbacks called when we start executing an event/when we have
173 : * run to completion (including enqueued microtasks).
174 : *
175 : * If there are no nested event loops, each call to
176 : * `stopwatchStartCallback` is followed by a call to
177 : * `stopwatchCommitCallback`. However, embedders should not assume
178 : * that this will always be the case, unless they take measures to
179 : * prevent nested event loops.
180 : *
181 : * In presence of nested event loops, several calls to
182 : * `stopwatchStartCallback` may occur before a call to
183 : * `stopwatchCommitCallback`. Embedders should assume that a
184 : * second call to `stopwatchStartCallback` cancels any measure
185 : * started by the previous calls to `stopwatchStartCallback` and
186 : * which have not been committed by `stopwatchCommitCallback`.
187 : */
188 0 : void setStopwatchStartCallback(js::StopwatchStartCallback cb, void* closure) {
189 0 : stopwatchStartCallback = cb;
190 0 : stopwatchStartClosure = closure;
191 0 : }
192 0 : void setStopwatchCommitCallback(js::StopwatchCommitCallback cb, void* closure) {
193 0 : stopwatchCommitCallback = cb;
194 0 : stopwatchCommitClosure = closure;
195 0 : }
196 :
197 : /**
198 : * Callback called to associate a JSCompartment to the set of
199 : * `PerformanceGroup`s that represent the components to which
200 : * it belongs.
201 : */
202 0 : void setGetGroupsCallback(js::GetGroupsCallback cb, void* closure) {
203 0 : getGroupsCallback = cb;
204 0 : getGroupsClosure = closure;
205 0 : }
206 :
207 : /**
208 : * The total amount of time spent waiting on CPOWs since the
209 : * start of the process, in microseconds.
210 : */
211 : uint64_t totalCPOWTime;
212 :
213 : /**
214 : * A variant of RDTSC artificially made monotonic.
215 : *
216 : * Always return 0 on platforms that do not support RDTSC.
217 : */
218 : uint64_t monotonicReadTimestampCounter();
219 :
220 : /**
221 : * Data extracted by the AutoStopwatch to determine how often
222 : * we reschedule the process to a different CPU during the
223 : * execution of JS.
224 : *
225 : * Warning: These values are incremented *only* on platforms
226 : * that offer a syscall/libcall to check on which CPU a
227 : * process is currently executed.
228 : */
229 : struct TestCpuRescheduling
230 : {
231 : // Incremented once we have finished executing code
232 : // in a group, if the CPU on which we started
233 : // execution is the same as the CPU on which
234 : // we finished.
235 : uint64_t stayed;
236 : // Incremented once we have finished executing code
237 : // in a group, if the CPU on which we started
238 : // execution is different from the CPU on which
239 : // we finished.
240 : uint64_t moved;
241 4 : TestCpuRescheduling()
242 4 : : stayed(0),
243 4 : moved(0)
244 4 : { }
245 : };
246 : TestCpuRescheduling testCpuRescheduling;
247 : private:
248 : PerformanceMonitoring(const PerformanceMonitoring&) = delete;
249 : PerformanceMonitoring& operator=(const PerformanceMonitoring&) = delete;
250 :
251 : private:
252 : friend struct PerformanceGroupHolder;
253 : js::StopwatchStartCallback stopwatchStartCallback;
254 : void* stopwatchStartClosure;
255 : js::StopwatchCommitCallback stopwatchCommitCallback;
256 : void* stopwatchCommitClosure;
257 :
258 : js::GetGroupsCallback getGroupsCallback;
259 : void* getGroupsClosure;
260 :
261 : /**
262 : * `true` if stopwatch monitoring is active for Jank, `false` otherwise.
263 : */
264 : bool isMonitoringJank_;
265 : /**
266 : * `true` if stopwatch monitoring is active for CPOW, `false` otherwise.
267 : */
268 : bool isMonitoringCPOW_;
269 :
270 : /**
271 : * The number of times we have entered the event loop.
272 : * Used to reset counters whenever we enter the loop,
273 : * which may be caused either by having completed the
274 : * previous run of the event loop, or by entering a
275 : * nested loop.
276 : *
277 : * Always incremented by 1, may safely overflow.
278 : */
279 : uint64_t iteration_;
280 :
281 : /**
282 : * The iteration at which the stopwatch was last started.
283 : *
284 : * Used both to avoid starting the stopwatch several times
285 : * during the same event loop and to avoid committing stale
286 : * stopwatch results.
287 : */
288 : uint64_t startedAtIteration_;
289 :
290 : /**
291 : * Groups used in the current iteration.
292 : */
293 : PerformanceGroupVector recentGroups_;
294 :
295 : /**
296 : * The highest value of the timestamp counter encountered
297 : * during this iteration.
298 : */
299 : uint64_t highestTimestampCounter_;
300 : };
301 :
302 : // Temporary disable untested code path.
303 : #if 0 // WINVER >= 0x0600
304 : struct cpuid_t {
305 : uint16_t group_;
306 : uint8_t number_;
307 : cpuid_t(uint16_t group, uint8_t number)
308 : : group_(group),
309 : number_(number)
310 : { }
311 : cpuid_t()
312 : : group_(0),
313 : number_(0)
314 : { }
315 : };
316 : #else
317 : typedef struct {} cpuid_t;
318 : #endif // defined(WINVER >= 0x0600)
319 :
320 : /**
321 : * RAII class to start/stop measuring performance when
322 : * entering/leaving a compartment.
323 : */
324 : class AutoStopwatch final {
325 : // The context with which this object was initialized.
326 : // Non-null.
327 : JSContext* const cx_;
328 :
329 : // An indication of the number of times we have entered the event
330 : // loop. Used only for comparison.
331 : uint64_t iteration_;
332 :
333 : // `true` if we are monitoring jank, `false` otherwise.
334 : bool isMonitoringJank_;
335 : // `true` if we are monitoring CPOW, `false` otherwise.
336 : bool isMonitoringCPOW_;
337 :
338 : // Timestamps captured while starting the stopwatch.
339 : uint64_t cyclesStart_;
340 : uint64_t CPOWTimeStart_;
341 :
342 : // The CPU on which we started the measure. Defined only
343 : // if `isMonitoringJank_` is `true`.
344 : cpuid_t cpuStart_;
345 :
346 : PerformanceGroupVector groups_;
347 :
348 : public:
349 : // If the stopwatch is active, constructing an instance of
350 : // AutoStopwatch causes it to become the current owner of the
351 : // stopwatch.
352 : //
353 : // Previous owner is restored upon destruction.
354 : explicit AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
355 : ~AutoStopwatch();
356 : private:
357 : void inline enter();
358 :
359 : bool inline exit();
360 :
361 : // Attempt to acquire a group
362 : // If the group is inactive or if the group already has a stopwatch,
363 : // do nothing and return `null`.
364 : // Otherwise, bind the group to `this` for the current iteration
365 : // and return `group`.
366 : PerformanceGroup* acquireGroup(PerformanceGroup* group);
367 :
368 : // Release a group. Noop if `this` is not the stopwatch of
369 : // `group` for the current iteration.
370 : void releaseGroup(PerformanceGroup* group);
371 :
372 : // Add recent changes to all the groups owned by this stopwatch.
373 : // Mark the groups as changed recently.
374 : bool addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta);
375 :
376 : // Add recent changes to a single group. Mark the group as changed recently.
377 : bool addToGroup(JSRuntime* runtime, uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group);
378 :
379 : // Update telemetry statistics.
380 : void updateTelemetry(const cpuid_t& a, const cpuid_t& b);
381 :
382 : // Perform a subtraction for a quantity that should be monotonic
383 : // but is not guaranteed to be so.
384 : //
385 : // If `start <= end`, return `end - start`.
386 : // Otherwise, return `0`.
387 : uint64_t inline getDelta(const uint64_t end, const uint64_t start) const;
388 :
389 : // Return the value of the Timestamp Counter, as provided by the CPU.
390 : // 0 on platforms for which we do not have access to a Timestamp Counter.
391 : uint64_t inline getCycles(JSRuntime*) const;
392 :
393 :
394 : // Return the identifier of the current CPU, on platforms for which we have
395 : // access to the current CPU.
396 : cpuid_t inline getCPU() const;
397 :
398 : // Compare two CPU identifiers.
399 : bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const;
400 : private:
401 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
402 : };
403 :
404 :
405 : } // namespace js
406 :
407 : #endif // vm_Stopwatch_h
|