Bug 1149486 - Regroup PerformanceStats by window. r=jandem, r=bholley
☠☠ backed out by 6973f1341ace ☠ ☠
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Sat, 18 Apr 2015 13:21:31 +0200
changeset 244217 9b60d38c552e5dbf39f5b66a9ca348c78127e1a7
parent 244216 162c7e7a8a9bc9180f616628375008a3e568f818
child 244218 99b209f7d0852b79207d307d56088e3e18d3b58a
push id28772
push usercbook@mozilla.com
push dateMon, 18 May 2015 11:29:34 +0000
treeherdermozilla-central@433f1e00ae3f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, bholley
bugs1149486
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1149486 - Regroup PerformanceStats by window. r=jandem, r=bholley low-level Bug 1149486 - Regrouping PerformanceStats by window (feedback);r=jandem,bholley Bug 1149486 - Extracting a window title and window ID for PerformanceStats;r=mossop
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcpublic.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -269,82 +269,64 @@ JS_GetEmptyString(JSRuntime* rt)
 {
     MOZ_ASSERT(rt->hasContexts());
     return rt->emptyString;
 }
 
 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
     // each PerformanceGroup once.
     typedef HashSet<js::PerformanceGroup*,
                     js::DefaultHasher<js::PerformanceGroup*>,
                     js::SystemAllocPolicy> Set;
     Set set;
     if (!set.init(100)) {
         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.
             continue;
         }
 
         Set::AddPtr ptr = set.lookupForAdd(group);
         if (ptr) {
             // Don't report the same group twice.
             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;
 }
 
 void
 AssertHeapIsIdle(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->isHeapBusy());
 }
@@ -924,16 +906,17 @@ JSAutoCompartment::JSAutoCompartment(JSC
   : cx_(cx),
     oldCompartment_(cx->compartment())
 {
     AssertHeapIsIdleOrIterating(cx_);
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     cx_->enterCompartment(target->compartment());
 }
 
+
 JSAutoCompartment::~JSAutoCompartment()
 {
     cx_->leaveCompartment(oldCompartment_);
 }
 
 JSAutoNullableCompartment::JSAutoNullableCompartment(JSContext* cx,
                                                      JSObject* targetOrNull
                                                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -715,16 +715,28 @@ typedef void
 
 typedef void
 (* JSZoneCallback)(JS::Zone* zone);
 
 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
 JS_NumberValue(double d)
 {
     int32_t i;
     d = JS::CanonicalizeNaN(d);
     if (mozilla::NumberIsInt32(d, &i))
@@ -5427,19 +5439,20 @@ struct PerformanceGroup {
     void releaseStopwatch(uint64_t iteration, const AutoStopwatch* stopwatch) {
         if (iteration_ != iteration)
             return;
 
         MOZ_ASSERT(stopwatch == stopwatch_ || stopwatch_ == nullptr);
         stopwatch_ = nullptr;
     }
 
-    PerformanceGroup()
+    explicit PerformanceGroup(void* key)
       : stopwatch_(nullptr)
       , iteration_(0)
+      , key_(key)
       , refCount_(0)
     { }
     ~PerformanceGroup()
     {
         MOZ_ASSERT(refCount_ == 0);
     }
   private:
     PerformanceGroup& operator=(const PerformanceGroup&) = delete;
@@ -5448,69 +5461,70 @@ struct PerformanceGroup {
     // The stopwatch currently monitoring the group,
     // or `nullptr` if none. Used ony for comparison.
     const AutoStopwatch* stopwatch_;
 
     // The current iteration of the event loop. If necessary,
     // 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);
         return ++refCount_;
     }
     uint64_t decRefCount() {
         MOZ_ASSERT(refCount_ > 0);
         return --refCount_;
     }
     friend struct PerformanceGroupHolder;
 
-  private:
+private:
     // A reference counter. Maintained by PerformanceGroupHolder.
     uint64_t refCount_;
 };
 
 //
 // Indirection towards a PerformanceGroup.
 // This structure handles reference counting for instances of PerformanceGroup.
 //
 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
     // instantiating a PerformanceGroup if you only need to get
     // available performance data.
     inline bool isLinked() const {
         return group_ != nullptr;
     }
 
     // Remove the link to the PerformanceGroup. This method is designed
     // as an invalidation mechanism if the JSCompartment changes nature
     // (new values of `isSystem()`, `principals()` or `addonId`).
     void unlink();
 
-    PerformanceGroupHolder(JSRuntime* runtime, JSCompartment* compartment)
+    PerformanceGroupHolder(JSRuntime* runtime)
       : runtime_(runtime)
-      , compartment_(compartment)
       , group_(nullptr)
     {   }
     ~PerformanceGroupHolder();
 private:
     // Return the key representing this PerformanceGroup in
     // Runtime::Stopwatch.
     // Do not deallocate the key.
-    void* getHashKey();
-
-    JSRuntime* runtime_;
-    JSCompartment* compartment_;
+    void* getHashKey(JSContext* cx);
+
+    JSRuntime *runtime_;
 
     // The PerformanceGroup held by this object.
     // Initially set to `nullptr` until the first cal to `getGroup`.
     // May be reset to `nullptr` by a call to `unlink`.
     js::PerformanceGroup* group_;
 };
 
 /**
@@ -5533,61 +5547,35 @@ extern JS_PUBLIC_API(bool)
 IsStopwatchActive(JSRuntime*);
 
 /**
  * Access the performance information stored in a compartment.
  */
 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 */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -48,17 +48,17 @@ JSCompartment::JSCompartment(Zone* zone,
     warnedAboutNoSuchMethod(false),
     warnedAboutFlagsArgument(false),
     addonId(options.addonIdOrNull()),
 #ifdef DEBUG
     firedOnNewGlobalObject(false),
 #endif
     global_(nullptr),
     enterCompartmentDepth(0),
-    performanceMonitoring(runtime_, this),
+    performanceMonitoring(runtime_),
     data(nullptr),
     objectMetadataCallback(nullptr),
     lastAnimationTime(0),
     regExps(runtime_),
     globalWriteBarriered(false),
     neuteredTypedObjects(0),
     propertyTree(thisForCtor()),
     selfHostingScriptSource(nullptr),
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -404,111 +404,114 @@ namespace js {
 struct AutoStopwatch 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)
-      : compartment_(nullptr)
-      , runtime_(nullptr)
+      : cx_(cx)
       , iteration_(0)
       , isActive_(false)
       , isTop_(false)
       , userTimeStart_(0)
       , systemTimeStart_(0)
       , CPOWTimeStart_(0)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-        runtime_ = cx->runtime();
-        if (!runtime_->stopwatch.isActive())
+
+        JSRuntime* runtime = JS_GetRuntime(cx_);
+        if (!runtime->stopwatch.isActive())
             return;
-        compartment_ = cx->compartment();
-        MOZ_ASSERT(compartment_);
-        if (compartment_->scheduledForDestruction)
+
+        JSCompartment* compartment = cx_->compartment();
+        if (compartment->scheduledForDestruction)
             return;
-        iteration_ = runtime_->stopwatch.iteration;
-
-        PerformanceGroup* group = compartment_->performanceMonitoring.getGroup();
+        iteration_ = runtime->stopwatch.iteration;
+
+        PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx);
         MOZ_ASSERT(group);
 
         if (group->hasStopwatch(iteration_)) {
             // Someone is already monitoring this group during this
             // tick, no need for further monitoring.
             return;
         }
 
         // 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;
         }
     }
     inline ~AutoStopwatch() {
         if (!isActive_) {
             // We are not in charge of monitoring anything.
             return;
         }
 
-        MOZ_ASSERT(!compartment_->scheduledForDestruction);
-
-        if (!runtime_->stopwatch.isActive()) {
+        JSRuntime* runtime = JS_GetRuntime(cx_);
+        JSCompartment* compartment = cx_->compartment();
+
+        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;
 
         uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
         updateDurations(totalTimeDelta, group->data.durations);
         group->data.ticks++;
 
         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;
         }
     }
 
  private:
 
     // Update an array containing the number of times we have missed
     // at least 2^0 successive ms, 2^1 successive ms, ...
     // 2^i successive ms.
@@ -522,17 +525,17 @@ struct AutoStopwatch final
              ++i, duration *= 2) {
             array[i]++;
         }
     }
 
     // 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);
 
 #if defined(XP_MACOSX)
         // On MacOS X, to get we per-thread data, we need to
         // reach into the kernel.
 
         mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
@@ -586,36 +589,32 @@ struct AutoStopwatch final
         if (!success)
             return false;
 
         ULARGE_INTEGER kernelTimeInt;
         ULARGE_INTEGER userTimeInt;
         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)
 
         return true;
     }
 
   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.
     uint64_t iteration_;
 
     // `true` if this object is currently used to monitor performance,
     // `false` otherwise, i.e. if the stopwatch mechanism is off or if
     // another stopwatch is already in charge of monitoring for the
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -892,25 +892,24 @@ js::IsStopwatchActive(JSRuntime* rt)
 }
 
 js::PerformanceGroupHolder::~PerformanceGroupHolder()
 {
     unlink();
 }
 
 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
 js::PerformanceGroupHolder::unlink()
 {
     if (!group_) {
         // The group has never been instantiated.
         return;
@@ -921,41 +920,47 @@ js::PerformanceGroupHolder::unlink()
 
     if (group->decRefCount() > 0) {
         // The group has at least another owner.
         return;
     }
 
 
     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_);
     }
 
     group_->incRefCount();
 
     return group_;
 }
 
 PerformanceData*
 js::GetPerformanceData(JSRuntime* rt)
 {
     return &rt->stopwatch.performance;
 }
+
+void
+JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb)
+{
+    rt->stopwatch.currentPerfGroupCallback = cb;
+}
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1484,19 +1484,32 @@ struct JSRuntime : public JS::shadow::Ru
          */
         bool isEmpty;
 
         /**
          * Performance data on the entire 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)
         { }
 
         /**
          * Reset the stopwatch.
          *
          * This method is meant to be called whenever we start processing
          * an event, to ensure that stop any ongoing measurement that would
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1774,16 +1774,30 @@ GetCompartmentName(JSCompartment* c, nsC
         // (such as about:memory) have to undo this change.
         if (replaceSlashes)
             name.ReplaceChar('/', '\\');
     } else {
         name.AssignLiteral("null-principal");
     }
 }
 
+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()
 {
     JSRuntime* rt = nsXPConnect::GetRuntimeInstance()->Runtime();
     return int64_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) *
            js::gc::ChunkSize;
 }
 
@@ -3288,16 +3302,57 @@ class XPCJSSourceHook: public js::Source
     }
 };
 
 static const JSWrapObjectCallbacks WrapObjectCallbacks = {
     xpc::WrapperFactory::Rewrap,
     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)),
    mCallContext(nullptr),
    mAutoRoots(nullptr),
    mResolveName(JSID_VOID),
    mResolvingWrapper(nullptr),
    mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)),
@@ -3468,16 +3523,18 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(JSMainRuntimeTemporaryPeakDistinguishedAmount);
     RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount);
     RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount);
     mozilla::RegisterJSSizeOfTab(JSSizeOfTab);
 
     // Watch for the JS boolean options.
     ReloadPrefsCallback(nullptr, this);
     Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
+
+    JS_SetCurrentPerfGroupCallback(runtime, ::GetCurrentPerfGroupCallback);
 }
 
 // static
 XPCJSRuntime*
 XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect)
 {
     NS_PRECONDITION(aXPConnect,"bad param");
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -134,19 +134,16 @@ IsXrayWrapper(JSObject* obj);
 // To emphasize the obvious: the return value here is not necessarily same-
 // compartment with the argument.
 JSObject*
 XrayAwareCalleeGlobal(JSObject* fun);
 
 void
 TraceXPCGlobal(JSTracer* trc, JSObject* obj);
 
-uint64_t
-GetCompartmentCPOWMicroseconds(JSCompartment* compartment);
-
 } /* namespace xpc */
 
 namespace JS {
 
 struct RuntimeStats;
 
 }
 
@@ -527,16 +524,22 @@ class ErrorReport {
   private:
     ~ErrorReport() {}
 };
 
 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 {
 namespace dom {
 
 typedef JSObject*
 (*DefineInterface)(JSContext* cx, JS::Handle<JSObject*> global,
                    JS::Handle<jsid> id, bool defineOnGlobal);