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 254756 60d809c185ad3be1f0dda05c2010f5b4e914e638
parent 254755 f0bef6917e181a2893a0a65639b4d97978bd9613
child 254757 1c0413eaf9f32e9a181d4fdbc5b3220f2cf5ab33
push id29113
push userryanvm@gmail.com
push dateMon, 27 Jul 2015 19:30:04 +0000
treeherdermozilla-central@5508066783ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1147664
milestone42.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 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("");