summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Stopwatch.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/Stopwatch.h')
-rw-r--r--js/src/vm/Stopwatch.h406
1 files changed, 406 insertions, 0 deletions
diff --git a/js/src/vm/Stopwatch.h b/js/src/vm/Stopwatch.h
new file mode 100644
index 000000000..a1b8bbbcb
--- /dev/null
+++ b/js/src/vm/Stopwatch.h
@@ -0,0 +1,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