diff options
Diffstat (limited to 'js/src/vm/Stopwatch.h')
-rw-r--r-- | js/src/vm/Stopwatch.h | 406 |
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 |