Bug 711130 (part 5) - Overhaul the "other measurements" measurements for JS memory consumption. r=terrence.
authorNicholas Nethercote <nnethercote@mozilla.com>
Mon, 25 Jun 2012 17:08:59 -0700
changeset 102459 ebab6b4a9c47d6bd24f005ddafe6cc575bc823aa
parent 102458 b81037d455e46ff77f872f16c95ab64122925f9c
child 102460 947e8490af73f9c522ea2f4050bb8f6d10e1bdb4
push idunknown
push userunknown
push dateunknown
reviewersterrence
bugs711130
milestone16.0a1
Bug 711130 (part 5) - Overhaul the "other measurements" measurements for JS memory consumption. r=terrence.
js/public/MemoryMetrics.h
js/src/MemoryMetrics.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcpublic.h
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
xpcom/base/nsIMemoryReporter.idl
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -25,16 +25,23 @@ namespace JS {
 
 /* Data for tracking analysis/inference memory usage. */
 struct TypeInferenceSizes
 {
     size_t scripts;
     size_t objects;
     size_t tables;
     size_t temporary;
+
+    void add(TypeInferenceSizes &sizes) {
+        this->scripts   += sizes.scripts;
+        this->objects   += sizes.objects;
+        this->tables    += sizes.tables;
+        this->temporary += sizes.temporary;
+    }
 };
 
 // These measurements relate directly to the JSRuntime, and not to
 // compartments within it.
 struct RuntimeSizes
 {
     RuntimeSizes()
       : object(0)
@@ -74,16 +81,19 @@ struct RuntimeSizes
 
 struct CompartmentStats
 {
     CompartmentStats() {
         memset(this, 0, sizeof(*this));
     }
 
     void   *extra;
+
+    // If you add a new number, remember to update add() and maybe
+    // gcHeapThingsSize()!
     size_t gcHeapArenaAdmin;
     size_t gcHeapUnusedGcThings;
 
     size_t gcHeapObjectsNonFunction;
     size_t gcHeapObjectsFunction;
     size_t gcHeapStrings;
     size_t gcHeapShapesTree;
     size_t gcHeapShapesDict;
@@ -102,62 +112,106 @@ struct CompartmentStats
     size_t shapesExtraDictTables;
     size_t shapesExtraTreeShapeKids;
     size_t shapesCompartmentTables;
     size_t scriptData;
     size_t mjitData;
     size_t crossCompartmentWrappers;
 
     TypeInferenceSizes typeInferenceSizes;
+
+    // Add cStats's numbers to this object's numbers.
+    void add(CompartmentStats &cStats) {
+        #define ADD(x)  this->x += cStats.x
+
+        ADD(gcHeapArenaAdmin);
+        ADD(gcHeapUnusedGcThings);
+
+        ADD(gcHeapObjectsNonFunction);
+        ADD(gcHeapObjectsFunction);
+        ADD(gcHeapStrings);
+        ADD(gcHeapShapesTree);
+        ADD(gcHeapShapesDict);
+        ADD(gcHeapShapesBase);
+        ADD(gcHeapScripts);
+        ADD(gcHeapTypeObjects);
+    #if JS_HAS_XML_SUPPORT
+        ADD(gcHeapXML);
+    #endif
+
+        ADD(objectSlots);
+        ADD(objectElements);
+        ADD(objectMisc);
+        ADD(stringChars);
+        ADD(shapesExtraTreeTables);
+        ADD(shapesExtraDictTables);
+        ADD(shapesExtraTreeShapeKids);
+        ADD(shapesCompartmentTables);
+        ADD(scriptData);
+        ADD(mjitData);
+        ADD(crossCompartmentWrappers);
+
+        #undef ADD
+
+        typeInferenceSizes.add(cStats.typeInferenceSizes);
+    }
+
+    // The size of all the live things in the GC heap.
+    size_t gcHeapThingsSize();
 };
 
 struct RuntimeStats
 {
     RuntimeStats(JSMallocSizeOfFun mallocSizeOf)
       : runtime()
       , gcHeapChunkTotal(0)
-      , gcHeapCommitted(0)
-      , gcHeapUnused(0)
+      , gcHeapChunkCleanDecommitted(0)
+      , gcHeapChunkDirtyDecommitted(0)
       , gcHeapUnusedChunks(0)
       , gcHeapUnusedArenas(0)
-      , gcHeapChunkCleanDecommitted(0)
-      , gcHeapChunkDirtyDecommitted(0)
       , gcHeapUnusedGcThings(0)
       , gcHeapChunkAdmin(0)
-      , totalObjects(0)
-      , totalShapes(0)
-      , totalScripts(0)
-      , totalStrings(0)
-      , totalMjit(0)
-      , totalTypeInference(0)
-      , totalAnalysisTemp(0)
+      , gcHeapGcThings(0)
+      , totals()
       , compartmentStatsVector()
       , currCompartmentStats(NULL)
       , mallocSizeOf(mallocSizeOf)
     {}
 
     RuntimeSizes runtime;
 
+    // If you add a new number, remember to update the constructor!
+
+    // Here's a useful breakdown of the GC heap.
+    //
+    // - rtStats.gcHeapChunkTotal
+    //   - decommitted bytes
+    //     - rtStats.gcHeapChunkCleanDecommitted (decommitted arenas in empty chunks)
+    //     - rtStats.gcHeapChunkDirtyDecommitted (decommitted arenas in non-empty chunks)
+    //   - unused bytes
+    //     - rtStats.gcHeapUnusedChunks (empty chunks)
+    //     - rtStats.gcHeapUnusedArenas (empty arenas within non-empty chunks)
+    //     - rtStats.total.gcHeapUnusedGcThings (empty GC thing slots within non-empty arenas)
+    //   - used bytes
+    //     - rtStats.gcHeapChunkAdmin
+    //     - rtStats.total.gcHeapArenaAdmin
+    //     - rtStats.gcHeapGcThings (in-use GC things)
+
     size_t gcHeapChunkTotal;
-    size_t gcHeapCommitted;
-    size_t gcHeapUnused;
+    size_t gcHeapChunkCleanDecommitted;
+    size_t gcHeapChunkDirtyDecommitted;
     size_t gcHeapUnusedChunks;
     size_t gcHeapUnusedArenas;
-    size_t gcHeapChunkCleanDecommitted;
-    size_t gcHeapChunkDirtyDecommitted;
     size_t gcHeapUnusedGcThings;
     size_t gcHeapChunkAdmin;
-    size_t totalObjects;
-    size_t totalShapes;
-    size_t totalScripts;
-    size_t totalStrings;
-    size_t totalMjit;
-    size_t totalTypeInference;
-    size_t totalAnalysisTemp;
+    size_t gcHeapGcThings;
 
+    // The sum of all compartment's measurements.
+    CompartmentStats totals;
+ 
     js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> compartmentStatsVector;
     CompartmentStats *currCompartmentStats;
 
     JSMallocSizeOfFun mallocSizeOf;
 
     virtual void initExtraCompartmentStats(JSCompartment *c, CompartmentStats *cstats) = 0;
 };
 
--- a/js/src/MemoryMetrics.cpp
+++ b/js/src/MemoryMetrics.cpp
@@ -17,16 +17,44 @@
 #include "jsobjinlines.h"
 
 #ifdef JS_THREADSAFE
 
 namespace JS {
 
 using namespace js;
 
+size_t
+CompartmentStats::gcHeapThingsSize()
+{
+    // These are just the GC-thing measurements.
+    size_t n = 0;
+    n += gcHeapObjectsNonFunction;
+    n += gcHeapObjectsFunction;
+    n += gcHeapStrings;
+    n += gcHeapShapesTree;
+    n += gcHeapShapesDict;
+    n += gcHeapShapesBase;
+    n += gcHeapScripts;
+    n += gcHeapTypeObjects;
+#if JS_HAS_XML_SUPPORT
+    n += gcHeapXML;
+#endif
+
+#ifdef DEBUG
+    size_t n2 = n;
+    n2 += gcHeapArenaAdmin;
+    n2 += gcHeapUnusedGcThings;
+    // These numbers should sum to a multiple of the arena size.
+    JS_ASSERT(n2 % gc::ArenaSize == 0);
+#endif
+
+    return n;
+}
+
 static void
 StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment)
 {
     // Append a new CompartmentStats to the vector.
     RuntimeStats *rtStats = static_cast<RuntimeStats *>(data);
 
     // CollectRuntimeStats reserves enough space.
     MOZ_ALWAYS_TRUE(rtStats->compartmentStatsVector.growBy(1));
@@ -152,98 +180,61 @@ StatsCellCallback(JSRuntime *rt, void *d
 }
 
 JS_PUBLIC_API(bool)
 CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats)
 {
     if (!rtStats->compartmentStatsVector.reserve(rt->compartments.length()))
         return false;
 
-    rtStats->gcHeapChunkCleanDecommitted =
-        rt->gcChunkPool.countCleanDecommittedArenas(rt) * gc::ArenaSize;
-    rtStats->gcHeapUnusedChunks =
-        size_t(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize -
-        rtStats->gcHeapChunkCleanDecommitted;
     rtStats->gcHeapChunkTotal =
         size_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
 
+    rtStats->gcHeapChunkCleanDecommitted =
+        rt->gcChunkPool.countCleanDecommittedArenas(rt) * gc::ArenaSize;
+
+    rtStats->gcHeapUnusedChunks =
+        size_t(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize -
+        rtStats->gcHeapChunkCleanDecommitted;
+
+    // This just computes rtStats->gcHeapChunkDirtyDecommitted.
+    IterateChunks(rt, rtStats, StatsChunkCallback);
+
+    // Take the per-compartment measurements.
     IterateCompartmentsArenasCells(rt, rtStats, StatsCompartmentCallback,
                                    StatsArenaCallback, StatsCellCallback);
-    IterateChunks(rt, rtStats, StatsChunkCallback);
 
+    // Take the "explcit/js/runtime/" measurements.
     rt->sizeOfIncludingThis(rtStats->mallocSizeOf, &rtStats->runtime);
 
-    // This is initialized to all bytes stored in used chunks, and then we
-    // subtract used space from it each time around the loop.
-    rtStats->gcHeapUnusedArenas = rtStats->gcHeapChunkTotal -
-                                  rtStats->gcHeapUnusedChunks -
-                                  rtStats->gcHeapChunkCleanDecommitted -
-                                  rtStats->gcHeapChunkDirtyDecommitted;
-
-    rtStats->totalMjit = rtStats->runtime.mjitCode;
-
-    for (size_t index = 0;
-         index < rtStats->compartmentStatsVector.length();
-         index++) {
-        CompartmentStats &cStats = rtStats->compartmentStatsVector[index];
+    rtStats->gcHeapGcThings = 0;
+    for (size_t i = 0; i < rtStats->compartmentStatsVector.length(); i++) {
+        CompartmentStats &cStats = rtStats->compartmentStatsVector[i];
 
-        size_t used = cStats.gcHeapArenaAdmin +
-                      cStats.gcHeapUnusedGcThings +
-                      cStats.gcHeapObjectsNonFunction +
-                      cStats.gcHeapObjectsFunction +
-                      cStats.gcHeapStrings +
-                      cStats.gcHeapShapesTree +
-                      cStats.gcHeapShapesDict +
-                      cStats.gcHeapShapesBase +
-                      cStats.gcHeapScripts +
-#if JS_HAS_XML_SUPPORT
-                      cStats.gcHeapXML +
-#endif
-                      cStats.gcHeapTypeObjects;
-
-        rtStats->gcHeapUnusedArenas -= used;
-        rtStats->gcHeapUnusedGcThings += cStats.gcHeapUnusedGcThings;
-        rtStats->totalObjects += cStats.gcHeapObjectsNonFunction +
-                                 cStats.gcHeapObjectsFunction +
-                                 cStats.objectSlots +
-                                 cStats.objectElements +
-                                 cStats.objectMisc;
-        rtStats->totalShapes  += cStats.gcHeapShapesTree +
-                                 cStats.gcHeapShapesDict +
-                                 cStats.gcHeapShapesBase +
-                                 cStats.shapesExtraTreeTables +
-                                 cStats.shapesExtraDictTables +
-                                 cStats.shapesCompartmentTables;
-        rtStats->totalScripts += cStats.gcHeapScripts +
-                                 cStats.scriptData;
-        rtStats->totalStrings += cStats.gcHeapStrings +
-                                 cStats.stringChars;
-        rtStats->totalMjit    += cStats.mjitData;
-        rtStats->totalTypeInference += cStats.gcHeapTypeObjects +
-                                       cStats.typeInferenceSizes.objects +
-                                       cStats.typeInferenceSizes.scripts +
-                                       cStats.typeInferenceSizes.tables;
-        rtStats->totalAnalysisTemp  += cStats.typeInferenceSizes.temporary;
+        rtStats->totals.add(cStats);
+        rtStats->gcHeapGcThings += cStats.gcHeapThingsSize();
     }
 
     size_t numDirtyChunks =
         (rtStats->gcHeapChunkTotal - rtStats->gcHeapUnusedChunks) / gc::ChunkSize;
     size_t perChunkAdmin =
         sizeof(gc::Chunk) - (sizeof(gc::Arena) * gc::ArenasPerChunk);
     rtStats->gcHeapChunkAdmin = numDirtyChunks * perChunkAdmin;
     rtStats->gcHeapUnusedArenas -= rtStats->gcHeapChunkAdmin;
 
-    rtStats->gcHeapUnused = rtStats->gcHeapUnusedArenas +
-                            rtStats->gcHeapUnusedChunks +
-                            rtStats->gcHeapUnusedGcThings;
-
-    rtStats->gcHeapCommitted = rtStats->gcHeapChunkTotal -
-                               rtStats->gcHeapChunkCleanDecommitted -
-                               rtStats->gcHeapChunkDirtyDecommitted;
-
+    // |gcHeapUnusedArenas| is the only thing left.  Compute it in terms of
+    // all the others.  See the comment in RuntimeStats for explanation.
+    rtStats->gcHeapUnusedArenas = rtStats->gcHeapChunkTotal -
+                                  rtStats->gcHeapChunkCleanDecommitted -
+                                  rtStats->gcHeapChunkDirtyDecommitted -
+                                  rtStats->gcHeapUnusedChunks -
+                                  rtStats->totals.gcHeapUnusedGcThings -
+                                  rtStats->gcHeapChunkAdmin -
+                                  rtStats->totals.gcHeapArenaAdmin -
+                                  rtStats->gcHeapGcThings;
     return true;
 }
 
 JS_PUBLIC_API(int64_t)
 GetExplicitNonHeapForRuntime(JSRuntime *rt, JSMallocSizeOfFun mallocSizeOf)
 {
     // explicit/<compartment>/gc-heap/*
     size_t n = size_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1222,338 +1222,351 @@ static const size_t SUNDRIES_THRESHOLD =
 #define REPORT(_path, _kind, _units, _amount, _desc)                          \
     do {                                                                      \
         nsresult rv;                                                          \
         rv = cb->Callback(EmptyCString(), _path, _kind, _units, _amount,      \
                           NS_LITERAL_CSTRING(_desc), closure);                \
         NS_ENSURE_SUCCESS(rv, rv);                                            \
     } while (0)
 
-#define CREPORT(_path, _kind, _units, _amount, _desc)                         \
-    do {                                                                      \
-        size_t amount = _amount;  /* evaluate _amount only once */            \
-        if (amount >= SUNDRIES_THRESHOLD) {                                   \
-            nsresult rv;                                                      \
-            rv = cb->Callback(EmptyCString(), _path, _kind, _units, amount,   \
-                              NS_LITERAL_CSTRING(_desc), closure);            \
-            NS_ENSURE_SUCCESS(rv, rv);                                        \
-        } else {                                                              \
-            otherSundries += amount;                                          \
-        }                                                                     \
-    } while (0)
-
 #define REPORT_BYTES(_path, _kind, _amount, _desc)                            \
     REPORT(_path, _kind, nsIMemoryReporter::UNITS_BYTES, _amount, _desc);
 
-#define CREPORT_BYTES(_path, _kind, _amount, _desc)                           \
-    CREPORT(_path, _kind, nsIMemoryReporter::UNITS_BYTES, _amount, _desc);
-
 #define REPORT_GC_BYTES(_path, _amount, _desc)                                \
     do {                                                                      \
         size_t amount = _amount;  /* evaluate _amount only once */            \
         nsresult rv;                                                          \
         rv = cb->Callback(EmptyCString(), _path,                              \
                           nsIMemoryReporter::KIND_NONHEAP,                    \
                           nsIMemoryReporter::UNITS_BYTES, amount,             \
                           NS_LITERAL_CSTRING(_desc), closure);                \
         NS_ENSURE_SUCCESS(rv, rv);                                            \
         gcTotal += amount;                                                    \
     } while (0)
 
+// Nb: all non-GC compartment reports are currently KIND_HEAP, and this macro
+// relies on that.
+#define CREPORT_BYTES(_path, _amount, _desc)                                  \
+    do {                                                                      \
+        size_t amount = _amount;  /* evaluate _amount only once */            \
+        if (amount >= SUNDRIES_THRESHOLD) {                                   \
+            nsresult rv;                                                      \
+            rv = cb->Callback(EmptyCString(), _path,                          \
+                              nsIMemoryReporter::KIND_HEAP,                   \
+                              nsIMemoryReporter::UNITS_BYTES, amount,         \
+                              NS_LITERAL_CSTRING(_desc), closure);            \
+            NS_ENSURE_SUCCESS(rv, rv);                                        \
+        } else {                                                              \
+            otherSundries += amount;                                          \
+        }                                                                     \
+    } while (0)
+
 #define CREPORT_GC_BYTES(_path, _amount, _desc)                               \
     do {                                                                      \
         size_t amount = _amount;  /* evaluate _amount only once */            \
         if (amount >= SUNDRIES_THRESHOLD) {                                   \
             nsresult rv;                                                      \
             rv = cb->Callback(EmptyCString(), _path,                          \
                               nsIMemoryReporter::KIND_NONHEAP,                \
                               nsIMemoryReporter::UNITS_BYTES, amount,         \
                               NS_LITERAL_CSTRING(_desc), closure);            \
             NS_ENSURE_SUCCESS(rv, rv);                                        \
             gcTotal += amount;                                                \
         } else {                                                              \
             gcHeapSundries += amount;                                         \
         }                                                                     \
     } while (0)
 
-template <int N>
-inline const nsCString
-MakePath(const nsACString &pathPrefix, const JS::CompartmentStats &cStats,
-         const char (&reporterName)[N])
-{
-    const char *name = static_cast<char *>(cStats.extra);
-    if (!name)
-        name = "error while initializing compartment name";
-    return pathPrefix + NS_LITERAL_CSTRING("compartment(") +
-           nsDependentCString(name) + NS_LITERAL_CSTRING(")/") +
-           nsDependentCString(reporterName);
-}
+#define RREPORT_BYTES(_path, _kind, _amount, _desc)                           \
+    do {                                                                      \
+        size_t amount = _amount;  /* evaluate _amount only once */            \
+        nsresult rv;                                                          \
+        rv = cb->Callback(EmptyCString(), _path, _kind,                       \
+                          nsIMemoryReporter::UNITS_BYTES, amount,             \
+                          NS_LITERAL_CSTRING(_desc), closure);                \
+        NS_ENSURE_SUCCESS(rv, rv);                                            \
+        rtTotal += amount;                                                    \
+    } while (0)
 
 namespace xpc {
 
 static nsresult
 ReportCompartmentStats(const JS::CompartmentStats &cStats,
                        const nsACString &pathPrefix,
                        nsIMemoryMultiReporterCallback *cb,
-                       nsISupports *closure, size_t *gcTotalOut)
+                       nsISupports *closure, size_t *gcTotalOut = NULL)
 {
     size_t gcTotal = 0, gcHeapSundries = 0, otherSundries = 0;
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/arena-admin"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/arena-admin"),
                      cStats.gcHeapArenaAdmin,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap, within arenas, that is used (a) to hold internal "
                      "bookkeeping information, and (b) to provide padding to "
                      "align GC things.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/unused-gc-things"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-gc-things"),
                      cStats.gcHeapUnusedGcThings,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap taken by empty GC thing slots within non-empty "
                      "arenas.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/objects/non-function"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/objects/non-function"),
                      cStats.gcHeapObjectsNonFunction,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds non-function objects.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/objects/function"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/objects/function"),
                      cStats.gcHeapObjectsFunction,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds function objects.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/strings"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/strings"),
                      cStats.gcHeapStrings,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the 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.");
+                     "counted under 'gc-heap/string-chars' instead.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/scripts"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/scripts"),
                      cStats.gcHeapScripts,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds JSScript instances. A JSScript is "
                      "created for each user-defined function in a script. One "
                      "is also created for the top-level code in a script.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/shapes/tree"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/tree"),
                      cStats.gcHeapShapesTree,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds shapes that are in a property tree.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/shapes/dict"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/dict"),
                      cStats.gcHeapShapesDict,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds shapes that are in dictionary mode.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/shapes/base"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/base"),
                      cStats.gcHeapShapesBase,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that collates data common to many shapes.");
 
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/type-objects"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/type-objects"),
                      cStats.gcHeapTypeObjects,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds type inference information.");
 
 #if JS_HAS_XML_SUPPORT
-    CREPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/xml"),
+    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/xml"),
                      cStats.gcHeapXML,
-                     "Memory on the compartment's garbage-collected JavaScript "
+                     "Memory on the garbage-collected JavaScript "
                      "heap that holds E4X XML objects.");
 #endif
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "objects/slots"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.objectSlots,
-                  "Memory allocated for the compartment's non-fixed object "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("objects/slots"),
+                  cStats.objectSlots,
+                  "Memory allocated for the 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 "
+                  "stored on the JavaScript heap; those slots "
                   "are not counted here, but in 'gc-heap/objects' instead.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "objects/elements"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.objectElements,
-                  "Memory allocated for the compartment's object element "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("objects/elements"),
+                  cStats.objectElements,
+                  "Memory allocated for object element "
                   "arrays, which are used to represent indexed object "
                   "properties.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "objects/misc"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.objectMisc,
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("objects/misc"),
+                  cStats.objectMisc,
                   "Memory allocated for various small, miscellaneous "
                   "structures that hang off certain kinds of objects.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "string-chars"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.stringChars,
-                  "Memory allocated to hold the compartment's string "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("string-chars"),
+                  cStats.stringChars,
+                  "Memory allocated to hold 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.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "shapes-extra/tree-tables"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.shapesExtraTreeTables,
-                  "Memory allocated for the compartment's property tables "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-tables"),
+                  cStats.shapesExtraTreeTables,
+                  "Memory allocated for the property tables "
                   "that belong to shapes that are in a property tree.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "shapes-extra/dict-tables"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.shapesExtraDictTables,
-                  "Memory allocated for the compartment's property tables "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/dict-tables"),
+                  cStats.shapesExtraDictTables,
+                  "Memory allocated for the property tables "
                   "that belong to shapes that are in dictionary mode.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "shapes-extra/tree-shape-kids"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.shapesExtraTreeShapeKids,
-                  "Memory allocated for the compartment's kid hashes that "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-shape-kids"),
+                  cStats.shapesExtraTreeShapeKids,
+                  "Memory allocated for the kid hashes that "
                   "belong to shapes that are in a property tree.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "shapes-extra/compartment-tables"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.shapesCompartmentTables,
-                  "Memory used by compartment wide tables storing shape "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/compartment-tables"),
+                  cStats.shapesCompartmentTables,
+                  "Memory used by compartment-wide tables storing shape "
                   "information for use during object construction.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "script-data"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.scriptData,
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("script-data"),
+                  cStats.scriptData,
                   "Memory allocated for JSScript bytecode and various "
                   "variable-length tables.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "mjit-data"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.mjitData,
-                  "Memory used by the method JIT for the compartment's "
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("mjit-data"),
+                  cStats.mjitData,
+                  "Memory used by the method JIT for "
                   "compilation data: JITScripts, native maps, and inline "
                   "cache structs.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "cross-compartment-wrappers"),
-                  nsIMemoryReporter::KIND_HEAP, cStats.crossCompartmentWrappers,
-                  "Memory used by the compartment's cross-compartment "
-                  "wrappers.");
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrappers"),
+                  cStats.crossCompartmentWrappers,
+                  "Memory used by cross-compartment wrappers.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "type-inference/script-main"),
-                  nsIMemoryReporter::KIND_HEAP,
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-inference/script-main"),
                   cStats.typeInferenceSizes.scripts,
                   "Memory used during type inference to store type sets of "
                   "variables and dynamically observed types.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "type-inference/object-main"),
-                  nsIMemoryReporter::KIND_HEAP,
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-inference/object-main"),
                   cStats.typeInferenceSizes.objects,
                   "Memory used during type inference to store types and "
                   "possible property types of JS objects.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "type-inference/tables"),
-                  nsIMemoryReporter::KIND_HEAP,
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-inference/tables"),
                   cStats.typeInferenceSizes.tables,
                   "Memory used during type inference for compartment-wide "
                   "tables.");
 
-    CREPORT_BYTES(MakePath(pathPrefix, cStats, "analysis-temporary"),
-                  nsIMemoryReporter::KIND_HEAP,
+    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("analysis-temporary"),
                   cStats.typeInferenceSizes.temporary,
                   "Memory used during type inference and compilation to hold "
                   "transient analysis information.  Cleared on GC.");
 
     if (gcHeapSundries > 0) {
-        REPORT_GC_BYTES(MakePath(pathPrefix, cStats, "gc-heap/sundries"),
+        // We deliberately don't use CREPORT_GC_BYTES here.
+        REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/sundries"),
                         gcHeapSundries,
-                        "The sum of all this compartment's gc-heap "
+                        "The sum of all the gc-heap "
                         "measurements that are too small to be worth showing "
                         "individually.");
     }
 
     if (otherSundries > 0) {
-        REPORT_BYTES(MakePath(pathPrefix, cStats, "other-sundries"),
-                     nsIMemoryReporter::KIND_HEAP,
-                     otherSundries,
-                     "The sum of all this compartment's non-gc-heap "
+        // We deliberately don't use CREPORT_BYTES here.
+        REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("other-sundries"),
+                     nsIMemoryReporter::KIND_HEAP, otherSundries,
+                     "The sum of all the non-gc-heap "
                      "measurements that are too small to be worth showing "
                      "individually.");
     }
 
-    *gcTotalOut += gcTotal;
+    if (gcTotalOut) {
+        *gcTotalOut += gcTotal;
+    }
 
     return NS_OK;
 }
 
 nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
                                  const nsACString &pathPrefix,
                                  nsIMemoryMultiReporterCallback *cb,
-                                 nsISupports *closure)
+                                 nsISupports *closure, size_t *rtTotalOut)
 {
     nsresult rv;
+
+    // Report each compartment's numbers.
+
     size_t gcTotal = 0;
-    for (size_t index = 0;
-         index < rtStats.compartmentStatsVector.length();
-         index++) {
-        rv = ReportCompartmentStats(rtStats.compartmentStatsVector[index],
-                                    pathPrefix, cb, closure, &gcTotal);
+    for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) {
+        JS::CompartmentStats cStats = rtStats.compartmentStatsVector[i];
+        const char *name = static_cast<char *>(cStats.extra);
+        nsCString pathPrefix2 = pathPrefix + NS_LITERAL_CSTRING("compartment(") +
+                                nsDependentCString(name) + NS_LITERAL_CSTRING(")/");
+
+        rv = ReportCompartmentStats(cStats, pathPrefix2, cb, closure, &gcTotal);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/runtime-object"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.object,
-                 "Memory used by the JSRuntime object.");
+    // Report the rtStats.runtime numbers under "runtime/", and compute their
+    // total for later.
+
+    size_t rtTotal = 0;
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/atoms-table"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.atomsTable,
-                 "Memory used by the atoms table.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/runtime-object"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.object,
+                  "Memory used by the JSRuntime object.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/contexts"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.contexts,
-                 "Memory used by JSContext objects and certain structures "
-                 "hanging off them.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/atoms-table"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.atomsTable,
+                  "Memory used by the atoms table.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/dtoa"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.dtoa,
-                 "Memory used by DtoaState, which is used for converting "
-                 "strings to numbers and vice versa.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/contexts"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.contexts,
+                  "Memory used by JSContext objects and certain structures "
+                  "hanging off them.");
+
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/dtoa"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.dtoa,
+                  "Memory used by DtoaState, which is used for converting "
+                  "strings to numbers and vice versa.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/temporary"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.temporary,
-                 "Memory held transiently in JSRuntime and used during "
-                 "compilation.  It mostly holds parse nodes.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/temporary"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.temporary,
+                  "Memory held transiently in JSRuntime and used during "
+                  "compilation.  It mostly holds parse nodes.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/mjit-code"),
-                 nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.mjitCode,
-                 "Memory used by the method JIT to hold the runtime's "
-                 "generated code.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/mjit-code"),
+                  nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.mjitCode,
+                  "Memory used by the method JIT to hold the runtime's "
+                  "generated code.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/regexp-code"),
-                 nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.regexpCode,
-                 "Memory used by the regexp JIT to hold generated code.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/regexp-code"),
+                  nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.regexpCode,
+                  "Memory used by the regexp JIT to hold generated code.");
+
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/unused-code-memory"),
+                  nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.unusedCodeMemory,
+                  "Memory allocated by the method and/or regexp JIT to hold the "
+                  "runtime's code, but which is currently unused.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/unused-code-memory"),
-                 nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.unusedCodeMemory,
-                 "Memory allocated by the method and/or regexp JIT to hold the "
-                 "runtime's code, but which is currently unused.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/stack-committed"),
+                  nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.stackCommitted,
+                  "Memory used for the JS call stack.  This is the committed "
+                  "portion of the stack; the uncommitted portion is not "
+                  "measured because it hardly costs anything.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/stack-committed"),
-                 nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.stackCommitted,
-                 "Memory used for the JS call stack.  This is the committed "
-                 "portion of the stack; the uncommitted portion is not "
-                 "measured because it hardly costs anything.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/gc-marker"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.gcMarker,
+                  "Memory used for the GC mark stack and gray roots.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/gc-marker"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.gcMarker,
-                 "Memory used for the GC mark stack and gray roots.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/math-cache"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.mathCache,
+                  "Memory used for the math cache.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/math-cache"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.mathCache,
-                 "Memory used for the math cache.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/script-filenames"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.scriptFilenames,
+                  "Memory used for the table holding script filenames.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/script-filenames"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.scriptFilenames,
-                 "Memory used for the table holding script filenames.");
+    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/compartment-objects"),
+                  nsIMemoryReporter::KIND_HEAP, rtStats.runtime.compartmentObjects,
+                  "Memory used for JSCompartment objects.  These are fairly "
+                  "small and all the same size, so they're not worth reporting "
+                  "on a per-compartment basis.");
 
-    REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/compartment-objects"),
-                 nsIMemoryReporter::KIND_HEAP, rtStats.runtime.compartmentObjects,
-                 "Memory used for JSCompartment objects.  These are fairly "
-                 "small and all the same size, so they're not worth reporting "
-                 "on a per-compartment basis.");
+    if (rtTotalOut) {
+        *rtTotalOut = rtTotal;
+    }
+
+    // Report GC numbers that don't belong to a compartment.
 
     REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-arenas"),
                     rtStats.gcHeapUnusedArenas,
                     "Memory on the garbage-collected JavaScript heap taken by "
                     "empty arenas within non-empty chunks.");
 
     REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-chunks"),
                     rtStats.gcHeapUnusedChunks,
@@ -1692,135 +1705,93 @@ public:
             xpcrt->SizeOfIncludingThis(JsMallocSizeOf) +
             XPCWrappedNativeScope::SizeOfAllScopesIncludingThis(JsMallocSizeOf);
 
         NS_NAMED_LITERAL_CSTRING(explicitJs, "explicit/js/");
 
         // This is the second step (see above).  First we report stuff in the
         // "explicit" tree, then we report other stuff.
 
+        size_t rtTotal = 0;
         nsresult rv =
             xpc::ReportJSRuntimeExplicitTreeStats(rtStats, explicitJs, cb,
-                                                  closure);
+                                                  closure, &rtTotal);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // Report the sums of the compartment numbers.
+        rv = ReportCompartmentStats(rtStats.totals,
+                                    NS_LITERAL_CSTRING("js-main-runtime/compartments/"),
+                                    cb, closure);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        REPORT_BYTES(explicitJs + NS_LITERAL_CSTRING("xpconnect"),
-                     nsIMemoryReporter::KIND_HEAP, xpconnect,
-                     "Memory used by XPConnect.");
+        // Report the sum of the runtime/ numbers.
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/runtime"),
+                     nsIMemoryReporter::KIND_OTHER, rtTotal,
+                     "The sum of all measurements under 'explicit/js/runtime/'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-unused-arenas"),
+        // Report the numbers for memory outside of compartments.
+
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/decommitted"),
                      nsIMemoryReporter::KIND_OTHER,
-                     rtStats.gcHeapUnusedArenas,
-                     "The same as 'explicit/js/gc-heap/unused-arenas'. "
-                     "Shown here for easy comparison with other 'js-gc' "
-                     "reporters.");
+                     rtStats.gcHeapChunkCleanDecommitted + rtStats.gcHeapChunkDirtyDecommitted,
+                     "The same as 'explicit/js/gc-heap/decommitted'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-unused-chunks"),
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapUnusedChunks,
-                     "The same as 'explicit/js/gc-heap/unused-chunks'.  "
-                     "Shown here for easy comparison with other 'js-gc' "
-                     "reporters.");
+                     "The same as 'explicit/js/gc-heap/unused-chunks'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-unused-gc-things"),
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-arenas"),
                      nsIMemoryReporter::KIND_OTHER,
-                     rtStats.gcHeapUnusedGcThings,
-                     "Memory on the main JSRuntime's garbage-collected "
-                     "JavaScript heap taken by empty GC thing slots within "
-                     "non-empty arenas. This is the sum of all compartments' "
-                     "'gc-heap/unused-gc-things' numbers.");
-
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed-unused"),
-                     nsIMemoryReporter::KIND_OTHER,
-                     rtStats.gcHeapUnused,
-                     "Amount of the GC heap that's committed, but that is "
-                     "neither part of an active allocation nor being used for "
-                     "bookkeeping.  Equal to 'gc-heap-unused-chunks' + "
-                     "'gc-heap-unused-arenas' + 'gc-heap-unused-gc-things'.");
+                     rtStats.gcHeapUnusedArenas,
+                     "The same as 'explicit/js/gc-heap/unused-arenas'.");
 
-        // Why 10000x?  100x because it's a percentage, and another 100x
-        // because nsIMemoryReporter requires that for UNITS_PERCENTAGE
-        // reporters so we can get two decimal places out of the integer value.
-        REPORT(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed-unused-ratio"),
-               nsIMemoryReporter::KIND_OTHER,
-               nsIMemoryReporter::UNITS_PERCENTAGE,
-               (PRInt64) 10000 * rtStats.gcHeapUnused /
-                   ((double) rtStats.gcHeapCommitted - rtStats.gcHeapUnused),
-               "Ratio of committed, unused bytes to allocated bytes; i.e. "
-               "'gc-heap-committed-unused' / 'gc-heap-allocated'.  This "
-               "measures the overhead of the GC heap allocator relative to the "
-               "amount of memory allocated.");
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/chunk-admin"),
+                     nsIMemoryReporter::KIND_OTHER,
+                     rtStats.gcHeapChunkAdmin,
+                     "The same as 'explicit/js/gc-heap/chunk-admin'.");
+
+        // Report a breakdown of the committed GC space.
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed"),
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/chunks"),
                      nsIMemoryReporter::KIND_OTHER,
-                     rtStats.gcHeapCommitted,
-                     "Committed memory (i.e., in physical memory or swap) "
-                     "used by the garbage-collected JavaScript heap.");
+                     rtStats.gcHeapUnusedChunks,
+                     "The same as 'explicit/js/gc-heap/unused-chunks'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-allocated"),
-                     nsIMemoryReporter::KIND_OTHER,
-                     (rtStats.gcHeapCommitted - rtStats.gcHeapUnused),
-                     "Amount of the GC heap used for active allocations and "
-                     "bookkeeping.  This is calculated as 'gc-heap-committed' "
-                     "- 'gc-heap-unused'.");
-
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-decommitted"),
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/arenas"),
                      nsIMemoryReporter::KIND_OTHER,
-                     rtStats.gcHeapChunkCleanDecommitted + rtStats.gcHeapChunkDirtyDecommitted,
-                     "The same as 'explicit/js/gc-heap/decommitted'.  Shown "
-                     "here for easy comparison with other 'js-gc' reporters.");
+                     rtStats.gcHeapUnusedArenas,
+                     "The same as 'explicit/js/gc-heap/unused-arenas'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-objects"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalObjects,
-                     "Memory used for all object-related data in the main "
-                     "JSRuntime. This is the sum of all compartments' "
-                     "'gc-heap/objects-non-function', "
-                     "'gc-heap/objects-function' and 'object-slots' numbers.");
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things"),
+                     nsIMemoryReporter::KIND_OTHER,
+                     rtStats.totals.gcHeapUnusedGcThings,
+                     "The same as 'js-main-runtime/compartments/gc-heap/unused-gc-things'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-shapes"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalShapes,
-                     "Memory used for all shape-related data in the main "
-                     "JSRuntime. This is the sum of all compartments' "
-                     "'gc-heap/shapes/tree', 'gc-heap/shapes/dict', "
-                     "'gc-heap/shapes/base', 'shapes-extra/tree-tables', "
-                     "'shapes-extra/dict-tables', "
-                     "'shapes-extra/tree-shape-kids' and "
-                     "'shapes-extra/empty-shape-arrays'.");
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"),
+                     nsIMemoryReporter::KIND_OTHER,
+                     rtStats.gcHeapChunkAdmin,
+                     "The same as 'explicit/js/gc-heap/chunk-admin'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-scripts"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalScripts,
-                     "Memory used for all script-related data in the main "
-                     "JSRuntime. This is the sum of all compartments' "
-                     "'gc-heap/scripts' and 'script-data' numbers.");
-
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-strings"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalStrings,
-                     "Memory used for all string-related data in the main "
-                     "JSRuntime. This is the sum of all compartments' "
-                     "'gc-heap/strings' and 'string-chars' numbers.");
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/arena-admin"),
+                     nsIMemoryReporter::KIND_OTHER,
+                     rtStats.totals.gcHeapArenaAdmin,
+                     "The same as 'js-main-runtime/compartments/gc-heap/arena-admin'.");
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-mjit"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalMjit,
-                     "Memory used by the method JIT in the main JSRuntime.  "
-                     "This is the sum of all compartments' 'mjit/code', and "
-                     "'mjit/data' numbers.");
+        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things"),
+                     nsIMemoryReporter::KIND_OTHER,
+                     rtStats.gcHeapGcThings,
+                     "Memory on the garbage-collected JavaScript heap that holds GC things such "
+                     "as objects, strings, scripts, etc.")
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-type-inference"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalTypeInference,
-                     "Non-transient memory used by type inference in the main "
-                     "JSRuntime. This is the sum of all compartments' "
-                     "'gc-heap/type-objects', 'type-inference/script-main', "
-                     "'type-inference/object-main' and "
-                     "'type-inference/tables' numbers.");
+        // Report xpconnect.
 
-        REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-analysis-temporary"),
-                     nsIMemoryReporter::KIND_OTHER, rtStats.totalAnalysisTemp,
-                     "Memory used transiently during type inference and "
-                     "compilation in the main JSRuntime. This is the sum of "
-                     "all compartments' 'analysis-temporary' numbers.");
+        REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect"),
+                     nsIMemoryReporter::KIND_HEAP, xpconnect,
+                     "Memory used by XPConnect.");
 
         return NS_OK;
     }
 
     NS_IMETHOD
     GetExplicitNonHeap(PRInt64 *n)
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -263,17 +263,17 @@ DOM_DefineQuickStubs(JSContext *cx, JSOb
                      PRUint32 interfaceCount, const nsIID **interfaceArray);
 
 // This reports all the stats in |rtStats| that belong in the "explicit" tree,
 // (which isn't all of them).
 nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
                                  const nsACString &pathPrefix,
                                  nsIMemoryMultiReporterCallback *cb,
-                                 nsISupports *closure);
+                                 nsISupports *closure, size_t *rtTotal = NULL);
 
 /**
  * Convert a jsval to PRInt64. Return true on success.
  */
 inline bool
 ValueToInt64(JSContext *cx, JS::Value v, int64_t *result)
 {
     if (JSVAL_IS_INT(v)) {
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -506,17 +506,16 @@ function getTreesByProcess(aMgr, aTreesB
              "non-sentence explicit description");
 
     } else if (isSmapsPath(aUnsafePath)) {
       assert(aKind === KIND_NONHEAP, "bad smaps kind");
       assert(aUnits === UNITS_BYTES, "bad smaps units");
       assert(aDescription !== "", "empty smaps description");
 
     } else {
-      assert(aKind === KIND_OTHER, "bad other kind");
       assert(gSentenceRegExp.test(aDescription),
              "non-sentence other description");
     }
 
     let process = aProcess === "" ? "Main" : aProcess;
     let unsafeNames = aUnsafePath.split('/');
     let unsafeName0 = unsafeNames[0];
     let isDegenerate = unsafeNames.length === 1;
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -58,17 +58,17 @@
   {
     // Record the values of some notable reporters.
     if (aPath === "explicit") {
       explicitAmounts.push(aAmount);
     } else if (aPath === "vsize") {
       vsizeAmounts.push(aAmount);
     } else if (aPath === "resident") {
       residentAmounts.push(aAmount);
-    } else if (aPath === "js-main-runtime-gc-heap-committed") {
+    } else if (aPath === "js-main-runtime-gc-heap-committed/used/gc-things") {
       jsGcHeapAmounts.push(aAmount); 
     } else if (aPath === "heap-allocated") {
       heapAllocatedAmounts.push(aAmount);
     } else if (aPath === "storage-sqlite") {
       storageSqliteAmounts.push(aAmount);
 
     // Check the presence of some other notable reporters.
     } else if (aPath.search(/^explicit\/js\/compartment\(/) >= 0) {
@@ -136,17 +136,17 @@
 
   // If mgr.explicit failed, we won't have "heap-allocated" either.
   if (haveExplicit) {
     checkSpecialReport("explicit",       explicitAmounts);
     checkSpecialReport("heap-allocated", heapAllocatedAmounts);
   }
   checkSpecialReport("vsize",          vsizeAmounts);
   checkSpecialReport("resident",       residentAmounts);
-  checkSpecialReport("js-main-runtime-gc-heap-committed", jsGcHeapAmounts);
+  checkSpecialReport("js-main-runtime-gc-heap-committed/used/gc-things", jsGcHeapAmounts);
   checkSpecialReport("storage-sqlite", storageSqliteAmounts);
 
   ok(areJsCompartmentsPresent,  "js compartments are present");
   ok(isSandboxLocationShown,    "sandbox locations are present");
   ok(areWindowObjectsPresent,   "window objects are present");
   ok(isPlacesPresent,           "places is present");
   ok(isImagesPresent,           "images is present");
   ok(isXptiWorkingSetPresent,   "xpti-working-set is present");
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -40,18 +40,18 @@ interface nsISimpleEnumerator;
  *   Reporters in this category must have kind NONHEAP, units BYTES, and a
  *   non-empty description.
  *
  * - The "compartments" and "ghost-windows" trees are optional.  They are
  *   used by about:compartments.  Reporters in these trees must have kind
  *   OTHER, units COUNT, an amount of 1, and a description that's an empty
  *   string.
  *
- * - All other reports must have kind OTHER, and a description that is a
- *   sentence.
+ * - All other reports are unconstrained except that they must have a
+ *   description that is a sentence.
  */
 [scriptable, uuid(b2c39f65-1799-4b92-a806-ab3cf6af3cfa)]
 interface nsIMemoryReporter : nsISupports
 {
   /*
    * The name of the process containing this reporter.  Each reporter initially
    * has "" in this field, indicating that it applies to the current process.
    * (This is true even for reporters in a child process.)  When a reporter
@@ -109,16 +109,19 @@ interface nsIMemoryReporter : nsISupport
    *  - NONHEAP: reporters measuring memory which the program explicitly
    *    allocated, but does not live on the heap.  Such memory is commonly
    *    allocated by calling one of the OS's memory-mapping functions (e.g.
    *    mmap, VirtualAlloc, or vm_allocate).  Reporters in this category
    *    must have units UNITS_BYTES.
    *
    *  - OTHER: reporters which don't fit into either of these categories.
    *    They can have any units.
+   *
+   * The kind only matters for reporters in the "explicit" tree;
+   * aboutMemory.js uses it to calculate "heap-unclassified".
    */
   const PRInt32 KIND_NONHEAP = 0;
   const PRInt32 KIND_HEAP    = 1;
   const PRInt32 KIND_OTHER   = 2;
 
   /*
    * The reporter kind.  See KIND_* above.
    */