summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Stopwatch.h
blob: a1b8bbbcb3db9ddda2fef9cae18ac2651a5858cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_Stopwatch_h
#define vm_Stopwatch_h

#include "mozilla/RefPtr.h"
#include "mozilla/Vector.h"

#include "jsapi.h"

/*
  An API for following in real-time the amount of CPU spent executing
  webpages, add-ons, etc.
*/

namespace js {

/**
 * A container for performance groups.
 *
 * Performance monitoring deals with the execution duration of code
 * that belongs to components, for a notion of components defined by
 * the embedding.  Typically, in a web browser, a component may be a
 * webpage and/or a frame and/or a module and/or an add-on and/or a
 * sandbox and/or a process etc.
 *
 * A PerformanceGroupHolder is owned y a JSCompartment and maps that
 * compartment to all the components to which it belongs.
 */
struct PerformanceGroupHolder {

    /**
     * Get the groups to which this compartment belongs.
     *
     * Pre-condition: Execution must have entered the compartment.
     *
     * May return `nullptr` if the embedding has not initialized
     * support for performance groups.
     */
    const PerformanceGroupVector* getGroups(JSContext*);

    explicit PerformanceGroupHolder(JSRuntime* runtime)
      : runtime_(runtime)
      , initialized_(false)
    {  }
    ~PerformanceGroupHolder();
    void unlink();
  private:
    JSRuntime* runtime_;

    // `true` once a call to `getGroups` has succeeded.
    bool initialized_;

    // The groups to which this compartment belongs. Filled if and only
    // if `initialized_` is `true`.
    PerformanceGroupVector groups_;
};

/**
 * Container class for everything related to performance monitoring.
 */
struct PerformanceMonitoring {
    /**
     * The number of the current iteration of the event loop.
     */
    uint64_t iteration() {
        return iteration_;
    }

    explicit PerformanceMonitoring(JSRuntime* runtime)
      : totalCPOWTime(0)
      , stopwatchStartCallback(nullptr)
      , stopwatchStartClosure(nullptr)
      , stopwatchCommitCallback(nullptr)
      , stopwatchCommitClosure(nullptr)
      , getGroupsCallback(nullptr)
      , getGroupsClosure(nullptr)
      , isMonitoringJank_(false)
      , isMonitoringCPOW_(false)
      , iteration_(0)
      , startedAtIteration_(0)
      , highestTimestampCounter_(0)
    { }

    /**
     * Reset the stopwatch.
     *
     * This method is meant to be called whenever we start
     * processing an event, to ensure that we stop any ongoing
     * measurement that would otherwise provide irrelevant
     * results.
     */
    void reset();

    /**
     * Start the stopwatch.
     *
     * This method is meant to be called once we know that the
     * current event contains JavaScript code to execute. Calling
     * this several times during the same iteration is idempotent.
     */
    void start();

    /**
     * Commit the performance data collected since the last call
     * to `start()`, unless `reset()` has been called since then.
     */
    bool commit();

    /**
     * Liberate memory and references.
     */
    void dispose(JSRuntime* rtx);

    /**
     * Activate/deactivate stopwatch measurement of jank.
     *
     * Noop if `value` is `true` and the stopwatch is already
     * measuring jank, or if `value` is `false` and the stopwatch
     * is not measuring jank.
     *
     * Otherwise, any pending measurements are dropped, but previous
     * measurements remain stored.
     *
     * May return `false` if the underlying hashtable cannot be allocated.
     */
    bool setIsMonitoringJank(bool value) {
        if (isMonitoringJank_ != value)
            reset();

        isMonitoringJank_ = value;
        return true;
    }
    bool isMonitoringJank() const {
        return isMonitoringJank_;
    }

    /**
     * Mark that a group has been used in this iteration.
     */
    bool addRecentGroup(PerformanceGroup* group);

    /**
     * Activate/deactivate stopwatch measurement of CPOW.
     *
     * Noop if `value` is `true` and the stopwatch is already
     * measuring CPOW, or if `value` is `false` and the stopwatch
     * is not measuring CPOW.
     *
     * Otherwise, any pending measurements are dropped, but previous
     * measurements remain stored.
     *
     * May return `false` if the underlying hashtable cannot be allocated.
     */
    bool setIsMonitoringCPOW(bool value) {
        if (isMonitoringCPOW_ != value)
            reset();

        isMonitoringCPOW_ = value;
        return true;
    }

    bool isMonitoringCPOW() const {
        return isMonitoringCPOW_;
    }

    /**
     * Callbacks called when we start executing an event/when we have
     * run to completion (including enqueued microtasks).
     *
     * If there are no nested event loops, each call to
     * `stopwatchStartCallback` is followed by a call to
     * `stopwatchCommitCallback`. However, embedders should not assume
     * that this will always be the case, unless they take measures to
     * prevent nested event loops.
     *
     * In presence of nested event loops, several calls to
     * `stopwatchStartCallback` may occur before a call to
     * `stopwatchCommitCallback`. Embedders should assume that a
     * second call to `stopwatchStartCallback` cancels any measure
     * started by the previous calls to `stopwatchStartCallback` and
     * which have not been committed by `stopwatchCommitCallback`.
     */
    void setStopwatchStartCallback(js::StopwatchStartCallback cb, void* closure) {
        stopwatchStartCallback = cb;
        stopwatchStartClosure = closure;
    }
    void setStopwatchCommitCallback(js::StopwatchCommitCallback cb, void* closure) {
        stopwatchCommitCallback = cb;
        stopwatchCommitClosure = closure;
    }

    /**
     * Callback called to associate a JSCompartment to the set of
     * `PerformanceGroup`s that represent the components to which
     * it belongs.
     */
    void setGetGroupsCallback(js::GetGroupsCallback cb, void* closure) {
        getGroupsCallback = cb;
        getGroupsClosure = closure;
    }

    /**
     * The total amount of time spent waiting on CPOWs since the
     * start of the process, in microseconds.
     */
    uint64_t totalCPOWTime;

    /**
     * A variant of RDTSC artificially made monotonic.
     *
     * Always return 0 on platforms that do not support RDTSC.
     */
    uint64_t monotonicReadTimestampCounter();

    /**
     * Data extracted by the AutoStopwatch to determine how often
     * we reschedule the process to a different CPU during the
     * execution of JS.
     *
     * Warning: These values are incremented *only* on platforms
     * that offer a syscall/libcall to check on which CPU a
     * process is currently executed.
     */
    struct TestCpuRescheduling
    {
        // Incremented once we have finished executing code
        // in a group, if the CPU on which we started
        // execution is the same as the CPU on which
        // we finished.
        uint64_t stayed;
        // Incremented once we have finished executing code
        // in a group, if the CPU on which we started
        // execution is different from the CPU on which
        // we finished.
        uint64_t moved;
        TestCpuRescheduling()
            : stayed(0),
              moved(0)
        { }
    };
    TestCpuRescheduling testCpuRescheduling;
  private:
    PerformanceMonitoring(const PerformanceMonitoring&) = delete;
    PerformanceMonitoring& operator=(const PerformanceMonitoring&) = delete;

  private:
    friend struct PerformanceGroupHolder;
    js::StopwatchStartCallback stopwatchStartCallback;
    void* stopwatchStartClosure;
    js::StopwatchCommitCallback stopwatchCommitCallback;
    void* stopwatchCommitClosure;

    js::GetGroupsCallback getGroupsCallback;
    void* getGroupsClosure;

    /**
     * `true` if stopwatch monitoring is active for Jank, `false` otherwise.
     */
    bool isMonitoringJank_;
    /**
     * `true` if stopwatch monitoring is active for CPOW, `false` otherwise.
     */
    bool isMonitoringCPOW_;

    /**
     * The number of times we have entered the event loop.
     * Used to reset counters whenever we enter the loop,
     * which may be caused either by having completed the
     * previous run of the event loop, or by entering a
     * nested loop.
     *
     * Always incremented by 1, may safely overflow.
     */
    uint64_t iteration_;

    /**
     * The iteration at which the stopwatch was last started.
     *
     * Used both to avoid starting the stopwatch several times
     * during the same event loop and to avoid committing stale
     * stopwatch results.
     */
    uint64_t startedAtIteration_;

    /**
     * Groups used in the current iteration.
     */
    PerformanceGroupVector recentGroups_;

    /**
     * The highest value of the timestamp counter encountered
     * during this iteration.
     */
    uint64_t highestTimestampCounter_;
};

#if WINVER >= 0x0600
struct cpuid_t {
    WORD group_;
    BYTE number_;
    cpuid_t(WORD group, BYTE number)
        : group_(group),
          number_(number)
    { }
    cpuid_t()
        : group_(0),
          number_(0)
    { }
};
#else
    typedef struct {} cpuid_t;
#endif // defined(WINVER >= 0x0600)

/**
 * RAII class to start/stop measuring performance when
 * entering/leaving a compartment.
 */
class AutoStopwatch final {
    // The context with which this object was initialized.
    // Non-null.
    JSContext* const cx_;

    // An indication of the number of times we have entered the event
    // loop.  Used only for comparison.
    uint64_t iteration_;

    // `true` if we are monitoring jank, `false` otherwise.
    bool isMonitoringJank_;
    // `true` if we are monitoring CPOW, `false` otherwise.
    bool isMonitoringCPOW_;

    // Timestamps captured while starting the stopwatch.
    uint64_t cyclesStart_;
    uint64_t CPOWTimeStart_;

    // The CPU on which we started the measure. Defined only
    // if `isMonitoringJank_` is `true`.
    cpuid_t cpuStart_;

    PerformanceGroupVector groups_;

  public:
    // If the stopwatch is active, constructing an instance of
    // AutoStopwatch causes it to become the current owner of the
    // stopwatch.
    //
    // Previous owner is restored upon destruction.
    explicit AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
    ~AutoStopwatch();
  private:
    void inline enter();

    bool inline exit();

    // Attempt to acquire a group
    // If the group is inactive or if the group already has a stopwatch,
    // do nothing and return `null`.
    // Otherwise, bind the group to `this` for the current iteration
    // and return `group`.
    PerformanceGroup* acquireGroup(PerformanceGroup* group);

    // Release a group. Noop if `this` is not the stopwatch of
    // `group` for the current iteration.
    void releaseGroup(PerformanceGroup* group);

    // Add recent changes to all the groups owned by this stopwatch.
    // Mark the groups as changed recently.
    bool addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta);

    // Add recent changes to a single group. Mark the group as changed recently.
    bool addToGroup(JSRuntime* runtime, uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group);

    // Update telemetry statistics.
    void updateTelemetry(const cpuid_t& a, const cpuid_t& b);

    // Perform a subtraction for a quantity that should be monotonic
    // but is not guaranteed to be so.
    //
    // If `start <= end`, return `end - start`.
    // Otherwise, return `0`.
    uint64_t inline getDelta(const uint64_t end, const uint64_t start) const;

    // Return the value of the Timestamp Counter, as provided by the CPU.
    // 0 on platforms for which we do not have access to a Timestamp Counter.
    uint64_t inline getCycles(JSRuntime*) const;


    // Return the identifier of the current CPU, on platforms for which we have
    // access to the current CPU.
    cpuid_t inline getCPU() const;

    // Compare two CPU identifiers.
    bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const;
  private:
    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
};


} // namespace js

#endif // vm_Stopwatch_h