376 lines
11 KiB
C++
376 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* 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();
|
|
|
|
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 {
|
|
uint16_t group_;
|
|
uint8_t number_;
|
|
cpuid_t(uint16_t group, uint8_t 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);
|
|
|
|
// 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
|