| /* -*- 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 { |
| |
| typedef mozilla::Vector<RefPtr<js::PerformanceGroup>> GroupVector; |
| |
| /** |
| * 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 GroupVector* 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`. |
| GroupVector 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) |
| { } |
| |
| /** |
| * 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; |
| |
| /** |
| * 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. |
| */ |
| GroupVector recentGroups_; |
| }; |
| |
| #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) |
| { } |
| }; |
| #elif defined(__linux__) |
| typedef int cpuid_t; |
| #else |
| typedef struct {} cpuid_t; |
| #endif // defined(WINVER >= 0x0600) || defined(__linux__) |
| |
| /** |
| * 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_; |
| |
| mozilla::Vector<RefPtr<js::PerformanceGroup>> 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() 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 |