Bug 1147664 - Detailed mode for PerformanceStats (low-level). r=jandem
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Wed, 10 Jun 2015 15:56:19 +0200
changeset 254733 60d809c185ad3be1f0dda05c2010f5b4e914e638
parent 254732 f0bef6917e181a2893a0a65639b4d97978bd9613
child 254734 1c0413eaf9f32e9a181d4fdbc5b3220f2cf5ab33
push id14247
push userryanvm@gmail.com
push dateMon, 27 Jul 2015 15:59:40 +0000
treeherderfx-team@1c0413eaf9f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1147664
milestone42.0a1
Bug 1147664 - Detailed mode for PerformanceStats (low-level). r=jandem
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/Interpreter.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
toolkit/components/perfmonitoring/nsPerformanceStats.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -312,46 +312,82 @@ IterPerformanceStats(JSContext* cx,
                     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()) {
+
+    // First report the shared groups
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         JSCompartment* compartment = c.get();
-        if (!compartment->performanceMonitoring.isLinked()) {
+        if (!c->principals()) {
+            // Compartments without principals could show up here, but
+            // reporting them doesn't really make sense.
+            continue;
+        }
+        if (!c->performanceMonitoring.hasSharedGroup()) {
             // Don't report compartments that do not even have a PerformanceGroup.
             continue;
         }
-
         js::AutoCompartment autoCompartment(cx, compartment);
-        PerformanceGroup* group = compartment->performanceMonitoring.getGroup(cx);
-
+        PerformanceGroup* group = compartment->performanceMonitoring.getSharedGroup(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 (!(*walker)(cx, group->data, group->uid, closure)) {
+        if (!(*walker)(cx,
+                       group->data, group->uid, nullptr,
+                       closure)) {
             // Issue in callback
             return false;
         }
         if (!set.add(ptr, group)) {
             // Memory issue
             return false;
         }
     }
+
+    // Then report the own groups
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+        JSCompartment* compartment = c.get();
+        if (!c->principals()) {
+            // Compartments without principals could show up here, but
+            // reporting them doesn't really make sense.
+            continue;
+        }
+        if (!c->performanceMonitoring.hasOwnGroup()) {
+            // Don't report compartments that do not even have a PerformanceGroup.
+            continue;
+        }
+        js::AutoCompartment autoCompartment(cx, compartment);
+        PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx);
+        if (ownGroup->data.ticks == 0) {
+            // Don't report compartments that have never been used.
+            continue;
+        }
+        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
+        if (!(*walker)(cx,
+                       ownGroup->data, ownGroup->uid, &sharedGroup->uid,
+                       closure)) {
+            // Issue in callback
+            return false;
+        }
+    }
+
+    // Finally, report the process stats
     *processStats = rt->stopwatch.performance;
     return true;
 }
 
 void
 AssertHeapIsIdle(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->isHeapBusy());
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5404,17 +5404,17 @@ BuildStackString(JSContext* cx, HandleOb
 
 } /* namespace JS */
 
 
 /* Stopwatch-based CPU monitoring. */
 
 namespace js {
 
-struct AutoStopwatch;
+class AutoStopwatch;
 
 // Container for performance data
 // All values are monotonic.
 struct PerformanceData {
     // Number of times we have spent at least 2^n consecutive
     // milliseconds executing code in this group.
     // durations[0] is increased whenever we spend at least 1 ms
     // executing code in this group
@@ -5535,55 +5535,67 @@ struct PerformanceGroup {
     friend struct PerformanceGroupHolder;
 
 private:
     // A reference counter. Maintained by PerformanceGroupHolder.
     uint64_t refCount_;
 };
 
 //
-// Indirection towards a PerformanceGroup.
-// This structure handles reference counting for instances of PerformanceGroup.
+// Each PerformanceGroupHolder handles:
+// - a reference-counted indirection towards a PerformanceGroup shared
+//   by several compartments
+// - a owned PerformanceGroup representing the performance of a single
+//   compartment.
 //
 struct PerformanceGroupHolder {
-    // Get the group.
+    // Get the shared group.
     // On first call, this causes a single Hashtable lookup.
     // Successive calls do not require further lookups.
-    js::PerformanceGroup* getGroup(JSContext*);
-
-    // `true` if the this holder is currently associated to a
+    js::PerformanceGroup* getSharedGroup(JSContext*);
+
+    // Get the own group.
+    js::PerformanceGroup* getOwnGroup(JSContext*);
+
+    // `true` if the this holder is currently associated to a shared
     // 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;
+    inline bool hasSharedGroup() const {
+        return sharedGroup_ != nullptr;
+    }
+    inline bool hasOwnGroup() const {
+        return ownGroup_ != 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();
 
     explicit PerformanceGroupHolder(JSRuntime* runtime)
       : runtime_(runtime)
-      , group_(nullptr)
+      , sharedGroup_(nullptr)
+      , ownGroup_(nullptr)
     {   }
     ~PerformanceGroupHolder();
-private:
+
+  private:
     // Return the key representing this PerformanceGroup in
     // Runtime::Stopwatch.
     // Do not deallocate the key.
     void* getHashKey(JSContext* cx);
 
     JSRuntime *runtime_;
 
-    // The PerformanceGroup held by this object.
-    // Initially set to `nullptr` until the first cal to `getGroup`.
+    // The PerformanceGroups held by this object.
+    // Initially set to `nullptr` until the first call to `getGroup`.
     // May be reset to `nullptr` by a call to `unlink`.
-    js::PerformanceGroup* group_;
+    js::PerformanceGroup* sharedGroup_;
+    js::PerformanceGroup* ownGroup_;
 };
 
 /**
  * Reset any stopwatch currently measuring.
  *
  * This function is designed to be called when we process a new event.
  */
 extern JS_PUBLIC_API(void)
@@ -5599,28 +5611,34 @@ ResetStopwatches(JSRuntime*);
 extern JS_PUBLIC_API(bool)
 SetStopwatchIsMonitoringCPOW(JSRuntime*, bool);
 extern JS_PUBLIC_API(bool)
 GetStopwatchIsMonitoringCPOW(JSRuntime*);
 extern JS_PUBLIC_API(bool)
 SetStopwatchIsMonitoringJank(JSRuntime*, bool);
 extern JS_PUBLIC_API(bool)
 GetStopwatchIsMonitoringJank(JSRuntime*);
+extern JS_PUBLIC_API(bool)
+SetStopwatchIsMonitoringPerCompartment(JSRuntime*, bool);
+extern JS_PUBLIC_API(bool)
+GetStopwatchIsMonitoringPerCompartment(JSRuntime*);
 
 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, uint64_t uid, void* closure);
+(PerformanceStatsWalker)(JSContext* cx,
+                         const PerformanceData& stats, uint64_t uid,
+                         const uint64_t* parentId, void* closure);
 
 /**
  * Extract the performance statistics.
  *
  * Note that before calling `walker`, we enter the corresponding context.
  */
 extern JS_PUBLIC_API(bool)
 IterPerformanceStats(JSContext* cx, PerformanceStatsWalker* walker, js::PerformanceData* process, void* closure);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -367,147 +367,259 @@ InvokeState::pushInterpreterFrame(JSCont
 
 InterpreterFrame*
 ExecuteState::pushInterpreterFrame(JSContext* cx)
 {
     return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, newTargetValue_,
                                                               scopeChain_, type_, evalInFrame_);
 }
 namespace js {
-
 // Implementation of per-performance group performance measurement.
 //
 //
 // All mutable state is stored in `Runtime::stopwatch` (per-process
 // performance stats and logistics) and in `PerformanceGroup` (per
 // group performance stats).
-struct AutoStopwatch final
-{
+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 this object is currently used to monitor performance
+    // for a shared PerformanceGroup, `false` otherwise, i.e. if the
+    // stopwatch mechanism is off or if another stopwatch is already
+    // in charge of monitoring for the same PerformanceGroup.
+    bool isMonitoringForGroup_;
+
+    // `true` if this object is currently used to monitor performance
+    // for a single compartment, `false` otherwise, i.e. if the
+    // stopwatch mechanism is off or if another stopwatch is already
+    // in charge of monitoring for the same PerformanceGroup.
+    bool isMonitoringForSelf_;
+
+    // `true` if this stopwatch is the topmost stopwatch on the stack
+    // for this event, `false` otherwise.
+    bool isMonitoringForTop_;
+
+    // `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 userTimeStart_;
+    uint64_t systemTimeStart_;
+    uint64_t CPOWTimeStart_;
+
+   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 inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : cx_(cx)
       , iteration_(0)
-      , isActive_(false)
-      , isTop_(false)
+      , isMonitoringForGroup_(false)
+      , isMonitoringForSelf_(false)
+      , isMonitoringForTop_(false)
+      , isMonitoringJank_(false)
+      , isMonitoringCPOW_(false)
       , userTimeStart_(0)
       , systemTimeStart_(0)
       , CPOWTimeStart_(0)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
-        JSRuntime* runtime = JS_GetRuntime(cx_);
-        if (!runtime->stopwatch.isMonitoringJank())
-            return;
-
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
             return;
 
+        JSRuntime* runtime = cx_->runtime();
         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.
+        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
+        if (!sharedGroup) {
+            // Either this Runtime is not configured for Performance Monitoring, or we couldn't
+            // allocate the group, or there was a problem with the hashtable.
             return;
         }
 
-        // Start the stopwatch.
-        if (!this->getTimes(runtime, &userTimeStart_, &systemTimeStart_))
-            return;
-        isActive_ = true;
-        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 (!sharedGroup->hasStopwatch(iteration_)) {
+            // 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.
+            sharedGroup->acquireStopwatch(iteration_, this);
+            isMonitoringForGroup_ = true;
+        }
+
+        PerformanceGroup* ownGroup = nullptr;
+        if (runtime->stopwatch.isMonitoringPerCompartment()) {
+            // As above, but for the group representing just this compartment.
+            ownGroup = compartment->performanceMonitoring.getOwnGroup(cx);
+            if (!ownGroup->hasStopwatch(iteration_)) {
+                ownGroup->acquireStopwatch(iteration_, this);
+                isMonitoringForSelf_ = true;
+            }
+        }
 
         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++;
-            isTop_ = true;
+            isMonitoringForTop_ = true;
+
+            MOZ_ASSERT(isMonitoringForGroup_);
         }
-    }
-    inline ~AutoStopwatch() {
-        if (!isActive_) {
+
+        if (!isMonitoringForGroup_ && !isMonitoringForSelf_) {
             // We are not in charge of monitoring anything.
+            // (isMonitoringForTop_ implies isMonitoringForGroup_,
+            // so we do not need to check it)
             return;
         }
 
-        JSRuntime* runtime = JS_GetRuntime(cx_);
-        JSCompartment* compartment = cx_->compartment();
-
-        MOZ_ASSERT(!compartment->scheduledForDestruction);
-
-        if (!runtime->stopwatch.isMonitoringJank()) {
-            // Monitoring has been stopped while we were
-            // executing the code. Drop everything.
+        enter();
+    }
+    ~AutoStopwatch() {
+        if (!isMonitoringForGroup_ && !isMonitoringForSelf_) {
+            // We are not in charge of monitoring anything.
+            // (isMonitoringForTop_ implies isMonitoringForGroup_,
+            // so we do not need to check it)
             return;
         }
 
+        JSCompartment* compartment = cx_->compartment();
+        if (compartment->scheduledForDestruction)
+            return;
+
+        JSRuntime* runtime = cx_->runtime();
         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(cx_);
-        MOZ_ASSERT(group);
-
-        // Compute time spent.
-        group->releaseStopwatch(iteration_, this);
-        uint64_t 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_;
-        group->data.totalUserTime += userTimeDelta;
-        group->data.totalSystemTime += systemTimeDelta;
-        group->data.totalCPOWTime += CPOWTimeDelta;
+        // Finish and commit measures
+        exit();
+
+        // Now release groups.
+        if (isMonitoringForGroup_) {
+            PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx_);
+            MOZ_ASSERT(sharedGroup);
+            sharedGroup->releaseStopwatch(iteration_, this);
+        }
+
+        if (isMonitoringForSelf_) {
+            PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx_);
+            MOZ_ASSERT(ownGroup);
+            ownGroup->releaseStopwatch(iteration_, this);
+        }
+
+        if (isMonitoringForTop_)
+            runtime->stopwatch.isEmpty = true;
+    }
+   private:
+    void enter() {
+        JSRuntime* runtime = cx_->runtime();
+
+        if (runtime->stopwatch.isMonitoringCPOW()) {
+            CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
+            isMonitoringCPOW_ = true;
+        }
+
+        if (runtime->stopwatch.isMonitoringJank()) {
+            if (this->getTimes(runtime, &userTimeStart_, &systemTimeStart_)) {
+                isMonitoringJank_ = true;
+            }
+        }
+
+    }
+
+    void exit() {
+        JSRuntime* runtime = cx_->runtime();
+
+        uint64_t userTimeDelta = 0;
+        uint64_t systemTimeDelta = 0;
+        if (isMonitoringJank_ && runtime->stopwatch.isMonitoringJank()) {
+            // We were monitoring jank when we entered and we still are.
+            uint64_t userTimeEnd, systemTimeEnd;
+            if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd)) {
+                // We make no attempt to recover from this error. If
+                // we bail out here, we lose nothing of value, plus
+                // I'm nearly sure that this error cannot happen in
+                // practice.
+                return;
+            }
+            userTimeDelta = userTimeEnd - userTimeStart_;
+            systemTimeDelta = systemTimeEnd - systemTimeStart_;
+        }
+
+        uint64_t CPOWTimeDelta = 0;
+        if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) {
+            // We were monitoring CPOW when we entered and we still are.
+            CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
+
+        }
+        commitDeltasToGroups(userTimeDelta, systemTimeDelta, CPOWTimeDelta);
+    }
+
+    void commitDeltasToGroups(uint64_t userTimeDelta,
+                              uint64_t systemTimeDelta,
+                              uint64_t CPOWTimeDelta)
+    {
+        JSCompartment* compartment = cx_->compartment();
+
+        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx_);
+        MOZ_ASSERT(sharedGroup);
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup->data);
+
+        if (isMonitoringForSelf_) {
+            PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx_);
+            MOZ_ASSERT(ownGroup);
+            applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup->data);
+        }
+
+        if (isMonitoringForTop_) {
+            JSRuntime* runtime = cx_->runtime();
+            applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, runtime->stopwatch.performance);
+        }
+    }
+
+
+    void applyDeltas(uint64_t userTimeDelta,
+                     uint64_t systemTimeDelta,
+                     uint64_t CPOWTimeDelta,
+                     PerformanceData& data) const {
+
+        data.ticks++;
 
         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;
-        }
-    }
-
- 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.
-    template<int N>
-    void updateDurations(uint64_t totalTimeDelta, uint64_t (&array)[N]) const {
+        data.totalUserTime += userTimeDelta;
+        data.totalSystemTime += systemTimeDelta;
+        data.totalCPOWTime += CPOWTimeDelta;
+
+        // 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.
+
         // Duration of one frame, i.e. 16ms in museconds
         size_t i = 0;
         uint64_t duration = 1000;
         for (i = 0, duration = 1000;
-             i < N && duration < totalTimeDelta;
-             ++i, duration *= 2) {
-            array[i]++;
+             i < ArrayLength(data.durations) && duration < totalTimeDelta;
+             ++i, duration *= 2)
+        {
+            data.durations[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(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
         MOZ_ASSERT(userTime);
@@ -580,41 +692,19 @@ struct AutoStopwatch final
         // Convert 100 ns to 1 us, make sure that the result is monotonic
         *userTime = runtime->stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
 
 #endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
 
         return true;
     }
 
-  private:
-    // 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 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
-    // same PerformanceGroup.
-    bool isActive_;
-
-    // `true` if this stopwatch is the topmost stopwatch on the stack
-    // for this event, `false` otherwise.
-    bool isTop_;
-
-    // Timestamps captured while starting the stopwatch.
-    uint64_t userTimeStart_;
-    uint64_t systemTimeStart_;
-    uint64_t CPOWTimeStart_;
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+private:
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
 };
 
 } // namespace js
 
 // MSVC with PGO inlines a lot of functions in RunScript, resulting in large
 // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to
 // avoid this.
 #ifdef _MSC_VER
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -894,16 +894,27 @@ js::SetStopwatchIsMonitoringCPOW(JSRunti
     return rt->stopwatch.setIsMonitoringCPOW(value);
 }
 bool
 js::GetStopwatchIsMonitoringCPOW(JSRuntime* rt)
 {
     return rt->stopwatch.isMonitoringCPOW();
 }
 
+bool
+js::SetStopwatchIsMonitoringPerCompartment(JSRuntime* rt, bool value)
+{
+    return rt->stopwatch.setIsMonitoringPerCompartment(value);
+}
+bool
+js::GetStopwatchIsMonitoringPerCompartment(JSRuntime* rt)
+{
+    return rt->stopwatch.isMonitoringPerCompartment();
+}
+
 js::PerformanceGroupHolder::~PerformanceGroupHolder()
 {
     unlink();
 }
 
 void*
 js::PerformanceGroupHolder::getHashKey(JSContext* cx)
 {
@@ -913,57 +924,81 @@ js::PerformanceGroupHolder::getHashKey(J
 
     // As a fallback, put everything in the same PerformanceGroup.
     return nullptr;
 }
 
 void
 js::PerformanceGroupHolder::unlink()
 {
-    if (!group_) {
+    if (ownGroup_) {
+        js_delete(ownGroup_);
+        ownGroup_ = nullptr;
+    }
+
+    if (!sharedGroup_) {
         // The group has never been instantiated.
         return;
     }
 
-    js::PerformanceGroup* group = group_;
-    group_ = nullptr;
+    js::PerformanceGroup* group = sharedGroup_;
+    sharedGroup_ = nullptr;
 
     if (group->decRefCount() > 0) {
         // The group has at least another owner.
         return;
     }
 
 
     JSRuntime::Stopwatch::Groups::Ptr ptr =
-        runtime_->stopwatch.groups_.lookup(group->key_);
+        runtime_->stopwatch.groups().lookup(group->key_);
     MOZ_ASSERT(ptr);
-    runtime_->stopwatch.groups_.remove(ptr);
+    runtime_->stopwatch.groups().remove(ptr);
     js_delete(group);
 }
 
 PerformanceGroup*
-js::PerformanceGroupHolder::getGroup(JSContext* cx)
+js::PerformanceGroupHolder::getOwnGroup(JSContext* cx)
 {
-    if (group_)
-        return group_;
+    if (ownGroup_)
+        return ownGroup_;
+
+    ownGroup_ = runtime_->new_<PerformanceGroup>(cx, nullptr);
+    return ownGroup_;
+}
+
+PerformanceGroup*
+js::PerformanceGroupHolder::getSharedGroup(JSContext* cx)
+{
+    if (sharedGroup_)
+        return sharedGroup_;
+
+    if (!runtime_->stopwatch.groups().initialized())
+        return nullptr;
 
     void* key = getHashKey(cx);
-    JSRuntime::Stopwatch::Groups::AddPtr ptr =
-        runtime_->stopwatch.groups_.lookupForAdd(key);
+    JSRuntime::Stopwatch::Groups::AddPtr ptr = runtime_->stopwatch.groups().lookupForAdd(key);
     if (ptr) {
-        group_ = ptr->value();
-        MOZ_ASSERT(group_);
+        sharedGroup_ = ptr->value();
+        MOZ_ASSERT(sharedGroup_);
     } else {
-        group_ = runtime_->new_<PerformanceGroup>(cx, key);
-        runtime_->stopwatch.groups_.add(ptr, key, group_);
+        sharedGroup_ = runtime_->new_<PerformanceGroup>(cx, key);
+        if (!sharedGroup_)
+            return nullptr;
+
+        if (!runtime_->stopwatch.groups().add(ptr, key, sharedGroup_)) {
+            js_delete(sharedGroup_);
+            sharedGroup_ = nullptr;
+            return nullptr;
+        }
     }
 
-    group_->incRefCount();
+    sharedGroup_->incRefCount();
 
-    return group_;
+    return sharedGroup_;
 }
 
 PerformanceData*
 js::GetPerformanceData(JSRuntime* rt)
 {
     return &rt->stopwatch.performance;
 }
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1479,16 +1479,39 @@ struct JSRuntime : public JS::shadow::Ru
 
   public:
 
     /* ------------------------------------------
        Performance measurements
        ------------------------------------------ */
     struct Stopwatch {
         /**
+         * A map used to collapse compartments belonging to the same
+         * add-on (respectively to the same webpage, to the platform)
+         * into a single group.
+         *
+         * Keys: for system compartments, a `JSAddonId*` (which may be
+         * `nullptr`), and for webpages, a `JSPrincipals*` (which may
+         * not). Note that compartments may start as non-system
+         * compartments and become compartments later during their
+         * lifetime, which requires an invalidation.
+         *
+         * This map is meant to be accessed only by instances of
+         * PerformanceGroupHolder, which handle both reference-counting
+         * of the values and invalidation of the key/value pairs.
+         */
+        typedef js::HashMap<void*, js::PerformanceGroup*,
+                            js::DefaultHasher<void*>,
+                            js::SystemAllocPolicy> Groups;
+
+        Groups& groups() {
+            return groups_;
+        }
+
+        /**
          * 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.
          */
@@ -1518,16 +1541,17 @@ struct JSRuntime : public JS::shadow::Ru
         JSCurrentPerfGroupCallback currentPerfGroupCallback;
 
         Stopwatch()
           : iteration(0)
           , isEmpty(true)
           , currentPerfGroupCallback(nullptr)
           , isMonitoringJank_(false)
           , isMonitoringCPOW_(false)
+          , isMonitoringPerCompartment_(false)
           , idCounter_(0)
         { }
 
         /**
          * 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
@@ -1559,16 +1583,31 @@ struct JSRuntime : public JS::shadow::Ru
 
             isMonitoringJank_ = value;
             return true;
         }
         bool isMonitoringJank() const {
             return isMonitoringJank_;
         }
 
+        bool setIsMonitoringPerCompartment(bool value) {
+            if (isMonitoringPerCompartment_ != value)
+                reset();
+
+            if (value && !groups_.initialized()) {
+                if (!groups_.init(128))
+                    return false;
+            }
+
+            isMonitoringPerCompartment_ = value;
+            return true;
+        }
+        bool isMonitoringPerCompartment() const {
+            return isMonitoringPerCompartment_;
+        }
 
         /**
          * Activate/deactivate stopwatch measurement of CPOW.
          */
         bool setIsMonitoringCPOW(bool value) {
             isMonitoringCPOW_ = value;
             return true;
         }
@@ -1602,43 +1641,25 @@ struct JSRuntime : public JS::shadow::Ru
             }
           private:
             uint64_t latestGood_;
         };
         MonotonicTimeStamp systemTimeFix;
         MonotonicTimeStamp userTimeFix;
 
     private:
-        /**
-         * A map used to collapse compartments belonging to the same
-         * add-on (respectively to the same webpage, to the platform)
-         * into a single group.
-         *
-         * Keys: for system compartments, a `JSAddonId*` (which may be
-         * `nullptr`), and for webpages, a `JSPrincipals*` (which may
-         * not). Note that compartments may start as non-system
-         * compartments and become compartments later during their
-         * lifetime, which requires an invalidation.
-         *
-         * This map is meant to be accessed only by instances of
-         * PerformanceGroupHolder, which handle both reference-counting
-         * of the values and invalidation of the key/value pairs.
-         */
-        typedef js::HashMap<void*, js::PerformanceGroup*,
-                            js::DefaultHasher<void*>,
-                            js::SystemAllocPolicy> Groups;
-
         Groups groups_;
         friend struct js::PerformanceGroupHolder;
 
         /**
          * `true` if stopwatch monitoring is active, `false` otherwise.
          */
         bool isMonitoringJank_;
         bool isMonitoringCPOW_;
+        bool isMonitoringPerCompartment_;
 
         /**
          * A counter used to generate unique identifiers for groups.
          */
         uint64_t idCounter_;
     };
     Stopwatch stopwatch;
 };
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -229,16 +229,34 @@ nsPerformanceSnapshot::GetWindowData(JSC
 
   nsCOMPtr<nsIDocument> doc = ptop->GetExtantDoc();
   if (!doc) {
     return;
   }
 
   doc->GetTitle(title);
   *windowId = ptop->WindowID();
+
+  *memoryUsed = -1;
+  int64_t jsObjectsSize;
+  int64_t jsStringsSize;
+  int64_t jsOtherSize;
+  int64_t domSize;
+  int64_t styleSize;
+  int64_t otherSize;
+  double jsMilliseconds;
+  double nonJSMilliseconds;
+  nsCOMPtr<nsIMemoryReporterManager> manager = nsMemoryReporterManager::GetOrCreate();
+  if (!manager) {
+    return;
+  }
+  nsresult rv = manager->GetSizeOfTab(top, &jsObjectsSize, &jsStringsSize, &jsOtherSize, &domSize, &styleSize, memoryUsed);
+  if (NS_FAILED(rv)) {
+    *memoryUsed = -1;
+  }
 }
 
 /* static */ void
 nsPerformanceSnapshot::GetAddonId(JSContext*,
                                   JS::Handle<JSObject*> global,
                                   nsAString& addonId)
 {
   addonId.AssignLiteral("");