Bug 1149486 - Regroup PerformanceStats by window. r=jandem, r=bholley

This commit is contained in:
David Rajchenbach-Teller 2015-04-18 13:21:31 +02:00
parent 0eb761fdef
commit b92e139ab0
8 changed files with 179 additions and 131 deletions

View File

@ -274,9 +274,10 @@ JS_GetEmptyString(JSRuntime* rt)
namespace js {
JS_PUBLIC_API(bool)
GetPerformanceStats(JSRuntime* rt,
PerformanceStatsVector& stats,
PerformanceStats& processStats)
IterPerformanceStats(JSContext* cx,
PerformanceStatsWalker walker,
PerformanceData* processStats,
void* closure)
{
// As a PerformanceGroup is typically associated to several
// compartments, use a HashSet to make sure that we only report
@ -289,13 +290,16 @@ GetPerformanceStats(JSRuntime* rt,
return false;
}
JSRuntime* rt = JS_GetRuntime(cx);
for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
JSCompartment* compartment = c.get();
if (!compartment->performanceMonitoring.isLinked()) {
// Don't report compartments that do not even have a PerformanceGroup.
continue;
}
PerformanceGroup* group = compartment->performanceMonitoring.getGroup();
js::AutoCompartment autoCompartment(cx, compartment);
PerformanceGroup* group = compartment->performanceMonitoring.getGroup(cx);
if (group->data.ticks == 0) {
// Don't report compartments that have never been used.
@ -308,38 +312,16 @@ GetPerformanceStats(JSRuntime* rt,
continue;
}
if (!stats.growBy(1)) {
// Memory issue
if (!(*walker)(cx, group->data, closure)) {
// Issue in callback
return false;
}
PerformanceStats* stat = &stats.back();
stat->isSystem = compartment->isSystem();
if (compartment->addonId)
stat->addonId = compartment->addonId;
if (compartment->addonId || !compartment->isSystem()) {
if (rt->compartmentNameCallback) {
(*rt->compartmentNameCallback)(rt, compartment,
stat->name,
mozilla::ArrayLength(stat->name));
} else {
strcpy(stat->name, "<unknown>");
}
} else {
strcpy(stat->name, "<platform>");
}
stat->performance = group->data;
if (!set.add(ptr, group)) {
// Memory issue
return false;
}
}
strcpy(processStats.name, "<process>");
processStats.addonId = nullptr;
processStats.isSystem = true;
processStats.performance = rt->stopwatch.performance;
*processStats = rt->stopwatch.performance;
return true;
}
@ -929,6 +911,7 @@ JSAutoCompartment::JSAutoCompartment(JSContext* cx, JSScript* target
cx_->enterCompartment(target->compartment());
}
JSAutoCompartment::~JSAutoCompartment()
{
cx_->leaveCompartment(oldCompartment_);

View File

@ -720,6 +720,18 @@ typedef void
(* JSCompartmentNameCallback)(JSRuntime* rt, JSCompartment* compartment,
char* buf, size_t bufsize);
/**
* Callback used to ask the embedding to determine in which
* Performance Group the current execution belongs. Typically, this is
* used to regroup JSCompartments from several iframes from the same
* page or from several compartments of the same addon into a single
* Performance Group.
*
* Returns an opaque key.
*/
typedef void*
(* JSCurrentPerfGroupCallback)(JSContext*);
/************************************************************************/
static MOZ_ALWAYS_INLINE jsval
@ -5446,9 +5458,10 @@ struct PerformanceGroup {
stopwatch_ = nullptr;
}
PerformanceGroup()
explicit PerformanceGroup(void* key)
: stopwatch_(nullptr)
, iteration_(0)
, key_(key)
, refCount_(0)
{ }
~PerformanceGroup()
@ -5467,6 +5480,9 @@ struct PerformanceGroup {
// may safely overflow.
uint64_t iteration_;
// The hash key for this PerformanceGroup.
void* const key_;
// Increment/decrement the refcounter, return the updated value.
uint64_t incRefCount() {
MOZ_ASSERT(refCount_ + 1 > 0);
@ -5478,7 +5494,7 @@ struct PerformanceGroup {
}
friend struct PerformanceGroupHolder;
private:
private:
// A reference counter. Maintained by PerformanceGroupHolder.
uint64_t refCount_;
};
@ -5491,7 +5507,7 @@ struct PerformanceGroupHolder {
// Get the group.
// On first call, this causes a single Hashtable lookup.
// Successive calls do not require further lookups.
js::PerformanceGroup* getGroup();
js::PerformanceGroup* getGroup(JSContext*);
// `true` if the this holder is currently associated to a
// PerformanceGroup, `false` otherwise. Use this method to avoid
@ -5506,9 +5522,8 @@ struct PerformanceGroupHolder {
// (new values of `isSystem()`, `principals()` or `addonId`).
void unlink();
PerformanceGroupHolder(JSRuntime* runtime, JSCompartment* compartment)
explicit PerformanceGroupHolder(JSRuntime* runtime)
: runtime_(runtime)
, compartment_(compartment)
, group_(nullptr)
{ }
~PerformanceGroupHolder();
@ -5516,10 +5531,9 @@ private:
// Return the key representing this PerformanceGroup in
// Runtime::Stopwatch.
// Do not deallocate the key.
void* getHashKey();
void* getHashKey(JSContext* cx);
JSRuntime* runtime_;
JSCompartment* compartment_;
JSRuntime *runtime_;
// The PerformanceGroup held by this object.
// Initially set to `nullptr` until the first cal to `getGroup`.
@ -5552,56 +5566,30 @@ IsStopwatchActive(JSRuntime*);
extern JS_PUBLIC_API(PerformanceData*)
GetPerformanceData(JSRuntime*);
typedef bool
(PerformanceStatsWalker)(JSContext* cx, const PerformanceData& stats, void* closure);
/**
* Performance statistics for a performance group (a process, an
* add-on, a webpage, the built-ins or a special compartment).
*/
struct PerformanceStats {
/**
* If this group represents an add-on, the ID of the addon,
* otherwise `nullptr`.
*/
JSAddonId* addonId;
/**
* If this group represents a webpage, the process itself or a special
* compartment, a human-readable name. Unspecified for add-ons.
*/
char name[1024];
/**
* `true` if the group represents in system compartments, `false`
* otherwise. A group may never contain both system and non-system
* compartments.
*/
bool isSystem;
/**
* Performance information.
*/
js::PerformanceData performance;
PerformanceStats()
: addonId(nullptr)
, isSystem(false)
{
name[0] = '\0';
}
};
typedef js::Vector<PerformanceStats, 0, js::SystemAllocPolicy> PerformanceStatsVector;
/**
* Extract the performance statistics.
*
* After a successful call, `stats` holds the `PerformanceStats` for
* all performance groups, and `global` holds a `PerformanceStats`
* representing the entire process.
* Note that before calling `walker`, we enter the corresponding context.
*/
extern JS_PUBLIC_API(bool)
GetPerformanceStats(JSRuntime* rt, js::PerformanceStatsVector& stats, js::PerformanceStats& global);
IterPerformanceStats(JSContext* cx, PerformanceStatsWalker* walker, js::PerformanceData* process, void* closure);
} /* namespace js */
/**
* Callback used to ask the embedding to determine in which
* Performance Group a compartment belongs. Typically, this is used to
* regroup JSCompartments from several iframes from the same page or
* from several compartments of the same addon into a single
* Performance Group.
*
* Returns an opaque key.
*/
extern JS_PUBLIC_API(void)
JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb);
#endif /* jsapi_h */

View File

@ -53,7 +53,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
#endif
global_(nullptr),
enterCompartmentDepth(0),
performanceMonitoring(runtime_, this),
performanceMonitoring(runtime_),
data(nullptr),
objectMetadataCallback(nullptr),
lastAnimationTime(0),

View File

@ -409,8 +409,7 @@ struct AutoStopwatch final
//
// Previous owner is restored upon destruction.
explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: compartment_(nullptr)
, runtime_(nullptr)
: cx_(cx)
, iteration_(0)
, isActive_(false)
, isTop_(false)
@ -419,16 +418,17 @@ struct AutoStopwatch final
, CPOWTimeStart_(0)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
runtime_ = cx->runtime();
if (!runtime_->stopwatch.isActive())
return;
compartment_ = cx->compartment();
MOZ_ASSERT(compartment_);
if (compartment_->scheduledForDestruction)
return;
iteration_ = runtime_->stopwatch.iteration;
PerformanceGroup* group = compartment_->performanceMonitoring.getGroup();
JSRuntime* runtime = JS_GetRuntime(cx_);
if (!runtime->stopwatch.isActive())
return;
JSCompartment* compartment = cx_->compartment();
if (compartment->scheduledForDestruction)
return;
iteration_ = runtime->stopwatch.iteration;
PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx);
MOZ_ASSERT(group);
if (group->hasStopwatch(iteration_)) {
@ -438,22 +438,22 @@ struct AutoStopwatch final
}
// Start the stopwatch.
if (!this->getTimes(&userTimeStart_, &systemTimeStart_))
if (!this->getTimes(runtime, &userTimeStart_, &systemTimeStart_))
return;
isActive_ = true;
CPOWTimeStart_ = runtime_->stopwatch.performance.totalCPOWTime;
CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
// We are now in charge of monitoring this group for the tick,
// until destruction of `this` or until we enter a nested event
// loop and `iteration_` is incremented.
group->acquireStopwatch(iteration_, this);
if (runtime_->stopwatch.isEmpty) {
if (runtime->stopwatch.isEmpty) {
// This is the topmost stopwatch on the stack.
// It will be in charge of updating the per-process
// performance data.
runtime_->stopwatch.isEmpty = false;
runtime_->stopwatch.performance.ticks++;
runtime->stopwatch.isEmpty = false;
runtime->stopwatch.performance.ticks++;
isTop_ = true;
}
}
@ -463,32 +463,35 @@ struct AutoStopwatch final
return;
}
MOZ_ASSERT(!compartment_->scheduledForDestruction);
JSRuntime* runtime = JS_GetRuntime(cx_);
JSCompartment* compartment = cx_->compartment();
if (!runtime_->stopwatch.isActive()) {
MOZ_ASSERT(!compartment->scheduledForDestruction);
if (!runtime->stopwatch.isActive()) {
// Monitoring has been stopped while we were
// executing the code. Drop everything.
return;
}
if (iteration_ != runtime_->stopwatch.iteration) {
if (iteration_ != runtime->stopwatch.iteration) {
// We have entered a nested event loop at some point.
// Any information we may have is obsolete.
return;
}
PerformanceGroup* group = compartment_->performanceMonitoring.getGroup();
PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx_);
MOZ_ASSERT(group);
// Compute time spent.
group->releaseStopwatch(iteration_, this);
uint64_t userTimeEnd, systemTimeEnd;
if (!this->getTimes(&userTimeEnd, &systemTimeEnd))
if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd))
return;
uint64_t userTimeDelta = userTimeEnd - userTimeStart_;
uint64_t systemTimeDelta = systemTimeEnd - systemTimeStart_;
uint64_t CPOWTimeDelta = runtime_->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
uint64_t CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
group->data.totalUserTime += userTimeDelta;
group->data.totalSystemTime += systemTimeDelta;
group->data.totalCPOWTime += CPOWTimeDelta;
@ -500,10 +503,10 @@ struct AutoStopwatch final
if (isTop_) {
// This is the topmost stopwatch on the stack.
// Record the timing information.
runtime_->stopwatch.performance.totalUserTime = userTimeEnd;
runtime_->stopwatch.performance.totalSystemTime = systemTimeEnd;
updateDurations(totalTimeDelta, runtime_->stopwatch.performance.durations);
runtime_->stopwatch.isEmpty = true;
runtime->stopwatch.performance.totalUserTime = userTimeEnd;
runtime->stopwatch.performance.totalSystemTime = systemTimeEnd;
updateDurations(totalTimeDelta, runtime->stopwatch.performance.durations);
runtime->stopwatch.isEmpty = true;
}
}
@ -527,7 +530,7 @@ struct AutoStopwatch final
// Get the OS-reported time spent in userland/systemland, in
// microseconds. On most platforms, this data is per-thread,
// but on some platforms we need to fall back to per-process.
bool getTimes(uint64_t* userTime, uint64_t* systemTime) const {
bool getTimes(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
MOZ_ASSERT(userTime);
MOZ_ASSERT(systemTime);
@ -591,12 +594,12 @@ struct AutoStopwatch final
kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
// Convert 100 ns to 1 us, make sure that the result is monotonic
*systemTime = runtime_-> stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
*systemTime = runtime->stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
userTimeInt.LowPart = userFileTime.dwLowDateTime;
userTimeInt.HighPart = userFileTime.dwHighDateTime;
// Convert 100 ns to 1 us, make sure that the result is monotonic
*userTime = runtime_-> stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
*userTime = runtime->stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
@ -604,13 +607,9 @@ struct AutoStopwatch final
}
private:
// The compartment with which this object was initialized.
// The context with which this object was initialized.
// Non-null.
JSCompartment* compartment_;
// The runtime with which this object was initialized.
// Non-null.
JSRuntime* runtime_;
JSContext* const cx_;
// An indication of the number of times we have entered the event
// loop. Used only for comparison.

View File

@ -898,15 +898,14 @@ js::PerformanceGroupHolder::~PerformanceGroupHolder()
}
void*
js::PerformanceGroupHolder::getHashKey()
js::PerformanceGroupHolder::getHashKey(JSContext* cx)
{
return compartment_->isSystem() ?
(void*)compartment_->addonId :
(void*)JS_GetCompartmentPrincipals(compartment_);
// This key may be `nullptr` if we have `isSystem() == true`
// and `compartment_->addonId`. This is absolutely correct,
// and this represents the `PerformanceGroup` used to track
// the performance of the the platform compartments.
if (runtime_->stopwatch.currentPerfGroupCallback) {
return (*runtime_->stopwatch.currentPerfGroupCallback)(cx);
}
// As a fallback, put everything in the same PerformanceGroup.
return nullptr;
}
void
@ -927,26 +926,26 @@ js::PerformanceGroupHolder::unlink()
JSRuntime::Stopwatch::Groups::Ptr ptr =
runtime_->stopwatch.groups_.lookup(getHashKey());
runtime_->stopwatch.groups_.lookup(group->key_);
MOZ_ASSERT(ptr);
runtime_->stopwatch.groups_.remove(ptr);
js_delete(group);
}
PerformanceGroup*
js::PerformanceGroupHolder::getGroup()
js::PerformanceGroupHolder::getGroup(JSContext* cx)
{
if (group_)
return group_;
void* key = getHashKey();
void* key = getHashKey(cx);
JSRuntime::Stopwatch::Groups::AddPtr ptr =
runtime_->stopwatch.groups_.lookupForAdd(key);
if (ptr) {
group_ = ptr->value();
MOZ_ASSERT(group_);
} else {
group_ = runtime_->new_<PerformanceGroup>();
group_ = runtime_->new_<PerformanceGroup>(key);
runtime_->stopwatch.groups_.add(ptr, key, group_);
}
@ -960,3 +959,9 @@ js::GetPerformanceData(JSRuntime* rt)
{
return &rt->stopwatch.performance;
}
void
JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb)
{
rt->stopwatch.currentPerfGroupCallback = cb;
}

View File

@ -1492,9 +1492,22 @@ struct JSRuntime : public JS::shadow::Runtime,
*/
js::PerformanceData performance;
/**
* Callback used to ask the embedding to determine in which
* Performance Group the current execution belongs. Typically, this is
* used to regroup JSCompartments from several iframes from the same
* page or from several compartments of the same addon into a single
* Performance Group.
*
* May be `nullptr`, in which case we put all the JSCompartments
* in the same PerformanceGroup.
*/
JSCurrentPerfGroupCallback currentPerfGroupCallback;
Stopwatch()
: iteration(0)
, isEmpty(true)
, currentPerfGroupCallback(nullptr)
, isActive_(false)
{ }

View File

@ -1779,6 +1779,20 @@ GetCompartmentName(JSCompartment* c, nsCString& name, int* anonymizeID,
}
}
extern void
xpc::GetCurrentCompartmentName(JSContext* cx, nsCString& name)
{
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
name.AssignLiteral("no global");
return;
}
JSCompartment* compartment = GetObjectCompartment(global);
int anonymizeID = 0;
GetCompartmentName(compartment, name, &anonymizeID, false);
}
static int64_t
JSMainRuntimeGCHeapDistinguishedAmount()
{
@ -3302,6 +3316,47 @@ static const JSWrapObjectCallbacks WrapObjectCallbacks = {
xpc::WrapperFactory::PrepareForWrapping
};
/**
* Group JSCompartments into PerformanceGroups.
*
* - All JSCompartments from the same add-on belong to the same
* PerformanceGroup.
* - All JSCompartments from the same same webpage (including
* frames) belong to the same PerformanceGroup.
* - All other JSCompartments (normally, system add-ons)
* belong to to a big uncategorized PerformanceGroup.
*/
static void*
GetCurrentPerfGroupCallback(JSContext* cx) {
RootedObject global(cx, CurrentGlobalOrNull(cx));
if (!global) {
// This can happen for the atom compartments, which is system
// code.
return nullptr;
}
JSAddonId* addonId = AddonIdOfObject(global);
if (addonId) {
// If this is an add-on, use the id as key.
return addonId;
}
// If the compartment belongs to a webpage, use the address of the
// topmost scriptable window, hence regrouping all frames of a
// window.
nsRefPtr<nsGlobalWindow> win = WindowOrNull(global);
if (win) {
nsCOMPtr<nsIDOMWindow> top;
nsresult rv = win->GetScriptableTop(getter_AddRefs(top));
NS_ENSURE_SUCCESS(rv, nullptr);
return top.get();
}
// Otherwise, this is platform code, use `nullptr` as key.
return nullptr;
}
XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
: CycleCollectedJSRuntime(nullptr, JS::DefaultHeapMaxBytes, JS::DefaultNurseryBytes),
mJSContextStack(new XPCJSContextStack(this)),
@ -3481,6 +3536,8 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
// Watch for the JS boolean options.
ReloadPrefsCallback(nullptr, this);
Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
JS_SetCurrentPerfGroupCallback(runtime, ::GetCurrentPerfGroupCallback);
}
// static

View File

@ -139,9 +139,6 @@ XrayAwareCalleeGlobal(JSObject* fun);
void
TraceXPCGlobal(JSTracer* trc, JSObject* obj);
uint64_t
GetCompartmentCPOWMicroseconds(JSCompartment* compartment);
} /* namespace xpc */
namespace JS {
@ -532,6 +529,12 @@ void
DispatchScriptErrorEvent(nsPIDOMWindow* win, JSRuntime* rt, xpc::ErrorReport* xpcReport,
JS::Handle<JS::Value> exception);
// Return a name for the compartment.
// This function makes reasonable efforts to make this name both mostly human-readable
// and unique. However, there are no guarantees of either property.
extern void
GetCurrentCompartmentName(JSContext*, nsCString& name);
} // namespace xpc
namespace mozilla {