Bug 674779 - Add per-compartment CPU accounting. r=jandem, r=blassey, r=bz

This commit is contained in:
David Rajchenbach-Teller 2015-02-26 11:53:41 +01:00
parent a2edd2efd6
commit 2559ae292e
24 changed files with 851 additions and 116 deletions

View File

@ -19,9 +19,11 @@ CPOWTimer::~CPOWTimer()
if (!obj)
return;
JSCompartment *compartment = js::GetObjectCompartment(obj);
xpc::CompartmentPrivate *compartmentPrivate = xpc::CompartmentPrivate::Get(compartment);
if (!compartmentPrivate)
if (!compartment)
return;
PRIntervalTime time = PR_IntervalNow() - startInterval;
compartmentPrivate->CPOWTime += time;
js::PerformanceData *performance = js::GetPerformanceData(compartment);
if (!performance)
return;
uint64_t time = PR_IntervalToMicroseconds(PR_IntervalNow() - startInterval);
performance->cpowTime += time;
}

View File

@ -35,6 +35,7 @@ LOCAL_INCLUDES += [
'/dom/base',
'/js/ipc',
'/js/public',
'/js/src',
'/js/xpconnect/src',
'/js/xpconnect/wrappers',
]

View File

@ -173,8 +173,9 @@ JS_GetCompartmentStats(JSRuntime *rt, CompartmentStatsVector &stats)
return false;
CompartmentTimeStats *stat = &stats.back();
stat->time = c.get()->totalTime;
stat->compartment = c.get();
stat->performance = stat->compartment->performance;
stat->isSystem = stat->compartment->isSystem;
stat->addonId = c.get()->addonId;
if (rt->compartmentNameCallback) {
(*rt->compartmentNameCallback)(rt, stat->compartment,
@ -5801,3 +5802,11 @@ JS::GetObjectZone(JSObject *obj)
{
return obj->zone();
}
JS_PUBLIC_API(PerformanceData*)
js::GetPerformanceData(JSCompartment *c)
{
if (!c)
return nullptr;
return &c->performance;
}

View File

@ -975,8 +975,8 @@ struct CompartmentTimeStats {
char compartmentName[1024];
JSAddonId *addonId;
JSCompartment *compartment;
uint64_t time; // microseconds
uint64_t cpowTime; // microseconds
bool isSystem;
js::PerformanceData performance;
};
typedef js::Vector<CompartmentTimeStats, 0, js::SystemAllocPolicy> CompartmentStatsVector;
@ -5126,4 +5126,33 @@ StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString
} /* namespace JS */
/* Stopwatch-based CPU monitoring. */
namespace js {
/**
* Reset any stopwatch currently measuring.
*
* This function is designed to be called when we process a new event.
*/
extern JS_PUBLIC_API(void)
ResetStopwatches(JSRuntime*);
/**
* Turn on/off stopwatch-based CPU monitoring.
*/
extern JS_PUBLIC_API(void)
SetStopwatchActive(JSRuntime*, bool);
extern JS_PUBLIC_API(bool)
IsStopwatchActive(JSRuntime*);
/**
* Access the performance information stored in a compartment.
*/
extern JS_PUBLIC_API(PerformanceData*)
GetPerformanceData(JSCompartment*);
} /* namespace js */
#endif /* jsapi_h */

View File

@ -50,7 +50,6 @@ JSCompartment::JSCompartment(Zone *zone, const JS::CompartmentOptions &options =
#endif
global_(nullptr),
enterCompartmentDepth(0),
totalTime(0),
data(nullptr),
objectMetadataCallback(nullptr),
lastAnimationTime(0),

View File

@ -170,18 +170,12 @@ struct JSCompartment
int64_t startInterval;
public:
int64_t totalTime;
js::PerformanceData performance;
void enter() {
if (addonId && !enterCompartmentDepth) {
startInterval = PRMJ_Now();
}
enterCompartmentDepth++;
}
void leave() {
enterCompartmentDepth--;
if (addonId && !enterCompartmentDepth) {
totalTime += (PRMJ_Now() - startInterval);
}
}
bool hasBeenEntered() { return !!enterCompartmentDepth; }

View File

@ -479,6 +479,60 @@ struct PerThreadDataFriendFields
template <typename T> friend class JS::Rooted;
};
// Container for performance data
struct PerformanceData {
// Jank indicator.
//
// missedFrames[i] == number of times execution of this
// compartment caused us to miss at least 2^i successive frames -
// we assume that a frame lasts 16ms.
uint64_t missedFrames[8];
// Total amount of time spent executing code in this compartment,
// in microseconds, since process launch.
uint64_t totalUserTime;
uint64_t totalSystemTime;
uint64_t ownUserTime;
uint64_t ownSystemTime;
uint64_t cpowTime;
// Total number of time code was executed in this compartment,
// since process launch.
uint64_t visits;
PerformanceData()
: totalUserTime(0)
, totalSystemTime(0)
, ownUserTime(0)
, ownSystemTime(0)
, cpowTime(0)
, visits(0)
{
memset(missedFrames, 0, sizeof(missedFrames));
}
PerformanceData(const PerformanceData& from)
: totalUserTime(from.totalUserTime)
, totalSystemTime(from.totalSystemTime)
, ownUserTime(from.ownUserTime)
, ownSystemTime(from.ownSystemTime)
, cpowTime(from.cpowTime)
, visits(from.visits)
{
memcpy(missedFrames, from.missedFrames, sizeof(missedFrames));
}
PerformanceData& operator=(const PerformanceData& from)
{
memcpy(missedFrames, from.missedFrames, sizeof(missedFrames));
totalUserTime = from.totalUserTime;
totalSystemTime = from.totalSystemTime;
ownUserTime = from.ownUserTime;
ownSystemTime = from.ownSystemTime;
cpowTime = from.cpowTime;
visits = from.visits;
return *this;
}
};
} /* namespace js */
#endif /* jspubtd_h */

View File

@ -10,6 +10,7 @@
#include "vm/Interpreter-inl.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/PodOperations.h"
@ -52,6 +53,13 @@
#include "vm/ScopeObject-inl.h"
#include "vm/Stack-inl.h"
#if defined(XP_UNIX)
#include <sys/resource.h>
#elif defined(XP_WIN)
#include <Processthreadsapi.h>
#include <Windows.h>
#endif // defined(XP_UNIX) || defined(XP_WIN)
using namespace js;
using namespace js::gc;
@ -411,11 +419,191 @@ ExecuteState::pushInterpreterFrame(JSContext *cx)
type_, evalInFrame_);
}
namespace js {
// Implementation of per-compartment performance measurement.
//
//
// All mutable state is stored in `Runtime::stopwatch`.
struct AutoStopwatch MOZ_FINAL
{
// 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 inline AutoStopwatch(JSContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: parent_(nullptr)
, context_(cx)
, descendentsUserTime_(0)
, descendentsSystemTime_(0)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JSRuntime *runtime = context_->runtime();
if (!runtime->stopwatch.isActive)
return;
#if defined(XP_UNIX)
int err = getrusage(RUSAGE_SELF, &timeStamp_);
if (err)
return;
#elif defined(XP_WIN)
FILETIME creationTime; // Ignored
FILETIME exitTime; // Ignored
BOOL success = GetProcessTimes(GetCurrentProcess(),
&creationTime, &exitTime,
&kernelTimeStamp_, &userTimeStamp_);
if (!success)
return;
#endif // defined(XP_UNIX) || defined(XP_WIN)
// Push on top of previous owner.
parent_ = runtime->stopwatch.owner;
runtime->stopwatch.owner = this;
}
// If the stopwatch is active, destructing an instance of
// AutoStopwatch causes ownership to return to the previous owner.
inline ~AutoStopwatch()
{
JSRuntime *runtime = context_->runtime();
if (runtime->stopwatch.owner != this) {
// We are not the owner of the stopwatch, either because
// we never acquired it, because we have entered a nested
// event loop, or because some stopwatch lower on the
// stack encountered an error.
// If there is any ongoing measure, discard it.
return;
}
// If this destructor cannot proceed to completion without error,
// prepare to cancel any ongoing measure.
runtime->stopwatch.owner = nullptr;
// Durations in museconds of the total user/system time spent
// by the entire process between construction and destruction
// of this object.
uint64_t totalUserTime, totalSystemTime;
#if defined(XP_UNIX)
struct rusage stop;
int err = getrusage(RUSAGE_SELF, &stop);
if (err)
return;
totalUserTime =
(stop.ru_utime.tv_usec - timeStamp_.ru_utime.tv_usec)
+ (stop.ru_utime.tv_sec - timeStamp_.ru_utime.tv_sec) * 1000000;
totalSystemTime =
(stop.ru_stime.tv_usec - timeStamp_.ru_stime.tv_usec)
+ (stop.ru_stime.tv_sec - timeStamp_.ru_stime.tv_sec) * 1000000;
#elif defined(XP_WIN)
FILETIME creationTime; // Ignored
FILETIME exitTime; // Ignored
FILETIME kernelTime;
FILETIME userTime;
BOOL success = GetProcessTimes(GetCurrentProcess(),
&creationTime, &exitTime,
&kernelTime, &userTime);
if (!success)
return;
// Convert values to a data structure that supports arithmetics.
ULARGE_INTEGER userTimeInt, userTimeStampInt;
userTimeInt.LowPart = userTime.dwLowDateTime;
userTimeInt.HighPart = userTime.dwHighDateTime;
userTimeStampInt.LowPart = userTimeStamp_.dwLowDateTime;
userTimeStampInt.HighPart = userTimeStamp_.dwHighDateTime;
totalUserTime = (userTimeInt.QuadPart - userTimeStampInt.QuadPart) / 10; // 100 ns to 1 mus
ULARGE_INTEGER kernelTimeInt, kernelTimeStampInt;
kernelTimeInt.LowPart = kernelTime.dwLowDateTime;
kernelTimeInt.HighPart = kernelTime.dwHighDateTime;
kernelTimeStampInt.LowPart = kernelTimeStamp_.dwLowDateTime;
kernelTimeStampInt.HighPart = kernelTimeStamp_.dwHighDateTime;
totalSystemTime = (kernelTimeInt.QuadPart - kernelTimeStampInt.QuadPart) / 10; // 100 ns to 1 mus
#endif // defined(XP_UNIX) || defined (XP_WIN)
// Process durations.
//
// Note that durations are per-process, while our measures attempt
// to be per-thread. In other words, the data we extract may be
// badly skewed by activity on other threads. We can only hope that
// things will eventually average out.
JSCompartment *compartment = context_->compartment();
compartment->performance.visits++;
compartment->performance.totalUserTime += totalUserTime;
compartment->performance.totalSystemTime += totalSystemTime;
uint64_t ownUserTime = totalUserTime - descendentsUserTime_;
uint64_t ownSystemTime = totalSystemTime - descendentsSystemTime_;
compartment->performance.ownUserTime += ownUserTime;
compartment->performance.ownSystemTime += ownSystemTime;
if (parent_) {
// The time we just spent executing code in this compartment
// should be substracted to determine the parent's own time.
parent_->descendentsUserTime_ += totalUserTime;
parent_->descendentsSystemTime_ += totalSystemTime;
}
uint64_t totalDuration = totalUserTime + totalSystemTime;
// Store performance information in the compartment
uint64_t missedFrames = 16 * 1000; // Duration of one frame, i.e. 16ms in museconds
for (size_t i = 0; i < ArrayLength(compartment->performance.missedFrames); ++i) {
if (totalDuration < missedFrames)
break;
compartment->performance.missedFrames[i]++;
missedFrames *= 2;
}
runtime->stopwatch.owner = parent_;
}
private:
// The AutoStopwatch for the caller compartment.
AutoStopwatch *parent_;
// The context with which this object was initialized.
JSContext *context_;
// Total time spent so far executing compartments that have been
// called by this compartment, in museconds. These values are
// updated upon destruction of callee AutoStopwatch instances.
uint64_t descendentsUserTime_;
uint64_t descendentsSystemTime_;
// Kernel and user time at the time of construction of this
// instance of AutoStopwatch. Unspecified if the stopwatch is not
// active.
#if defined(XP_UNIX)
struct rusage timeStamp_;
#elif defined(XP_WIN)
FILETIME kernelTimeStamp_;
FILETIME userTimeStamp_;
#endif // defined(XP_UNIX) || defined(XP_WIN)
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
}
bool
js::RunScript(JSContext *cx, RunState &state)
{
JS_CHECK_RECURSION(cx, return false);
#if defined(NIGHTLY_BUILD)
js::AutoStopwatch stopwatch(cx);
#endif // defined(NIGHTLY_BUILD)
SPSEntryMarker marker(cx->runtime(), state.script());
state.script()->ensureNonLazyCanonicalFunction(cx);

View File

@ -848,3 +848,21 @@ JS::UpdateJSRuntimeProfilerSampleBufferGen(JSRuntime *runtime, uint32_t generati
runtime->setProfilerSampleBufferGen(generation);
runtime->updateProfilerSampleBufferLapCount(lapCount);
}
void
js::ResetStopwatches(JSRuntime *rt)
{
rt->stopwatch.reset();
}
void
js::SetStopwatchActive(JSRuntime *rt, bool isActive)
{
rt->stopwatch.isActive = isActive;
}
bool
js::IsStopwatchActive(JSRuntime *rt)
{
return rt->stopwatch.isActive;
}

View File

@ -566,6 +566,7 @@ class PerThreadData : public PerThreadDataFriendFields
class AutoLockForExclusiveAccess;
struct AutoStopwatch;
} // namespace js
struct JSRuntime : public JS::shadow::Runtime,
@ -1420,6 +1421,53 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Last time at which an animation was played for this runtime. */
int64_t lastAnimationTime;
public:
/* ------------------------------------------
Performance measurements
------------------------------------------ */
struct Stopwatch {
/**
* The current owner of the stopwatch.
*
* This is `nullptr` if we have not started executing JS code
* in this tick yet, or if we are not using stopwatch
* monitoring. If monitoring is activated, whenever we
* instantiate an `AutoStopwatch`, it becomes the owner, and
* whenever we destruct it, it returns ownership to the
* previous owner.
*
* Note that `owner` is reset to `nullptr` if we start
* processing a nested event. By design, this has the
* side-effect of discarding any ongoing measurement that
* would otherwise be completely skewed by the nested event
* loop.
*/
js::AutoStopwatch *owner;
/**
* `true` if stopwatch monitoring is active, `false` otherwise.
*/
bool isActive;
Stopwatch()
: owner(nullptr)
, isActive(false)
{ }
/**
* Reset the stopwatch.
*
* This method is meant to be called whewnever we start processing
* an event, to ensure that stop any ongoing measurement that would
* otherwise provide irrelevant results.
*/
void reset() {
owner = nullptr;
}
};
Stopwatch stopwatch;
};
namespace js {

View File

@ -122,7 +122,7 @@ interface ScheduledGCCallback : nsISupports
/**
* interface of Components.utils
*/
[scriptable, uuid(2617a800-63c1-11e4-9803-0800200c9a66)]
[scriptable, uuid(9b153002-3bc6-4d83-b825-64d21b13a098)]
interface nsIXPCComponents_Utils : nsISupports
{
@ -679,6 +679,14 @@ interface nsIXPCComponents_Utils : nsISupports
* startup, measured with a monotonic clock.
*/
double now();
/*
* Whether we are currently monitoring CPU use on this thread.
*
* Note that CPU monitoring is rather expensive, so it generally
* not be active at all times.
*/
attribute boolean stopwatchMonitoring;
};
/**

View File

@ -3529,6 +3529,22 @@ nsXPCComponents_Utils::Now(double *aRetval)
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::SetStopwatchMonitoring(bool aValue)
{
JSRuntime* rt = nsXPConnect::GetRuntimeInstance()->Runtime();
SetStopwatchActive(rt, aValue);
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::GetStopwatchMonitoring(bool* aResult)
{
JSRuntime* rt = nsXPConnect::GetRuntimeInstance()->Runtime();
*aResult = IsStopwatchActive(rt);
return NS_OK;
}
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/

View File

@ -348,13 +348,6 @@ xpc::TraceXPCGlobal(JSTracer *trc, JSObject *obj)
namespace xpc {
uint64_t
GetCompartmentCPOWMicroseconds(JSCompartment *compartment)
{
xpc::CompartmentPrivate *compartmentPrivate = xpc::CompartmentPrivate::Get(compartment);
return compartmentPrivate ? PR_IntervalToMicroseconds(compartmentPrivate->CPOWTime) : 0;
}
JSObject*
CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal,
JS::CompartmentOptions& aOptions)

View File

@ -626,6 +626,7 @@ public:
void OnProcessNextEvent() {
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
mSlowScriptSecondHalf = false;
js::ResetStopwatches(Get()->Runtime());
}
void OnAfterProcessNextEvent() {
mSlowScriptCheckpoint = mozilla::TimeStamp();
@ -3623,7 +3624,6 @@ public:
, skipWriteToGlobalPrototype(false)
, universalXPConnectEnabled(false)
, forcePermissiveCOWs(false)
, CPOWTime(0)
, skipCOWCallableChecks(false)
, scriptability(c)
, scope(nullptr)
@ -3677,9 +3677,6 @@ public:
// Using it in production is inherently unsafe.
bool forcePermissiveCOWs;
// A running count of how much time we've spent processing CPOWs.
PRIntervalTime CPOWTime;
// Disables the XPConnect security checks that deny access to callables and
// accessor descriptors on COWs. Do not use this unless you are bholley.
bool skipCOWCallableChecks;

View File

@ -10,66 +10,158 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
function go() {
let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
.getService(Ci.nsICompartmentInfo);
let compartments = compartmentInfo.getCompartments();
let count = compartments.length;
let addons = {};
for (let i = 0; i < count; i++) {
let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
if (addons[compartment.addonId]) {
addons[compartment.addonId].time += compartment.time;
addons[compartment.addonId].CPOWTime += compartment.CPOWTime;
addons[compartment.addonId].compartments.push(compartment);
} else {
addons[compartment.addonId] = {
time: compartment.time,
CPOWTime: compartment.CPOWTime,
compartments: [compartment]
};
}
}
let dataDiv = document.getElementById("data");
for (let addon in addons) {
let el = document.createElement("tr");
let name = document.createElement("td");
let time = document.createElement("td");
let cpow = document.createElement("td");
name.className = "addon";
time.className = "time";
cpow.className = "cpow";
name.textContent = addon;
AddonManager.getAddonByID(addon, function(a) {
if (a) {
name.textContent = a.name;
}
});
time.textContent = addons[addon].time +"μs";
cpow.textContent = addons[addon].CPOWTime +"μs";
el.appendChild(time);
el.appendChild(cpow);
el.appendChild(name);
let div = document.createElement("tr");
for (let comp of addons[addon].compartments) {
let c = document.createElement("tr");
let name = document.createElement("td");
let time = document.createElement("td");
let cpow = document.createElement("td");
name.className = "addon";
time.className = "time";
cpow.className = "cpow";
name.textContent = comp.compartmentName;
time.textContent = comp.time +"μs";
cpow.textContent = comp.CPOWTime +"μs";
c.appendChild(time);
c.appendChild(cpow);
c.appendChild(name);
div.appendChild(c);
div.className = "details";
}
el.addEventListener("click", function() { div.style.display = (div.style.display != "block" ? "block" : "none"); });
el.appendChild(div);
dataDiv.appendChild(el);
}
/**
* The various measures we extract from a nsICompartment.
*/
const MEASURES = [
{key: "totalUserTime", label: "Total user (µs)"},
{key: "totalSystemTime", label: "Total system (µs)"},
{key: "ownUserTime", label: "Own user (µs)"},
{key: "ownSystemTime", label: "Own system (µs)"},
{key: "cpowTime", label: "CPOW (µs)"},
{key: "visits", label: "Activations"},
];
/**
* All information on a single owner (add-on, page, built-ins).
*
* @param {string} id A unique identifier.
* @param {string} kind One of "<addon>", "<built-in>", "<page>".
* @param {string|null} A name for this owner. Not expected for add-ons.
*/
function Owner(id, kind, name) {
for (let {key} of MEASURES) {
this[key] = 0;
}
this.compartments = [];
this.id = id;
this.kind = kind;
this.name = name;
}
Owner.prototype = {
add: function(compartment) {
this.compartments.push(compartment);
for (let {key} of MEASURES) {
this[key] += compartment[key];
}
},
promiseName: function() {
if (this.kind != "<addon>") {
return Promise.resolve(this.name);
}
return new Promise(resolve =>
AddonManager.getAddonByID(this.id, a =>
resolve(a?a.name:null)
)
);
}
};
function getStatistics() {
let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
.getService(Ci.nsICompartmentInfo);
let compartments = compartmentInfo.getCompartments();
let count = compartments.length;
let data = {};
for (let i = 0; i < count; i++) {
let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
let kind, id, name;
if (!compartment.isSystem) {
name = id = compartment.compartmentName;
kind = "<page>";
} else if (compartment.addonId == "<non-addon>") {
id = kind = name = "<built-in>";
} else {
name = id = compartment.addonId;
kind = "<addon>";
}
let key = kind + ":" + id;
let owner = data[key];
if (!owner) {
owner = data[key] = new Owner(id, kind, name);
}
owner.add(compartment);
}
return [data[k] for (k of Object.keys(data))].sort((a, b) => a.totalUserTime <= b.totalUserTime);
}
function update() {
try {
console.log("Updating");
// Activate (or reactivate) monitoring
Cu.stopwatchMonitoring = true;
let dataElt = document.getElementById("data");
dataElt.innerHTML = "";
// Generate table headers
let headerElt = document.createElement("tr");
dataElt.appendChild(headerElt);
for (let column of [...MEASURES, {key:"compartments", name: "Compartments"}, {key: "name", name: ""}]) {
let el = document.createElement("td");
el.classList.add(column.key);
el.classList.add("header");
el.textContent = column.label;
headerElt.appendChild(el);
}
// Generate table contents
let data = getStatistics();
console.log("Data", data);
for (let item of data) {
// Make sure that we don't show compartments with
// no time spent.
let show = false;
for (let column of MEASURES) {
if (item[column.key]) {
show = true;
}
}
if (!show) {
continue;
}
let row = document.createElement("tr");
row.classList.add(item.kind);
dataElt.appendChild(row);
// Measures
for (let column of MEASURES) {
let el = document.createElement("td");
el.classList.add(column.key);
el.classList.add("contents");
el.textContent = item[column.key];
row.appendChild(el);
}
// Number of compartments
let el = document.createElement("td");
el.classList.add("contents");
el.classList.add("compartments");
el.textContent = item.compartments.length;
row.appendChild(el);
// Name
el = document.createElement("td");
el.classList.add("contents");
el.classList.add("name");
row.appendChild(el);
item.promiseName().then(name => {
name ? el.textContent = name : item.id;
});
}
} catch (ex) {
console.error(ex);
}
}
function stop() {
Cu.stopwatchMonitoring = false;
}
function go() {
update();
window.setInterval(update, 5000);
window.addEventListener("beforeunload", stop);
}

View File

@ -29,15 +29,19 @@
color: gray;
display: none;
}
tr.addons {
background-color: white;
}
tr.builtins {
background-color: rgb(1, 1, .5);
}
tr.pages {
background-color: rgb(.5, 1, 1);
}
</style>
</head>
<body onload="go()">
<table id="data">
<tr class="header">
<td class="time">time</td>
<td class="cpow">time in CPOWs</td>
<td class="addon">name</td>
</tr>
</table>
</body>
</html>

View File

@ -4,6 +4,8 @@
# 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/.
FAIL_ON_WARNINGS = True
JAR_MANIFESTS += ['jar.mn']
XPIDL_MODULE = 'compartments'
@ -20,4 +22,6 @@ EXPORTS += [
'nsCompartmentInfo.h'
]
BROWSER_CHROME_MANIFESTS += ['tests/mochi/browser.ini']
FINAL_LIBRARY = 'xul'

View File

@ -12,12 +12,16 @@
#include "nsIMutableArray.h"
#include "nsJSUtils.h"
#include "xpcpublic.h"
#include "jspubtd.h"
class nsCompartment : public nsICompartment {
public:
nsCompartment(nsAString& aCompartmentName, nsAString& aAddonId,
uint64_t aTime, uint64_t aCPOWTime)
: mCompartmentName(aCompartmentName), mAddonId(aAddonId), mTime(aTime), mCPOWTime(aCPOWTime) {}
nsCompartment(nsAString& aCompartmentName, nsAString& aAddonId, bool aIsSystem, js::PerformanceData aPerformanceData)
: mCompartmentName(aCompartmentName)
, mAddonId(aAddonId)
, mIsSystem(aIsSystem)
, mPerformanceData(aPerformanceData)
{}
NS_DECL_ISUPPORTS
@ -27,28 +31,68 @@ public:
return NS_OK;
};
/* readonly attribute unsigned long time; */
NS_IMETHOD GetTime(uint64_t* aTime) MOZ_OVERRIDE {
*aTime = mTime;
return NS_OK;
}
/* readonly attribute wstring addon id; */
NS_IMETHOD GetAddonId(nsAString& aAddonId) MOZ_OVERRIDE {
aAddonId.Assign(mAddonId);
return NS_OK;
};
/* readonly attribute unsigned long CPOW time; */
NS_IMETHOD GetCPOWTime(uint64_t* aCPOWTime) MOZ_OVERRIDE {
*aCPOWTime = mCPOWTime;
/* readonly attribute unsigned long long totalUserTime; */
NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) {
*aTotalUserTime = mPerformanceData.totalUserTime;
return NS_OK;
};
/* readonly attribute unsigned long long totalSystemTime; */
NS_IMETHOD GetTotalSystemTime(uint64_t *aTotalSystemTime) {
*aTotalSystemTime = mPerformanceData.totalSystemTime;
return NS_OK;
};
/* readonly attribute unsigned long long ownUserTime; */
NS_IMETHOD GetOwnUserTime(uint64_t *aOwnUserTime) {
*aOwnUserTime = mPerformanceData.ownUserTime;
return NS_OK;
};
/* readonly attribute unsigned long long ownSystemTime; */
NS_IMETHOD GetOwnSystemTime(uint64_t *aOwnSystemTime) {
*aOwnSystemTime = mPerformanceData.ownSystemTime;
return NS_OK;
};
/* readonly attribute unsigned long long CPOWTime; */
NS_IMETHOD GetCPOWTime(uint64_t *aCpowTime) {
*aCpowTime = mPerformanceData.cpowTime;
return NS_OK;
};
/* readonly attribute unsigned long long visits; */
NS_IMETHOD GetVisits(uint64_t *aVisits) {
*aVisits = mPerformanceData.visits;
return NS_OK;
};
/* unsigned long long getMissedFrames (in unsigned long i); */
NS_IMETHOD GetMissedFrames(uint32_t i, uint64_t *_retval) {
if (i >= mozilla::ArrayLength(mPerformanceData.missedFrames)) {
return NS_ERROR_INVALID_ARG;
}
*_retval = mPerformanceData.missedFrames[i];
return NS_OK;
};
NS_IMETHOD GetIsSystem(bool *_retval) {
*_retval = mIsSystem;
return NS_OK;
}
private:
nsString mCompartmentName;
nsString mAddonId;
uint64_t mTime;
uint64_t mCPOWTime;
bool mIsSystem;
js::PerformanceData mPerformanceData;
virtual ~nsCompartment() {}
};
@ -77,17 +121,18 @@ nsCompartmentInfo::GetCompartments(nsIArray** aCompartments)
size_t num = stats.length();
for (size_t pos = 0; pos < num; pos++) {
CompartmentTimeStats *c = &stats[pos];
nsString addonId;
if (stats[pos].addonId) {
AssignJSFlatString(addonId, (JSFlatString*)stats[pos].addonId);
if (c->addonId) {
AssignJSFlatString(addonId, (JSFlatString*)c->addonId);
} else {
addonId.AssignLiteral("<non-addon>");
}
uint32_t cpowTime = xpc::GetCompartmentCPOWMicroseconds(stats[pos].compartment);
nsCString compartmentName(stats[pos].compartmentName);
nsCString compartmentName(c->compartmentName);
NS_ConvertUTF8toUTF16 name(compartmentName);
compartments->AppendElement(new nsCompartment(name, addonId, stats[pos].time, cpowTime), false);
compartments->AppendElement(new nsCompartment(name, addonId, c->isSystem, c->performance), false);
}
compartments.forget(aCompartments);
return NS_OK;

View File

@ -7,16 +7,52 @@
#include "nsISupports.idl"
#include "nsIArray.idl"
[scriptable, uuid(13dd4c09-ff11-4943-8dc2-d96eb69c963b)]
[scriptable, uuid(3a23d383-052e-4199-8914-74c037fe359e)]
interface nsICompartment : nsISupports {
/* name of compartment */
readonly attribute AString compartmentName;
/* time spent executing code in this compartment in microseconds */
readonly attribute unsigned long long time;
/* the id of the addon associated with this compartment, or null */
readonly attribute AString addonId;
/* time spent processing CPOWs in microseconds */
/*
Total amount of time spent executing code in this compartment, in
microseconds.
Note that these durations are only computed while
Components.utils.stopwatchMonitoring == true and that they are never
reset to 0.
*/
readonly attribute unsigned long long totalUserTime;
readonly attribute unsigned long long totalSystemTime;
readonly attribute unsigned long long ownUserTime;
readonly attribute unsigned long long ownSystemTime;
readonly attribute unsigned long long CPOWTime;
/* Number of times we have executed code in this compartment.
Updated only while Components.utils.stopwatchMonitoring == true,
never reset to 0.
*/
readonly attribute unsigned long long visits;
/* `true` if this is a system compartment (either an add-on or a built-in).*/
readonly attribute bool isSystem;
/**
* The number of times execution of code in this compartment has apparently
* caused frame drops.
*
* getMissedFrames(0): number of times we have dropped at least 1 frame
* getMissedFrames(1): number of times we have dropped at least 2 successive frames
* ...
* getMissedFrames(i): number of times we have dropped at least 2^i successive frames
*
* Updated only while Components.utils.stopwatchMonitoring == true,
* never reset to 0.
*/
unsigned long long getMissedFrames(in unsigned long i);
/* Number of values that may be consulted in `getMissedFrames`.*/
const unsigned long MISSED_FRAMES_RANGE = 8;
};
[scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)]

View File

@ -0,0 +1,10 @@
# 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/.
[DEFAULT]
support-files =
browser_compartments.html
content.js
[browser_compartments.js]

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>
browser_compartments.html
</title>
<script type="text/javascript">
// Use some CPU
// Compute an arbitrary value, print it out to make sure that the JS
// engine doesn't discard all our computation.
var date = Date.now();
var array = [];
var i = 0;
while (Date.now() - date <= 200) {
array[i%2] = i++;
}
console.log("Arbitrary value", array);
</script>
</head>
<body>
browser_compartments.html
</body>
</html>

View File

@ -0,0 +1,123 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ROOT = getRootDirectory(gTestPath);
const FRAME_SCRIPTS = [
ROOT + "content.js",
];
const URL = "chrome://mochitests/content/browser/toolkit/components/aboutcompartments/tests/mochi/browser_compartments.html";
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
for (let script of FRAME_SCRIPTS) {
mm.loadFrameScript(script, true);
}
registerCleanupFunction(() => {
for (let script of FRAME_SCRIPTS) {
mm.removeDelayedFrameScript(script, true);
}
});
function promiseContentResponse(browser, name, message) {
let mm = browser.messageManager;
let promise = new Promise(resolve => {
function removeListener() {
mm.removeMessageListener(name, listener);
}
function listener(msg) {
removeListener();
resolve(msg.data);
}
mm.addMessageListener(name, listener);
registerCleanupFunction(removeListener);
});
mm.sendAsyncMessage(name, message);
return promise;
}
function getStatistics() {
let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
.getService(Ci.nsICompartmentInfo);
let data = compartmentInfo.getCompartments();
let result = [];
for (let i = 0; i < data.length; ++i) {
result.push(data.queryElementAt(i, Ci.nsICompartment));
}
return result;
}
function* promiseTabLoadEvent(tab, url)
{
return new Promise(function (resolve, reject) {
function handleLoadEvent(event) {
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank" ||
(url && event.target.location.href != url)) {
return;
}
tab.linkedBrowser.removeEventListener("load", handleLoadEvent, true);
resolve(event);
}
tab.linkedBrowser.addEventListener("load", handleLoadEvent, true, true);
if (url)
tab.linkedBrowser.loadURI(url);
});
}
add_task(function* init() {
let monitoring = Cu.stopwatchMonitoring;
Cu.stopwatchMonitoring = true;
registerCleanupFunction(() => {
Cu.stopwatchMonitoring = monitoring;
});
});
add_task(function* test() {
info("Extracting initial state");
let stats0 = getStatistics();
Assert.notEqual(stats0.length, 0, "There is more than one compartment");
Assert.ok(!stats0.find(stat => stat.compartmentName.indexOf(URL) != -1),
"The url doesn't appear yet");
stats0.forEach(stat => {
Assert.ok(stat.totalUserTime >= stat.ownUserTime, "Total >= own user time: " + stat.compartmentName);
Assert.ok(stat.totalSystemTime >= stat.ownSystemTime, "Total >= own system time: " + stat.compartmentName);
});
let newTab = gBrowser.addTab();
let browser = newTab.linkedBrowser;
// Setup monitoring in the tab
info("Setting up monitoring in the tab");
let childMonitoring = yield promiseContentResponse(browser, "compartments-test:setMonitoring", true);
info("Waiting for load to be complete");
yield promiseTabLoadEvent(newTab, URL);
if (!gMultiProcessBrowser) {
// In non-e10s mode, the stats are counted in the single process
let stats1 = getStatistics();
let pageStats = stats1.find(stat => stat.compartmentName.indexOf(URL) != -1);
Assert.notEqual(pageStats, null, "The new page appears in the single-process statistics");
Assert.notEqual(pageStats.totalUserTime, 0, "Total single-process user time is > 0");
}
// Regardless of e10s, the stats should be accessible in the content process (or pseudo-process)
let stats2 = yield promiseContentResponse(browser, "compartments-test:getCompartments", null);
let pageStats = stats2.find(stat => stat.compartmentName.indexOf(URL) != -1);
Assert.notEqual(pageStats, null, "The new page appears in the content statistics");
Assert.notEqual(pageStats.totalUserTime, 0, "Total content user time is > 0");
Assert.ok(pageStats.totalUserTime >= 1000, "Total content user time is at least 1ms");
// Cleanup
yield promiseContentResponse(browser, "compartments-test:setMonitoring", childMonitoring);
gBrowser.removeTab(newTab);
});

View File

@ -0,0 +1,36 @@
/* 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/. */
"use strict";
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
addMessageListener("compartments-test:setMonitoring", msg => {
let stopwatchMonitoring = Cu.stopwatchMonitoring;
Cu.stopwatchMonitoring = msg.data;
sendAsyncMessage("compartments-test:setMonitoring", stopwatchMonitoring);
});
addMessageListener("compartments-test:getCompartments", () => {
let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
.getService(Ci.nsICompartmentInfo);
let data = compartmentInfo.getCompartments();
let result = [];
for (let i = 0; i < data.length; ++i) {
let xpcom = data.queryElementAt(i, Ci.nsICompartment);
let object = {};
for (let k of Object.keys(xpcom)) {
let value = xpcom[k];
if (typeof value != "function") {
object[k] = value;
}
}
object.frames = [];
for (let i = 0; i < Ci.nsICompartment.MISSED_FRAMES_RANGE; ++i) {
object.frames[i] = xpcom.getMissedFrames(i);
}
result.push(object);
}
sendAsyncMessage("compartments-test:getCompartments", result);
});

View File

@ -26,11 +26,13 @@ let AddonWatcher = {
}
if (this._callback) {
// Already initialized
return;
}
this._interval = Preferences.get("browser.addon-watch.interval", 15000);
if (this._interval == -1) {
// Deactivated by preferences
return;
}
@ -41,6 +43,9 @@ let AddonWatcher = {
// probably some malformed JSON, ignore and carry on
this._ignoreList = new Set();
}
// Start monitoring
Cu.stopwatchMonitoring = true;
this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
},
uninit: function() {
@ -48,6 +53,7 @@ let AddonWatcher = {
this._timer.cancel();
this._timer = null;
}
Cu.stopwatchMonitoring = false;
},
_checkAddons: function() {
let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]