blob: 9c5729334a75441f246548c10bd81aa3d5994654 [file] [log] [blame]
/* -*- 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