Bug 674721 - 'Add memory reporting for web worker data'. r=jst+njn.
authorBen Turner <bent.mozilla@gmail.com>
Mon, 01 Aug 2011 21:06:17 -0700
changeset 74428 baa3a2a2cea43895296c1bc656056e6e2bda7b43
parent 74427 b3a481c2e02aaa5b1ca5f07b0db9d6023086d613
child 74430 9134cf8f5f4aae07898266968bce6042cc3d5bf2
push id235
push userbzbarsky@mozilla.com
push dateTue, 27 Sep 2011 17:13:04 +0000
treeherdermozilla-beta@2d1e082d176a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs674721
milestone8.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 674721 - 'Add memory reporting for web worker data'. r=jst+njn.
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcpublic.h
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -39,41 +39,45 @@
 #include "RuntimeService.h"
 
 #include "nsIDOMChromeWindow.h"
 #include "nsIDocument.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIObserverService.h"
 #include "nsIPrincipal.h"
 #include "nsIJSContextStack.h"
+#include "nsIMemoryReporter.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupportsPriority.h"
 #include "nsITimer.h"
 #include "nsPIDOMWindow.h"
 
+#include "jsprf.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMPrivate.h"
+#include "xpcpublic.h"
 
 #include "Events.h"
 #include "EventTarget.h"
 #include "Worker.h"
 #include "WorkerPrivate.h"
 
 USING_WORKERS_NAMESPACE
 
 using mozilla::MutexAutoLock;
 using mozilla::MutexAutoUnlock;
 using mozilla::Preferences;
+using namespace mozilla::xpconnect::memory;
 
 // The size of the worker runtime heaps in bytes.
 #define WORKER_RUNTIME_HEAPSIZE 32 * 1024 * 1024
 
 // The size of the C stack in bytes.
 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT sizeof(size_t) * 32 * 1024
 
 // The maximum number of threads to use for workers, overridable via pref.
@@ -280,16 +284,80 @@ CreateJSContextForWorker(WorkerPrivate* 
 
   if (aWorkerPrivate->IsChromeWorker()) {
     JS_SetVersion(workerCx, JSVERSION_LATEST);
   }
 
   return workerCx;
 }
 
+class WorkerMemoryReporter : public nsIMemoryMultiReporter
+{
+  JSRuntime* mRuntime;
+  nsCString mPathPrefix;
+
+public:
+  NS_DECL_ISUPPORTS
+
+  WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate, JSRuntime* aRuntime)
+  : mRuntime(aRuntime)
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+    nsCString escapedDomain(aWorkerPrivate->Domain());
+    escapedDomain.ReplaceChar('/', '\\');
+
+    NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL());
+    escapedURL.ReplaceChar('/', '\\');
+
+    // 64bit address plus '0x' plus null terminator.
+    char address[21];
+    JSUint32 addressSize =
+      JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate);
+    if (addressSize == JSUint32(-1)) {
+      NS_WARNING("JS_snprintf failed!");
+      address[0] = '\0';
+      addressSize = 0;
+    }
+
+    mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") +
+                  escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
+                  escapedURL + NS_LITERAL_CSTRING(", ") +
+                  nsDependentCString(address, addressSize) +
+                  NS_LITERAL_CSTRING(")/");
+  }
+
+  NS_IMETHOD
+  CollectReports(nsIMemoryMultiReporterCallback* aCallback,
+                 nsISupports* aClosure)
+  {
+    AssertIsOnMainThread();
+
+    JS_TriggerAllOperationCallbacks(mRuntime);
+
+    IterateData data;
+    if (!CollectCompartmentStatsForRuntime(mRuntime, &data)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    for (CompartmentStats *stats = data.compartmentStatsVector.begin();
+         stats != data.compartmentStatsVector.end();
+         ++stats)
+    {
+      ReportCompartmentStats(*stats, mPathPrefix, aCallback, aClosure);
+    }
+
+    ReportJSStackSizeForRuntime(mRuntime, mPathPrefix, aCallback, aClosure);
+
+    return NS_OK;
+  }
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
+
 class WorkerThreadRunnable : public nsRunnable
 {
   WorkerPrivate* mWorkerPrivate;
 
 public:
   WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate)
   : mWorkerPrivate(aWorkerPrivate)
   {
@@ -306,22 +374,33 @@ public:
 
     JSContext* cx = CreateJSContextForWorker(workerPrivate);
     if (!cx) {
       // XXX need to fire an error at parent.
       NS_ERROR("Failed to create runtime and context!");
       return NS_ERROR_FAILURE;
     }
 
-    {
-      JSAutoRequest ar(cx);
-      workerPrivate->DoRunLoop(cx);
+    JSRuntime* rt = JS_GetRuntime(cx);
+
+    nsRefPtr<WorkerMemoryReporter> reporter =
+      new WorkerMemoryReporter(workerPrivate, rt);
+    if (NS_FAILED(NS_RegisterMemoryMultiReporter(reporter))) {
+      NS_WARNING("Failed to register memory reporter!");
+      reporter = nsnull;
     }
 
-    JSRuntime* rt = JS_GetRuntime(cx);
+    workerPrivate->DoRunLoop(cx);
+
+    if (reporter) {
+      if (NS_FAILED(NS_UnregisterMemoryMultiReporter(reporter))) {
+        NS_WARNING("Failed to unregister memory reporter!");
+      }
+      reporter = nsnull;
+    }
 
     // XXX Bug 666963 - CTypes can create another JSContext for use with
     // closures, and then it holds that context in a reserved slot on the CType
     // prototype object. We have to destroy that context before we can destroy
     // the runtime, and we also have to make sure that it isn't the last context
     // to be destroyed (otherwise it will assert). To accomplish this we create
     // an unused dummy context, destroy our real context, and then destroy the
     // dummy. Once this bug is resolved we can remove this nastiness and simply
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2037,28 +2037,32 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
 
       while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) {
         mCondVar.Wait();
       }
 
       {
         MutexAutoUnlock unlock(mMutex);
 
+        JSAutoRequest ar(aCx);
+
 #ifdef EXTRA_GC
         // Find GC bugs...
         JS_GC(aCx);
 #endif
 
         event->Run();
         NS_RELEASE(event);
       }
 
       currentStatus = mStatus;
     }
 
+    JSAutoRequest ar(aCx);
+
 #ifdef EXTRA_GC
     // Find GC bugs...
     JS_GC(aCx);
 #endif
 
     if (currentStatus != Running && !HasActiveFeatures()) {
       // If the close handler has finished and all features are done then we can
       // kill this thread.
@@ -2088,16 +2092,18 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
   NS_NOTREACHED("Shouldn't get here!");
 }
 
 bool
 WorkerPrivate::OperationCallback(JSContext* aCx)
 {
   AssertIsOnWorkerThread();
 
+  JS_YieldRequest(aCx);
+
   bool mayContinue = true;
 
   for (;;) {
     // Run all control events now.
     for (;;) {
       nsIRunnable* event;
       {
         MutexAutoLock lock(mMutex);
@@ -2121,16 +2127,18 @@ WorkerPrivate::OperationCallback(JSConte
     JS_FlushCaches(aCx);
     JS_GC(aCx);
 
     while ((mayContinue = MayContinueRunning())) {
       MutexAutoLock lock(mMutex);
       if (!mControlQueue.IsEmpty()) {
         break;
       }
+
+      JSAutoSuspendRequest asr(aCx);
       mCondVar.Wait(PR_MillisecondsToInterval(RemainingRunTimeMS()));
     }
   }
 
   if (!mayContinue) {
     // We want only uncatchable exceptions here.
     NS_ASSERTION(!JS_IsExceptionPending(aCx),
                  "Should not have an exception set here!");
@@ -2475,16 +2483,17 @@ WorkerPrivate::RunSyncLoop(JSContext* aC
   SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get();
 
   for (;;) {
     nsIRunnable* event;
     {
       MutexAutoLock lock(mMutex);
 
       while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) {
+        JSAutoSuspendRequest asr(aCx);
         mCondVar.Wait();
       }
     }
 
 #ifdef EXTRA_GC
     // Find GC bugs...
     JS_GC(mJSContext);
 #endif
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -37,31 +37,33 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* Per JSRuntime object */
 
 #include "xpcprivate.h"
+#include "xpcpublic.h"
 #include "WrapperFactory.h"
 #include "dom_quickstubs.h"
 
 #include "jsgcchunk.h"
 #include "jsscope.h"
 #include "nsIMemoryReporter.h"
 #include "nsPrintfCString.h"
 #include "mozilla/FunctionTimer.h"
 #include "prsystem.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 using namespace mozilla;
+using namespace mozilla::xpconnect::memory;
 
 /***************************************************************************/
 
 const char* XPCJSRuntime::mStrings[] = {
     "constructor",          // IDX_CONSTRUCTOR
     "toString",             // IDX_TO_STRING
     "toSource",             // IDX_TO_SOURCE
     "lastResult",           // IDX_LAST_RESULT
@@ -1222,16 +1224,215 @@ XPCJSRuntime::~XPCJSRuntime()
 #ifdef DEBUG_shaver_off
         fprintf(stderr, "nJRSI: destroyed runtime %p\n", (void *)mJSRuntime);
 #endif
     }
 
     XPCPerThreadData::ShutDown();
 }
 
+namespace {
+
+PRInt64
+GetCompartmentScriptsSize(JSCompartment *c)
+{
+    PRInt64 n = 0;
+    for(JSScript *script = (JSScript *)c->scripts.next;
+        &script->links != &c->scripts;
+        script = (JSScript *)script->links.next)
+    {
+        n += script->totalSize();
+    }
+    return n;
+}
+
+#ifdef JS_METHODJIT
+
+PRInt64
+GetCompartmentMjitCodeSize(JSCompartment *c)
+{
+    return c->getMjitCodeSize();
+}
+
+PRInt64
+GetCompartmentMjitDataSize(JSCompartment *c)
+{
+    PRInt64 n = 0;
+    for(JSScript *script = (JSScript *)c->scripts.next;
+        &script->links != &c->scripts;
+        script = (JSScript *)script->links.next)
+    {
+        n += script->jitDataSize();
+    }
+    return n;
+}
+
+#endif  // JS_METHODJIT
+
+#ifdef JS_TRACER
+
+PRInt64
+GetCompartmentTjitCodeSize(JSCompartment *c)
+{
+    if(c->hasTraceMonitor())
+    {
+        size_t total, frag_size, free_size;
+        c->traceMonitor()->getCodeAllocStats(total, frag_size, free_size);
+        return total;
+    }
+    return 0;
+}
+
+PRInt64
+GetCompartmentTjitDataAllocatorsMainSize(JSCompartment *c)
+{
+    return c->hasTraceMonitor()
+         ? c->traceMonitor()->getVMAllocatorsMainSize()
+         : 0;
+}
+
+PRInt64
+GetCompartmentTjitDataAllocatorsReserveSize(JSCompartment *c)
+{
+    return c->hasTraceMonitor()
+         ? c->traceMonitor()->getVMAllocatorsReserveSize()
+         : 0;
+}
+
+#endif  // JS_TRACER
+
+void
+CompartmentCallback(JSContext *cx, void *vdata, JSCompartment *compartment)
+{
+    // Append a new CompartmentStats to the vector.
+    IterateData *data = static_cast<IterateData *>(vdata);
+    CompartmentStats compartmentStats(cx, compartment);
+    data->compartmentStatsVector.infallibleAppend(compartmentStats);
+    CompartmentStats *curr = data->compartmentStatsVector.end() - 1;
+    data->currCompartmentStats = curr;
+
+    // Get the compartment-level numbers.
+    curr->scripts = GetCompartmentScriptsSize(compartment);
+#ifdef JS_METHODJIT
+    curr->mjitCode = GetCompartmentMjitCodeSize(compartment);
+    curr->mjitData = GetCompartmentMjitDataSize(compartment);
+#endif
+#ifdef JS_TRACER
+    curr->tjitCode = GetCompartmentTjitCodeSize(compartment);
+    curr->tjitDataAllocatorsMain = GetCompartmentTjitDataAllocatorsMainSize(compartment);
+    curr->tjitDataAllocatorsReserve = GetCompartmentTjitDataAllocatorsReserveSize(compartment);
+#endif
+}
+
+void
+ArenaCallback(JSContext *cx, void *vdata, js::gc::Arena *arena,
+              size_t traceKind, size_t thingSize)
+{
+    IterateData *data = static_cast<IterateData *>(vdata);
+    data->currCompartmentStats->gcHeapArenaHeaders +=
+        sizeof(js::gc::ArenaHeader);
+    data->currCompartmentStats->gcHeapArenaPadding +=
+        arena->thingsStartOffset(thingSize) - sizeof(js::gc::ArenaHeader);
+    // We don't call the callback on unused things.  So we compute the
+    // unused space like this:  arenaUnused = maxArenaUnused - arenaUsed.
+    // We do this by setting arenaUnused to maxArenaUnused here, and then
+    // subtracting thingSize for every used cell, in CellCallback().
+    data->currCompartmentStats->gcHeapArenaUnused += arena->thingsSpan(thingSize);
+}
+
+void
+CellCallback(JSContext *cx, void *vdata, void *thing, size_t traceKind,
+             size_t thingSize)
+{
+    IterateData *data = static_cast<IterateData *>(vdata);
+    CompartmentStats *curr = data->currCompartmentStats;
+    if(traceKind == JSTRACE_OBJECT)
+    {
+        curr->gcHeapObjects += thingSize;
+        JSObject *obj = static_cast<JSObject *>(thing);
+        if(obj->hasSlotsArray())
+            curr->objectSlots += obj->numSlots() * sizeof(js::Value);
+    }
+    else if(traceKind == JSTRACE_STRING)
+    {
+        curr->gcHeapStrings += thingSize;
+        JSString *str = static_cast<JSString *>(thing);
+        curr->stringChars += str->charsHeapSize();
+    }
+    else if(traceKind == JSTRACE_SHAPE)
+    {
+        curr->gcHeapShapes += thingSize;
+        js::Shape *shape = static_cast<js::Shape *>(thing);
+        if(shape->hasTable())
+            curr->propertyTables += shape->getTable()->sizeOf();
+    }
+    else
+    {
+        JS_ASSERT(traceKind == JSTRACE_XML);
+        curr->gcHeapXml += thingSize;
+    }
+    // Yes, this is a subtraction:  see ArenaCallback() for details.
+    curr->gcHeapArenaUnused -= thingSize;
+}
+
+template <int N>
+inline void
+ReportMemory(const nsACString &path, PRInt32 kind, PRInt32 units,
+             PRInt64 amount, const char (&desc)[N],
+             nsIMemoryMultiReporterCallback *callback, nsISupports *closure)
+{
+    callback->Callback(NS_LITERAL_CSTRING(""), path, kind, units, amount,
+                       NS_LITERAL_CSTRING(desc), closure);
+}
+
+template <int N>
+inline void
+ReportMemoryBytes(const nsACString &path, PRInt32 kind, PRInt64 amount,
+                  const char (&desc)[N],
+                  nsIMemoryMultiReporterCallback *callback,
+                  nsISupports *closure)
+{
+    ReportMemory(path, kind, nsIMemoryReporter::UNITS_BYTES, amount, desc,
+                 callback, closure);
+}
+
+template <int N>
+inline void
+ReportMemoryBytes0(const nsCString &path, PRInt32 kind, PRInt64 amount,
+                   const char (&desc)[N],
+                   nsIMemoryMultiReporterCallback *callback,
+                   nsISupports *closure)
+{
+    if(amount)
+        ReportMemoryBytes(path, kind, amount, desc, callback, closure);
+}
+
+template <int N>
+inline void
+ReportMemoryPercentage(const nsACString &path, PRInt32 kind, PRInt64 amount,
+                       const char (&desc)[N],
+                       nsIMemoryMultiReporterCallback *callback,
+                       nsISupports *closure)
+{
+    ReportMemory(path, kind, nsIMemoryReporter::UNITS_PERCENTAGE, amount, desc,
+                 callback, closure);
+}
+
+template <int N>
+inline const nsCString
+MakeMemoryReporterPath(const nsACString &pathPrefix,
+                       const nsACString &compartmentName,
+                       const char (&reporterName)[N])
+{
+  return pathPrefix + NS_LITERAL_CSTRING("compartment(") + compartmentName +
+         NS_LITERAL_CSTRING(")/") + nsDependentCString(reporterName);
+}
+
+} // anonymous namespace
+
 class XPConnectGCChunkAllocator
     : public js::GCChunkAllocator
 {
 public:
     XPConnectGCChunkAllocator() {}
 
     PRInt64 GetGCChunkBytesInUse() {
         return mNumGCChunksInUse * js::GC_CHUNK_SIZE;
@@ -1278,35 +1479,16 @@ static XPConnectGCChunkAllocator gXPCJSC
 NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap,
     "js-gc-heap",
     KIND_OTHER,
     nsIMemoryReporter::UNITS_BYTES,
     gXPCJSChunkAllocator.GetGCChunkBytesInUse,
     "Memory used by the garbage-collected JavaScript heap.")
 
 static PRInt64
-GetJSStack()
-{
-    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
-    PRInt64 n = 0;
-    for (js::ThreadDataIter i(rt); !i.empty(); i.popFront())
-        n += i.threadData()->stackSpace.committedSize();
-    return n;
-}
-
-NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStack,
-    "explicit/js/stack",
-    KIND_NONHEAP,
-    nsIMemoryReporter::UNITS_BYTES,
-    GetJSStack,
-    "Memory used for the JavaScript stack.  This is the committed portion "
-    "of the stack; any uncommitted portion is not measured because it "
-    "hardly costs anything.")
-
-static PRInt64
 GetJSSystemCompartmentCount()
 {
     JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
     size_t n = 0;
     for (size_t i = 0; i < rt->compartments.length(); i++) {
         if (rt->compartments[i]->isSystemCompartment) {
             n++;
         }
@@ -1349,448 +1531,337 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJS
     KIND_OTHER,
     nsIMemoryReporter::UNITS_COUNT,
     GetJSUserCompartmentCount,
     "The number of JavaScript compartments for user code.  The sum of this "
     "and 'js-compartments-system' might not match the number of "
     "compartments listed under 'js' if a garbage collection occurs at an "
     "inopportune time, but such cases should be rare.")
 
-class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter
+namespace mozilla {
+namespace xpconnect {
+namespace memory {
+
+CompartmentStats::CompartmentStats(JSContext *cx, JSCompartment *c)
 {
-private:
-    struct CompartmentStats
-    {
-        CompartmentStats(JSContext *cx, JSCompartment *c) {
-            memset(this, 0, sizeof(*this));
+    memset(this, 0, sizeof(*this));
 
-            if (c == cx->runtime->atomsCompartment) {
-                name = NS_LITERAL_CSTRING("atoms");
-            } else if (c->principals) {
-                if (c->principals->codebase) {
-                    // A hack: replace forward slashes with '\\' so they aren't
-                    // treated as path separators.  Users of the reporters
-                    // (such as about:memory) have to undo this change.
-                    name.Assign(c->principals->codebase);
-                    char* cur = name.BeginWriting();
-                    char* end = name.EndWriting();
-                    for (; cur < end; ++cur) {
-                        if ('/' == *cur) {
-                            *cur = '\\';
-                        }
-                    }
-                    // If it's the system compartment, append the address.
-                    // This means that multiple system compartments (and there
-                    // can be many) can be distinguished.
-                    if (c->isSystemCompartment) {
-                        static const int maxLength = 31;   // ample; 64-bit address max is 18 chars
-                        nsPrintfCString address(maxLength, ", 0x%llx", PRUint64(c));
-                        name.Append(address);
-                    }
-                } else {
-                    name = NS_LITERAL_CSTRING("null-codebase");
-                }
-            } else {
-                name = NS_LITERAL_CSTRING("null-principal");
+    if(c == cx->runtime->atomsCompartment)
+    {
+        name.AssignLiteral("atoms");
+    }
+    else if(c->principals)
+    {
+        if(c->principals->codebase)
+        {
+            // A hack: replace forward slashes with '\\' so they aren't
+            // treated as path separators.  Users of the reporters
+            // (such as about:memory) have to undo this change.
+            name.Assign(c->principals->codebase);
+            name.ReplaceChar('/', '\\');
+
+            // If it's the system compartment, append the address.
+            // This means that multiple system compartments (and there
+            // can be many) can be distinguished.
+            if(c->isSystemCompartment)
+            {
+                // ample; 64-bit address max is 18 chars
+                static const int maxLength = 31;
+                nsPrintfCString address(maxLength, ", 0x%llx", PRUint64(c));
+                name.Append(address);
             }
         }
-
-        nsCString name;
-        PRInt64 gcHeapArenaHeaders;
-        PRInt64 gcHeapArenaPadding;
-        PRInt64 gcHeapArenaUnused;
-
-        PRInt64 gcHeapObjects;
-        PRInt64 gcHeapStrings;
-        PRInt64 gcHeapShapes;
-        PRInt64 gcHeapXml;
-
-        PRInt64 objectSlots;
-        PRInt64 stringChars;
-        PRInt64 propertyTables;
+        else
+        {
+            name.AssignLiteral("null-codebase");
+        }
+    }
+    else
+    {
+        name.AssignLiteral("null-principal");
+    }
+}
 
-        PRInt64 scripts;
-#ifdef JS_METHODJIT
-        PRInt64 mjitCode;
-        PRInt64 mjitData;
-#endif
-#ifdef JS_TRACER
-        PRInt64 tjitCode;
-        PRInt64 tjitDataAllocatorsMain;
-        PRInt64 tjitDataAllocatorsReserve;
-#endif
-    };
-
-    struct IterateData
+JSBool
+CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data)
+{
+    JSContext *cx = JS_NewContext(rt, 0);
+    if(!cx)
     {
-        IterateData(JSRuntime *rt)
-        : compartmentStatsVector()
-        , currCompartmentStats(NULL)
-        {
-            compartmentStatsVector.reserve(rt->compartments.length());
-        }
-
-        js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> compartmentStatsVector;
-        CompartmentStats *currCompartmentStats;
-    };
-
-    static PRInt64
-    GetCompartmentScriptsSize(JSCompartment *c)
-    {
-        PRInt64 n = 0;
-        for (JSScript *script = (JSScript *)c->scripts.next;
-             &script->links != &c->scripts;
-             script = (JSScript *)script->links.next)
-        {
-            n += script->totalSize(); 
-        }
-        return n;
+        NS_ERROR("couldn't create context for memory tracing");
+        return false;
     }
 
-    #ifdef JS_METHODJIT
-
-    static PRInt64
-    GetCompartmentMjitCodeSize(JSCompartment *c)
     {
-        return c->getMjitCodeSize();
-    }
+      JSAutoRequest ar(cx);
 
-    static PRInt64
-    GetCompartmentMjitDataSize(JSCompartment *c)
-    {
-        PRInt64 n = 0;
-        for (JSScript *script = (JSScript *)c->scripts.next;
-             &script->links != &c->scripts;
-             script = (JSScript *)script->links.next)
-        {
-            n += script->jitDataSize(); 
-        }
-        return n;
+      data->compartmentStatsVector.reserve(rt->compartments.length());
+      js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
+                                         ArenaCallback, CellCallback);
     }
 
-    #endif  // JS_METHODJIT
+    JS_DestroyContextNoGC(cx);
+    return true;
+}
 
-    #ifdef JS_TRACER
+void
+ReportCompartmentStats(const CompartmentStats &stats,
+                       const nsACString &pathPrefix,
+                       nsIMemoryMultiReporterCallback *callback,
+                       nsISupports *closure)
+{
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/arena-headers"),
+                       JS_GC_HEAP_KIND, stats.gcHeapArenaHeaders,
+    "Memory on the compartment's garbage-collected JavaScript heap, within "
+    "arenas, that is used to hold internal book-keeping information.",
+                       callback, closure);
+
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/arena-padding"),
+                       JS_GC_HEAP_KIND, stats.gcHeapArenaPadding,
+    "Memory on the compartment's garbage-collected JavaScript heap, within "
+    "arenas, that is unused and present only so that other data is aligned. "
+    "This constitutes internal fragmentation.",
+                       callback, closure);
 
-    static PRInt64
-    GetCompartmentTjitCodeSize(JSCompartment *c)
-    {
-        if (c->hasTraceMonitor()) {
-            size_t total, frag_size, free_size;
-            c->traceMonitor()->getCodeAllocStats(total, frag_size, free_size);
-            return total;
-        }
-        return 0;
-    }
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/arena-unused"),
+                       JS_GC_HEAP_KIND, stats.gcHeapArenaUnused,
+    "Memory on the compartment's garbage-collected JavaScript heap, within "
+    "arenas, that could be holding useful data but currently isn't.",
+                       callback, closure);
+
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/objects"),
+                       JS_GC_HEAP_KIND, stats.gcHeapObjects,
+    "Memory on the compartment's garbage-collected JavaScript heap that holds "
+    "objects.",
+                       callback, closure);
 
-    static PRInt64
-    GetCompartmentTjitDataAllocatorsMainSize(JSCompartment *c)
-    {
-        return c->hasTraceMonitor()
-             ? c->traceMonitor()->getVMAllocatorsMainSize()
-             : 0;
-    }
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/strings"),
+                       JS_GC_HEAP_KIND, stats.gcHeapStrings,
+    "Memory on the compartment's garbage-collected JavaScript heap that holds "
+    "string headers.  String headers contain various pieces of information "
+    "about a string, but do not contain (except in the case of very short "
+    "strings) the string characters;  characters in longer strings are counted "
+    "under 'gc-heap/string-chars' instead.",
+                       callback, closure);
+
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/shapes"),
+                       JS_GC_HEAP_KIND, stats.gcHeapShapes,
+    "Memory on the compartment's garbage-collected JavaScript heap that holds "
+    "shapes. A shape is an internal data structure that makes JavaScript "
+    "property accesses fast.",
+                       callback, closure);
 
-    static PRInt64
-    GetCompartmentTjitDataAllocatorsReserveSize(JSCompartment *c)
-    {
-        return c->hasTraceMonitor()
-             ? c->traceMonitor()->getVMAllocatorsReserveSize()
-             : 0;
-    }
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "gc-heap/xml"),
+                       JS_GC_HEAP_KIND, stats.gcHeapXml,
+    "Memory on the compartment's garbage-collected JavaScript heap that holds "
+    "E4X XML objects.",
+                       callback, closure);
 
-    #endif  // JS_TRACER
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "object-slots"),
+                       nsIMemoryReporter::KIND_HEAP, stats.objectSlots,
+    "Memory allocated for the compartment's non-fixed object slot arrays, "
+    "which are used to represent object properties.  Some objects also "
+    "contain a fixed number of slots which are stored on the compartment's "
+    "JavaScript heap; those slots are not counted here, but in "
+    "'gc-heap/objects' instead.",
+                       callback, closure);
+
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "string-chars"),
+                       nsIMemoryReporter::KIND_HEAP, stats.stringChars,
+    "Memory allocated to hold the compartment's string characters.  Sometimes "
+    "more memory is allocated than necessary, to simplify string "
+    "concatenation.  Each string also includes a header which is stored on the "
+    "compartment's JavaScript heap;  that header is not counted here, but in "
+    "'gc-heap/strings' instead.",
+                       callback, closure);
 
-    static void
-    CompartmentCallback(JSContext *cx, void *vdata, JSCompartment *compartment)
-    {
-        // Append a new CompartmentStats to the vector.
-        IterateData *data = static_cast<IterateData *>(vdata);
-        CompartmentStats compartmentStats(cx, compartment);
-        data->compartmentStatsVector.infallibleAppend(compartmentStats);
-        CompartmentStats *curr = data->compartmentStatsVector.end() - 1;
-        data->currCompartmentStats = curr;
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "property-tables"),
+                       nsIMemoryReporter::KIND_HEAP, stats.propertyTables,
+    "Memory allocated for the compartment's property tables.  A property "
+    "table is an internal data structure that makes JavaScript property "
+    "accesses fast.",
+                       callback, closure);
 
-        // Get the compartment-level numbers.
-        curr->scripts = GetCompartmentScriptsSize(compartment);
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "scripts"),
+                       nsIMemoryReporter::KIND_HEAP, stats.scripts,
+    "Memory allocated for the compartment's JSScripts.  A JSScript is created "
+    "for each user-defined function in a script.  One is also created for "
+    "the top-level code in a script.  Each JSScript includes byte-code and "
+    "various other things.",
+                       callback, closure);
+
 #ifdef JS_METHODJIT
-        curr->mjitCode = GetCompartmentMjitCodeSize(compartment);
-        curr->mjitData = GetCompartmentMjitDataSize(compartment);
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "mjit-code"),
+                       nsIMemoryReporter::KIND_NONHEAP, stats.mjitCode,
+    "Memory used by the method JIT to hold the compartment's generated code.",
+                       callback, closure);
+
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "mjit-data"),
+                       nsIMemoryReporter::KIND_HEAP, stats.mjitData,
+    "Memory used by the method JIT for the compartment's compilation data: "
+    "JITScripts, native maps, and inline cache structs.",
+                       callback, closure);
 #endif
 #ifdef JS_TRACER
-        curr->tjitCode = GetCompartmentTjitCodeSize(compartment);
-        curr->tjitDataAllocatorsMain = GetCompartmentTjitDataAllocatorsMainSize(compartment);
-        curr->tjitDataAllocatorsReserve = GetCompartmentTjitDataAllocatorsReserveSize(compartment);
-#endif
-    }
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "tjit-code"),
+                       nsIMemoryReporter::KIND_NONHEAP, stats.tjitCode,
+    "Memory used by the trace JIT to hold the compartment's generated code.",
+                       callback, closure);
 
-    static void
-    ArenaCallback(JSContext *cx, void *vdata, js::gc::Arena *arena,
-                  size_t traceKind, size_t thingSize)
-    {
-        IterateData *data = static_cast<IterateData *>(vdata);
-        data->currCompartmentStats->gcHeapArenaHeaders +=
-            sizeof(js::gc::ArenaHeader);
-        data->currCompartmentStats->gcHeapArenaPadding +=
-            arena->thingsStartOffset(thingSize) - sizeof(js::gc::ArenaHeader);
-        // We don't call the callback on unused things.  So we compute the
-        // unused space like this:  arenaUnused = maxArenaUnused - arenaUsed.
-        // We do this by setting arenaUnused to maxArenaUnused here, and then
-        // subtracting thingSize for every used cell, in CellCallback().
-        data->currCompartmentStats->gcHeapArenaUnused += arena->thingsSpan(thingSize);
-    }
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "tjit-data/allocators-main"),
+                       nsIMemoryReporter::KIND_HEAP,
+                       stats.tjitDataAllocatorsMain,
+    "Memory used by the trace JIT to store the compartment's trace-related "
+    "data.  This data is allocated via the compartment's VMAllocators.",
+                       callback, closure);
+
+    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
+                                              "tjit-data/allocators-reserve"),
+                       nsIMemoryReporter::KIND_HEAP,
+                       stats.tjitDataAllocatorsReserve,
+    "Memory used by the trace JIT and held in reserve for the compartment's "
+    "VMAllocators in case of OOM.",
+                       callback, closure);
+#endif
+}
 
-    static void
-    CellCallback(JSContext *cx, void *vdata, void *thing, size_t traceKind,
-                 size_t thingSize)
-    {
-        IterateData *data = static_cast<IterateData *>(vdata);
-        CompartmentStats *curr = data->currCompartmentStats;
-        if (traceKind == JSTRACE_OBJECT) {
-            curr->gcHeapObjects += thingSize;
-            JSObject *obj = static_cast<JSObject *>(thing);
-            if (obj->hasSlotsArray()) {
-                curr->objectSlots += obj->numSlots() * sizeof(js::Value);
-            }
-        } else if (traceKind == JSTRACE_STRING) {
-            curr->gcHeapStrings += thingSize;
-            JSString *str = static_cast<JSString *>(thing);
-            curr->stringChars += str->charsHeapSize();
-        } else if (traceKind == JSTRACE_SHAPE) {
-            curr->gcHeapShapes += thingSize;
-            js::Shape *shape = static_cast<js::Shape *>(thing);
-            if (shape->hasTable()) {
-                curr->propertyTables += shape->getTable()->sizeOf();
-            }
-        } else {
-            JS_ASSERT(traceKind == JSTRACE_XML);
-            curr->gcHeapXml += thingSize;
-        }
-        // Yes, this is a subtraction:  see ArenaCallback() for details.
-        curr->gcHeapArenaUnused -= thingSize;
-    }
+void
+ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix,
+                            nsIMemoryMultiReporterCallback *callback,
+                            nsISupports *closure)
+{
+    PRInt64 stackSize = 0;
+    for(js::ThreadDataIter i(rt); !i.empty(); i.popFront())
+        stackSize += i.threadData()->stackSpace.committedSize();
 
+    ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("stack"),
+                      nsIMemoryReporter::KIND_NONHEAP, stackSize,
+    "Memory used for the JavaScript stack.  This is the committed portion "
+    "of the stack; any uncommitted portion is not measured because it "
+    "hardly costs anything.",
+                      callback, closure);
+}
+
+} // namespace memory
+} // namespace xpconnect
+} // namespace mozilla
+
+class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter
+{
 public:
     NS_DECL_ISUPPORTS
 
-    XPConnectJSCompartmentsMultiReporter()
-    {
-    }
-
-    nsCString mkPath(const nsACString &compartmentName,
-                     const char* reporterName)
-    {
-        nsCString path(NS_LITERAL_CSTRING("explicit/js/compartment("));
-        path += compartmentName;
-        path += NS_LITERAL_CSTRING(")/");
-        path += nsDependentCString(reporterName);
-        return path;
-    }
-
     NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *callback,
                               nsISupports *closure)
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
-        IterateData data(rt);
 
         // In the first step we get all the stats and stash them in a local
         // data structure.  In the second step we pass all the stashed stats to
         // the callback.  Separating these steps is important because the
         // callback may be a JS function, and executing JS while getting these
         // stats seems like a bad idea.
-        {
-            JSContext *cx = JS_NewContext(rt, 0);
-            if (!cx) {
-                NS_ERROR("couldn't create context for memory tracing");
-                return NS_ERROR_FAILURE;
-            }
-            JS_BeginRequest(cx);
-            js::IterateCompartmentsArenasCells(cx, &data, CompartmentCallback, ArenaCallback,
-                                               CellCallback);
-            JS_EndRequest(cx);
-            JS_DestroyContextNoGC(cx);
-        }
-
-        NS_NAMED_LITERAL_CSTRING(p, "");
+        IterateData data;
+        if(!CollectCompartmentStatsForRuntime(rt, &data))
+            return NS_ERROR_FAILURE;
 
         PRInt64 gcHeapChunkTotal = gXPCJSChunkAllocator.GetGCChunkBytesInUse();
         // This is initialized to gcHeapChunkTotal, and then we subtract used
         // space from it each time around the loop.
         PRInt64 gcHeapChunkUnused = gcHeapChunkTotal;
         PRInt64 gcHeapArenaUnused = 0;
 
-        #define BYTES(path, kind, amount, desc) \
-            callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_BYTES, \
-                               amount, NS_LITERAL_CSTRING(desc), closure)
-
-        #define BYTES0(path, kind, amount, desc) \
-            do { \
-                if (amount != 0) \
-                    BYTES(path, kind, amount, desc); \
-            } while (0)
-
-        #define PERCENTAGE(path, kind, amount, desc) \
-            callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_PERCENTAGE, \
-                               amount, NS_LITERAL_CSTRING(desc), closure);
+        NS_NAMED_LITERAL_CSTRING(pathPrefix, "explicit/js/");
 
         // This is the second step (see above).
-        for (CompartmentStats *stats = data.compartmentStatsVector.begin();
-             stats != data.compartmentStatsVector.end();
-             ++stats)
+        for(CompartmentStats *stats = data.compartmentStatsVector.begin();
+            stats != data.compartmentStatsVector.end();
+            ++stats)
         {
-            nsCString &name = stats->name;
-
             gcHeapChunkUnused -=
                 stats->gcHeapArenaHeaders + stats->gcHeapArenaPadding +
                 stats->gcHeapArenaUnused +
                 stats->gcHeapObjects + stats->gcHeapStrings +
                 stats->gcHeapShapes + stats->gcHeapXml;
 
             gcHeapArenaUnused += stats->gcHeapArenaUnused;
 
-            BYTES0(mkPath(name, "gc-heap/arena-headers"),
-               JS_GC_HEAP_KIND, stats->gcHeapArenaHeaders,
-    "Memory on the compartment's garbage-collected JavaScript heap, within "
-    "arenas, that is used to hold internal book-keeping information.");
-
-            BYTES0(mkPath(name, "gc-heap/arena-padding"),
-               JS_GC_HEAP_KIND, stats->gcHeapArenaPadding,
-    "Memory on the compartment's garbage-collected JavaScript heap, within "
-    "arenas, that is unused and present only so that other data is aligned. "
-    "This constitutes internal fragmentation.");
-
-            BYTES0(mkPath(name, "gc-heap/arena-unused"),
-               JS_GC_HEAP_KIND, stats->gcHeapArenaUnused,
-    "Memory on the compartment's garbage-collected JavaScript heap, within "
-    "arenas, that could be holding useful data but currently isn't.");
-
-            BYTES0(mkPath(name, "gc-heap/objects"),
-               JS_GC_HEAP_KIND, stats->gcHeapObjects,
-    "Memory on the compartment's garbage-collected JavaScript heap that holds "
-    "objects.");
-
-            BYTES0(mkPath(name, "gc-heap/strings"),
-               JS_GC_HEAP_KIND, stats->gcHeapStrings,
-    "Memory on the compartment's garbage-collected JavaScript heap that holds "
-    "string headers.  String headers contain various pieces of information "
-    "about a string, but do not contain (except in the case of very short "
-    "strings) the string characters;  characters in longer strings are counted "
-    "under 'gc-heap/string-chars' instead.");
-
-            BYTES0(mkPath(name, "gc-heap/shapes"),
-               JS_GC_HEAP_KIND, stats->gcHeapShapes,
-    "Memory on the compartment's garbage-collected JavaScript heap that holds "
-    "shapes. A shape is an internal data structure that makes JavaScript "
-    "property accesses fast.");
-
-            BYTES0(mkPath(name, "gc-heap/xml"),
-               JS_GC_HEAP_KIND, stats->gcHeapXml,
-    "Memory on the compartment's garbage-collected JavaScript heap that holds "
-    "E4X XML objects.");
-
-            BYTES0(mkPath(name, "object-slots"),
-               nsIMemoryReporter::KIND_HEAP, stats->objectSlots,
-    "Memory allocated for the compartment's non-fixed object slot arrays, "
-    "which are used to represent object properties.  Some objects also "
-    "contain a fixed number of slots which are stored on the compartment's "
-    "JavaScript heap; those slots are not counted here, but in "
-    "'gc-heap/objects' instead.");
-
-            BYTES0(mkPath(name, "string-chars"),
-               nsIMemoryReporter::KIND_HEAP, stats->stringChars,
-    "Memory allocated to hold the compartment's string characters.  Sometimes "
-    "more memory is allocated than necessary, to simplify string "
-    "concatenation.  Each string also includes a header which is stored on the "
-    "compartment's JavaScript heap;  that header is not counted here, but in "
-    "'gc-heap/strings' instead.");
-
-            BYTES0(mkPath(name, "property-tables"),
-               nsIMemoryReporter::KIND_HEAP, stats->propertyTables,
-    "Memory allocated for the compartment's property tables.  A property "
-    "table is an internal data structure that makes JavaScript property "
-    "accesses fast.");
-
-            BYTES0(mkPath(name, "scripts"),
-               nsIMemoryReporter::KIND_HEAP, stats->scripts,
-    "Memory allocated for the compartment's JSScripts.  A JSScript is created "
-    "for each user-defined function in a script.  One is also created for "
-    "the top-level code in a script.  Each JSScript includes byte-code and "
-    "various other things.");
-
-#ifdef JS_METHODJIT
-            BYTES0(mkPath(name, "mjit-code"),
-               nsIMemoryReporter::KIND_NONHEAP, stats->mjitCode,
-    "Memory used by the method JIT to hold the compartment's generated code.");
-
-            BYTES0(mkPath(name, "mjit-data"),
-               nsIMemoryReporter::KIND_HEAP, stats->mjitData,
-    "Memory used by the method JIT for the compartment's compilation data: "
-    "JITScripts, native maps, and inline cache structs.");
-#endif
-#ifdef JS_TRACER
-            BYTES0(mkPath(name, "tjit-code"),
-               nsIMemoryReporter::KIND_NONHEAP, stats->tjitCode,
-    "Memory used by the trace JIT to hold the compartment's generated code.");
-
-            BYTES0(mkPath(name, "tjit-data/allocators-main"),
-               nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsMain,
-    "Memory used by the trace JIT to store the compartment's trace-related "
-    "data.  This data is allocated via the compartment's VMAllocators.");
-
-            BYTES0(mkPath(name, "tjit-data/allocators-reserve"),
-               nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsReserve,
-    "Memory used by the trace JIT and held in reserve for the compartment's "
-    "VMAllocators in case of OOM.");
-#endif
+            ReportCompartmentStats(*stats, pathPrefix, callback, closure);
         }
 
         JS_ASSERT(gcHeapChunkTotal % js::GC_CHUNK_SIZE == 0);
         size_t numChunks = gcHeapChunkTotal / js::GC_CHUNK_SIZE;
         PRInt64 perChunkAdmin =
             sizeof(js::gc::Chunk) - (sizeof(js::gc::Arena) * js::gc::ArenasPerChunk);
         PRInt64 gcHeapChunkAdmin = numChunks * perChunkAdmin;
         gcHeapChunkUnused -= gcHeapChunkAdmin;
 
         // Why 10000x?  100x because it's a percentage, and another 100x
         // because nsIMemoryReporter requires that for percentage amounts so
         // they can be fractional.
         PRInt64 gcHeapUnusedPercentage =
             (gcHeapChunkUnused + gcHeapArenaUnused) * 10000 /
             gXPCJSChunkAllocator.GetGCChunkBytesInUse();
 
-        BYTES(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-unused"),
-           JS_GC_HEAP_KIND, gcHeapChunkUnused,
+        ReportMemoryBytes(pathPrefix +
+                          NS_LITERAL_CSTRING("gc-heap-chunk-unused"),
+                          JS_GC_HEAP_KIND, gcHeapChunkUnused,
     "Memory on the garbage-collected JavaScript heap, within chunks, that "
-    "could be holding useful data but currently isn't.");
+    "could be holding useful data but currently isn't.",
+                          callback, closure);
 
-        BYTES(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"),
-           nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused,
+        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"),
+                          nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused,
     "The same as 'explicit/js/gc-heap-chunk-unused'.  Shown here for "
-    "easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'.");
+    "easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'.",
+                          callback, closure);
 
-        BYTES(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-admin"),
-           JS_GC_HEAP_KIND, gcHeapChunkAdmin,
+        ReportMemoryBytes(pathPrefix +
+                          NS_LITERAL_CSTRING("gc-heap-chunk-admin"),
+                          JS_GC_HEAP_KIND, gcHeapChunkAdmin,
     "Memory on the garbage-collected JavaScript heap, within chunks, that is "
-    "used to hold internal book-keeping information.");
+    "used to hold internal book-keeping information.",
+                          callback, closure);
 
-        BYTES(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"),
-           nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused,
+        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"),
+                          nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused,
     "Memory on the garbage-collected JavaScript heap, within arenas, that "
     "could be holding useful data but currently isn't.  This is the sum of "
-    "all compartments' 'gc-heap/arena-unused' numbers.");
+    "all compartments' 'gc-heap/arena-unused' numbers.",
+                          callback, closure);
 
-        PERCENTAGE(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"),
-           nsIMemoryReporter::KIND_OTHER, gcHeapUnusedPercentage,
+        ReportMemoryPercentage(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"),
+                               nsIMemoryReporter::KIND_OTHER,
+                               gcHeapUnusedPercentage,
     "Fraction of the garbage-collected JavaScript heap that is unused. "
     "Computed as ('js-gc-heap-chunk-unused' + 'js-gc-heap-arena-unused') / "
-    "'js-gc-heap'.");
+    "'js-gc-heap'.",
+                               callback, closure);
+
+        ReportJSStackSizeForRuntime(rt, pathPrefix, callback, closure);
 
         return NS_OK;
     }
 };
+
 NS_IMPL_THREADSAFE_ISUPPORTS1(
   XPConnectJSCompartmentsMultiReporter
 , nsIMemoryMultiReporter
 )
 
 #ifdef MOZ_CRASHREPORTER
 static JSBool
 DiagnosticMemoryCallback(void *ptr, size_t size)
@@ -1863,17 +1934,16 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
         if (!mWatchdogWakeup)
             NS_RUNTIMEABORT("JS_NEW_CONDVAR failed.");
 
         mJSRuntime->setActivityCallback(ActivityCallback, this);
 
         mJSRuntime->setCustomGCChunkAllocator(&gXPCJSChunkAllocator);
 
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
-        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStack));
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSSystemCompartmentCount));
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSUserCompartmentCount));
         NS_RegisterMemoryMultiReporter(new XPConnectJSCompartmentsMultiReporter);
     }
 
     if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,
                           sizeof(ObjectHolder), 512))
         mJSHolders.ops = nsnull;
--- a/js/src/xpconnect/src/xpcpublic.h
+++ b/js/src/xpconnect/src/xpcpublic.h
@@ -38,20 +38,22 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef xpcpublic_h
 #define xpcpublic_h
 
 #include "jsapi.h"
 #include "jsobj.h"
 #include "jsgc.h"
+#include "jspubtd.h"
 
 #include "nsISupports.h"
 #include "nsIPrincipal.h"
 #include "nsWrapperCache.h"
+#include "nsStringGlue.h"
 
 class nsIPrincipal;
 
 static const uint32 XPC_GC_COLOR_BLACK = 0;
 static const uint32 XPC_GC_COLOR_GRAY = 1;
 
 nsresult
 xpc_CreateGlobalObject(JSContext *cx, JSClass *clasp,
@@ -178,9 +180,72 @@ xpc_UnmarkGrayObject(JSObject *obj)
 inline JSObject*
 nsWrapperCache::GetWrapper() const
 {
   JSObject* obj = GetWrapperPreserveColor();
   xpc_UnmarkGrayObject(obj);
   return obj;
 }
 
+class nsIMemoryMultiReporterCallback;
+
+namespace mozilla {
+namespace xpconnect {
+namespace memory {
+
+struct CompartmentStats
+{
+    CompartmentStats(JSContext *cx, JSCompartment *c);
+
+    nsCString name;
+    PRInt64 gcHeapArenaHeaders;
+    PRInt64 gcHeapArenaPadding;
+    PRInt64 gcHeapArenaUnused;
+
+    PRInt64 gcHeapObjects;
+    PRInt64 gcHeapStrings;
+    PRInt64 gcHeapShapes;
+    PRInt64 gcHeapXml;
+
+    PRInt64 objectSlots;
+    PRInt64 stringChars;
+    PRInt64 propertyTables;
+
+    PRInt64 scripts;
+#ifdef JS_METHODJIT
+    PRInt64 mjitCode;
+    PRInt64 mjitData;
 #endif
+#ifdef JS_TRACER
+    PRInt64 tjitCode;
+    PRInt64 tjitDataAllocatorsMain;
+    PRInt64 tjitDataAllocatorsReserve;
+#endif
+};
+
+struct IterateData
+{
+    IterateData()
+    : compartmentStatsVector(), currCompartmentStats(NULL) { }
+
+    js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> compartmentStatsVector;
+    CompartmentStats *currCompartmentStats;
+};
+
+JSBool
+CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data);
+
+void
+ReportCompartmentStats(const CompartmentStats &stats,
+                       const nsACString &pathPrefix,
+                       nsIMemoryMultiReporterCallback *callback,
+                       nsISupports *closure);
+
+void
+ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix,
+                            nsIMemoryMultiReporterCallback *callback,
+                            nsISupports *closure);
+
+} // namespace memory
+} // namespace xpconnect
+} // namespace mozilla
+
+#endif