Bug 913260 (part 3) - Formalize the concept of "distinguished amounts" in the memory reporter manager. r=mccr8.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 19 Sep 2013 15:52:28 -0700
changeset 148631 fec6f224f37895f7f84a94b2a6e509ce720b40b9
parent 148630 753597994318ad97681db5b904e0556763b0dad0
child 148632 9e162ee7d4d1fa7c882292ab228b58fd7158ddf3
push id25349
push userryanvm@gmail.com
push dateWed, 25 Sep 2013 18:52:12 +0000
treeherdermozilla-central@39f30376058c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs913260
milestone27.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 913260 (part 3) - Formalize the concept of "distinguished amounts" in the memory reporter manager. r=mccr8.
js/xpconnect/src/XPCJSRuntime.cpp
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/tests/test_aboutmemory.xul
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
toolkit/components/telemetry/TelemetryPing.js
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
xpcom/base/nsMemoryReporterManager.h
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1588,57 +1588,29 @@ private:
     int64_t Amount() MOZ_OVERRIDE
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
         return int64_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) *
                js::gc::ChunkSize;
     }
 };
 
-// Nb: js-main-runtime-compartments/system + js-main-runtime-compartments/user
-// could be different to the number of compartments reported by JSReporter if a
-// garbage collection occurred between them being consulted.  We could move
-// these reporters into JSReporter to avoid that problem, but then we couldn't
-// easily report them via telemetry, so we live with the small risk of
-// inconsistencies.
-
-class RedundantJSMainRuntimeCompartmentsSystemReporter MOZ_FINAL : public MemoryUniReporter
+static int64_t
+JSMainRuntimeCompartmentsSystemDistinguishedAmount()
 {
-public:
-    // An empty description is ok because this is a "redundant/"-prefixed
-    // reporter and so is ignored by about:memory.
-    RedundantJSMainRuntimeCompartmentsSystemReporter()
-      : MemoryUniReporter("redundant/js-main-runtime-compartments/system",
-                          KIND_OTHER, UNITS_COUNT, "")
-    {}
-private:
-    int64_t Amount() MOZ_OVERRIDE
-    {
-        JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
-        return JS::SystemCompartmentCount(rt);
-    }
-};
-
-class RedundantJSMainRuntimeCompartmentsUserReporter MOZ_FINAL : public MemoryUniReporter
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
+    return JS::SystemCompartmentCount(rt);
+}
+
+static int64_t
+JSMainRuntimeCompartmentsUserDistinguishedAmount()
 {
-public:
-    // An empty description is ok because this is a "redundant/"-prefixed
-    // reporter and so is ignored by about:memory.
-    RedundantJSMainRuntimeCompartmentsUserReporter()
-      : MemoryUniReporter("redundant/js-main-runtime-compartments/user",
-                          KIND_OTHER, UNITS_COUNT,
-"The number of JavaScript compartments for user code in the main JSRuntime.")
-    {}
-private:
-    int64_t Amount() MOZ_OVERRIDE
-    {
-        JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
-        return JS::UserCompartmentCount(rt);
-    }
-};
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
+    return JS::UserCompartmentCount(rt);
+}
 
 // This is also a single reporter so it can be used by telemetry.
 class JSMainRuntimeTemporaryPeakReporter MOZ_FINAL : public MemoryUniReporter
 {
 public:
     JSMainRuntimeTemporaryPeakReporter()
       : MemoryUniReporter("js-main-runtime-temporary-peak",
                            KIND_OTHER, UNITS_BYTES,
@@ -3051,21 +3023,22 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     js::SetSourceHook(runtime, new XPCJSSourceHook);
 
     // Set up locale information and callbacks for the newly-created runtime so
     // that the various toLocaleString() methods, localeCompare(), and other
     // internationalization APIs work as desired.
     if (!xpc_LocalizeRuntime(runtime))
         NS_RUNTIMEABORT("xpc_LocalizeRuntime failed.");
 
+    // Register memory reporters and distinguished amount functions.
     NS_RegisterMemoryReporter(new JSMainRuntimeGCHeapReporter());
-    NS_RegisterMemoryReporter(new RedundantJSMainRuntimeCompartmentsSystemReporter());
-    NS_RegisterMemoryReporter(new RedundantJSMainRuntimeCompartmentsUserReporter());
     NS_RegisterMemoryReporter(new JSMainRuntimeTemporaryPeakReporter());
     NS_RegisterMemoryReporter(new JSMainRuntimeCompartmentsReporter);
+    RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount);
+    RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount);
 
     // Install a JavaScript 'debugger' keyword handler in debug builds only
 #ifdef DEBUG
     if (!JS_GetGlobalDebugHooks(runtime)->debuggerHandler)
         xpc_InstallJSDebuggerKeywordHandler(runtime);
 #endif
 }
 
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -126,66 +126,50 @@ function onUnload()
                      "child-memory-reporter-update");
 }
 
 //---------------------------------------------------------------------------
 
 /**
  * Iterates over each reporter.
  *
- * @param aIgnoreReporter
- *        Function that indicates if we should skip an entire reporter, based
- *        on its name.
- * @param aIgnoreReport
- *        Function that indicates if we should skip a single report from a
- *        reporter, based on its path.
  * @param aHandleReport
  *        The function that's called for each non-skipped report.
  */
-function processMemoryReporters(aIgnoreReporter, aIgnoreReport, aHandleReport)
+function processMemoryReporters(aHandleReport)
 {
   let handleReport = function(aProcess, aUnsafePath, aKind, aUnits,
                               aAmount, aDescription) {
-    if (!aIgnoreReport(aUnsafePath)) {
-      aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
-                    aDescription, /* presence = */ undefined);
-    }
+    aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
+                  aDescription, /* presence = */ undefined);
   }
 
   let e = gMgr.enumerateReporters();
   while (e.hasMoreElements()) {
     let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-    if (!aIgnoreReporter(mr.name)) {
-      // |collectReports| never passes in a |presence| argument.
-      mr.collectReports(handleReport, null);
-    }
+    mr.collectReports(handleReport, null);
   }
 }
 
 /**
  * Iterates over each report.
  *
  * @param aReports
  *        Array of reports, read from a file or the clipboard.
- * @param aIgnoreReport
- *        Function that indicates if we should skip a single report, based
- *        on its path.
  * @param aHandleReport
  *        The function that's called for each report.
  */
-function processMemoryReportsFromFile(aReports, aIgnoreReport, aHandleReport)
+function processMemoryReportsFromFile(aReports, aHandleReport)
 {
   // Process each memory reporter with aHandleReport.
 
   for (let i = 0; i < aReports.length; i++) {
     let r = aReports[i];
-    if (!aIgnoreReport(r.path)) {
-      aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
-                    r.description, r._presence);
-    }
+    aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
+                  r.description, r._presence);
   }
 }
 
 //---------------------------------------------------------------------------
 
 // The <div> holding everything but the header and footer (if they're present).
 // It's what is updated each time the page changes.
 let gMain;
@@ -194,17 +178,16 @@ let gMain;
 let gFooter;
 
 // The "verbose" checkbox.
 let gVerbose;
 
 // Values for the second argument to updateMainAndFooter.
 let HIDE_FOOTER = 0;
 let SHOW_FOOTER = 1;
-let IGNORE_FOOTER = 2;
 
 function updateMainAndFooter(aMsg, aFooterAction, aClassName)
 {
   // Clear gMain by replacing it with an empty node.
   let tmp = gMain.cloneNode(false);
   gMain.parentNode.replaceChild(tmp, gMain);
   gMain = tmp;
 
@@ -221,17 +204,16 @@ function updateMainAndFooter(aMsg, aFoot
       className = className + " " + aClassName;
     }
     appendElementWithText(gMain, 'div', className, aMsg);
   }
 
   switch (aFooterAction) {
    case HIDE_FOOTER:   gFooter.classList.add('hidden');    break;
    case SHOW_FOOTER:   gFooter.classList.remove('hidden'); break;
-   case IGNORE_FOOTER:                                     break;
    default: assertInput(false, "bad footer action in updateMainAndFooter");
   }
 }
 
 function appendTextNode(aP, aText)
 {
   let e = document.createTextNode(aText);
   aP.appendChild(e);
@@ -499,18 +481,18 @@ function updateAboutMemoryFromJSONObject
 {
   try {
     assertInput(aObj.version === gCurrentFileFormatVersion,
                 "data version number missing or doesn't match");
     assertInput(aObj.hasMozMallocUsableSize !== undefined,
                 "missing 'hasMozMallocUsableSize' property");
     assertInput(aObj.reports && aObj.reports instanceof Array,
                 "missing or non-array 'reports' property");
-    let process = function(aIgnoreReporter, aIgnoreReport, aHandleReport) {
-      processMemoryReportsFromFile(aObj.reports, aIgnoreReport, aHandleReport);
+    let process = function(aHandleReport) {
+      processMemoryReportsFromFile(aObj.reports, aHandleReport);
     }
     appendAboutMemoryMain(process, aObj.hasMozMallocUsableSize);
   } catch (ex) {
     handleException(ex);
   }
 }
 
 /**
@@ -932,29 +914,16 @@ function getPCollsByProcess(aProcessRepo
 {
   let pcollsByProcess = {};
 
   // This regexp matches sentences and sentence fragments, i.e. strings that
   // start with a capital letter and ends with a '.'.  (The final sentence may
   // be in parentheses, so a ')' might appear after the '.'.)
   const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
 
-  // Ignore any "redundant/"-prefixed reporters and reports, which are only
-  // used by telemetry.
-
-  function ignoreReporter(aName)
-  {
-    return aName.startsWith("redundant/");
-  }
-
-  function ignoreReport(aUnsafePath)
-  {
-    return aUnsafePath.startsWith("redundant/");
-  }
-
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aDescription, aPresence)
   {
     if (aUnsafePath.startsWith("explicit/")) {
       assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP,
                   "bad explicit kind");
       assertInput(aUnits === UNITS_BYTES, "bad explicit units");
       assertInput(gSentenceRegExp.test(aDescription),
@@ -983,17 +952,18 @@ function getPCollsByProcess(aProcessRepo
         assertInput(gSentenceRegExp.test(aDescription),
                     "non-sentence other description: " + aUnsafePath + ", " +
                     aDescription);
       }
     }
 
     assert(aPresence === undefined ||
            aPresence == DReport.PRESENT_IN_FIRST_ONLY ||
-           aPresence == DReport.PRESENT_IN_SECOND_ONLY);
+           aPresence == DReport.PRESENT_IN_SECOND_ONLY,
+           "bad presence");
 
     let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
     let unsafeNames = aUnsafePath.split('/');
     let unsafeName0 = unsafeNames[0];
     let isDegenerate = unsafeNames.length === 1;
 
     // Get the PColl table for the process, creating it if necessary.
     let pcoll = pcollsByProcess[process];
@@ -1041,17 +1011,17 @@ function getPCollsByProcess(aProcessRepo
       t._amount = aAmount;
       t._description = aDescription;
       if (aPresence !== undefined) {
         t._presence = aPresence;
       }
     }
   }
 
-  aProcessReports(ignoreReporter, ignoreReport, handleReport);
+  aProcessReports(handleReport);
 
   return pcollsByProcess;
 }
 
 //---------------------------------------------------------------------------
 
 // There are two kinds of TreeNode.
 // - Leaf TreeNodes correspond to reports.
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
@@ -145,41 +145,32 @@
     is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception");
   }
  
   // The main process always comes first when we display about:memory.  The
   // remaining processes are sorted by their |resident| values (starting with
   // the largest).  Processes without a |resident| memory reporter are saved
   // for the end.
   let fakeReporters2 = [
-    { name: "redundant/foobar",
-      collectReports: function(aCbObj, aClosure) {
-        // Shouldn't reach here;  aboutMemory.js should skip the reporter.
-        // (Nb: this must come after |mgr.explicit| is accessed, otherwise it
-        // *will* be run by nsMemoryReporterManager::GetExplicit()).
-        ok(false, "'redundant/foobar' reporter was run");
-      }
-    },
     { name: "fake3",
       collectReports: function(aCbObj, aClosure) {
         function f(aP1, aP2, aK, aU, aA) {
           aCbObj.callback(aP1, aP2, aK, aU, aA, "Desc.", aClosure);
         }
         f("2nd", "heap-allocated",  OTHER,   BYTES,1000* MB);
         f("2nd", "heap-unallocated",OTHER,   BYTES,100 * MB);
         f("2nd", "explicit/a/b/c",  HEAP,    BYTES,497 * MB);
         f("2nd", "explicit/a/b/c",  HEAP,    BYTES,  1 * MB); // dup: merge
         f("2nd", "explicit/a/b/c",  HEAP,    BYTES,  1 * MB); // dup: merge
         f("2nd", "explicit/flip\\the\\backslashes",
                                     HEAP,    BYTES,200 * MB);
         f("2nd", "explicit/compartment(compartment-url)",
                                     HEAP,    BYTES,200 * MB);
         f("2nd", "other0",          OTHER,   BYTES,666 * MB);
         f("2nd", "other1",          OTHER,   BYTES,111 * MB);
-        f("2nd", "redundant/blah",  NONHEAP, BYTES,24*4*KB); // ignored!
 
         // Check that we can handle "heap-allocated" not being present.
         f("3rd", "explicit/a/b",    HEAP,    BYTES,333 * MB);
         f("3rd", "explicit/a/c",    HEAP,    BYTES,444 * MB);
         f("3rd", "other1",          OTHER,   BYTES,  1 * MB);
         f("3rd", "resident",        OTHER,   BYTES,100 * MB);
 
         // Invalid values (negative, too-big) should be identified.
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -40,26 +40,17 @@
   const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
 
   let vsizeAmounts = [];
   let residentAmounts = [];
   let jsGcHeapAmounts = [];
   let heapAllocatedAmounts = [];
   let storageSqliteAmounts = [];
 
-  let areJsNonWindowCompartmentsPresent = false;
-  let areWindowObjectsJsCompartmentsPresent = false;
-  let isSandboxLocationShown = false;
-  let isPlacesPresent = false;
-  let isImagesPresent = false;
-  let isXptiWorkingSetPresent = false;
-  let isAtomTablePresent = false;
-  let isBigStringPresent = false;
-  let isSmallString1Present = false;
-  let isSmallString2Present = false;
+  let present = {}
 
   // Generate a long, random string.  We'll check that this string is
   // reported in at least one of the memory reporters.
   let bigString = "";
   while (bigString.length < 10000) {
     bigString += Math.random();
   }
   let bigStringPrefix = bigString.substring(0, 100);
@@ -87,58 +78,62 @@
       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-non-window\/.*compartment\(/) >= 0) {
-      areJsNonWindowCompartmentsPresent = true;
+      present.jsNonWindowCompartments = true;
     } else if (aPath.search(/^explicit\/window-objects\/top\(.*\/js-compartment\(/) >= 0) {
-      areWindowObjectsJsCompartmentsPresent = true;
+      present.windowObjectsJsCompartments = true;
     } else if (aPath.search(/^explicit\/storage\/sqlite\/places.sqlite/) >= 0) {
-      isPlacesPresent = true;
+      present.places = true;
     } else if (aPath.search(/^explicit\/images/) >= 0) {
-      isImagesPresent = true;
+      present.images = true;
     } else if (aPath.search(/^explicit\/xpti-working-set$/) >= 0) {
-      isXptiWorkingSetPresent = true;
+      present.xptiWorkingSet = true;
     } else if (aPath.search(/^explicit\/atom-tables$/) >= 0) {
-      isAtomTablePresent = true;
+      present.atomTable = true;
     } else if (/\[System Principal\].*this-is-a-sandbox-name/.test(aPath)) {
       // A system compartment with a location (such as a sandbox) should
       // show that location.
-      isSandboxLocationShown = true;
+      present.sandboxLocation = true;
     } else if (aPath.contains(bigStringPrefix)) {
-      isBigStringPresent = true;
+      present.bigString = true;
     } else if (aPath.contains("!)(*&")) {
-      isSmallString1Present = true;
+      present.smallString1 = true;
     } else if (aPath.contains("@)(*&")) {
-      isSmallString2Present = true;
+      present.smallString2 = true;
     }
   }
 
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
-  // Access mgr.explicit and mgr.resident just to make sure they don't crash.
-  // We can't check their actual values because they're non-deterministic.
+  // Access the distinguished amounts (mgr.explicit et al.) just to make sure
+  // they don't crash.  We can't check their actual values because they're
+  // non-deterministic.
   //
   // Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a
   // --enable-trace-malloc build.  Allow for that exception, but *only* that
   // exception.
   let dummy;
   let haveExplicit = true;
   try {
     dummy = mgr.explicit;
   } catch (ex) {
     is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception");
     haveExplicit = false;
   }
   dummy = mgr.resident;
+  dummy = mgr.residentFast;
+  dummy = mgr.JSMainRuntimeCompartmentsSystem;
+  dummy = mgr.JSMainRuntimeCompartmentsUser;
 
   let e = mgr.enumerateReporters();
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     r.collectReports(handleReport, null);
 
     // Access |name| to make sure it doesn't crash or assert.
     dummy = r.name;
@@ -157,23 +152,23 @@
   if (haveExplicit) {
     checkSpecialReport("heap-allocated", heapAllocatedAmounts);
   }
   checkSpecialReport("vsize",          vsizeAmounts);
   checkSpecialReport("resident",       residentAmounts);
   checkSpecialReport("js-main-runtime-gc-heap-committed/used/gc-things", jsGcHeapAmounts);
   checkSpecialReport("storage-sqlite", storageSqliteAmounts);
 
-  ok(areJsNonWindowCompartmentsPresent,     "js-non-window compartments are present");
-  ok(areWindowObjectsJsCompartmentsPresent, "window-objects/.../js compartments are present");
-  ok(isSandboxLocationShown,                "sandbox locations are present");
-  ok(isPlacesPresent,                       "places is present");
-  ok(isImagesPresent,                       "images is present");
-  ok(isXptiWorkingSetPresent,               "xpti-working-set is present");
-  ok(isAtomTablePresent,                    "atom-table is present");
-  ok(isBigStringPresent,                    "large string is present");
-  ok(isSmallString1Present,                 "small string 1 is present");
-  ok(isSmallString2Present,                 "small string 2 is present");
+  ok(present.jsNonWindowCompartments,     "js-non-window compartments are present");
+  ok(present.windowObjectsJsCompartments, "window-objects/.../js compartments are present");
+  ok(present.places,                      "places is present");
+  ok(present.images,                      "images is present");
+  ok(present.xptiWorkingSet,              "xpti-working-set is present");
+  ok(present.atomTable,                   "atom-table is present");
+  ok(present.sandboxLocation,             "sandbox locations are present");
+  ok(present.bigString,                   "large string is present");
+  ok(present.smallString1,                "small string 1 is present");
+  ok(present.smallString2,                "small string 2 is present");
 
   ]]>
   </script>
 </window>
 
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ b/toolkit/components/telemetry/TelemetryPing.js
@@ -36,34 +36,28 @@ const PREF_PREVIOUS_BUILDID = PREF_BRANC
 
 // Do not gather data more than once a minute
 const TELEMETRY_INTERVAL = 60000;
 // Delay before intializing telemetry (ms)
 const TELEMETRY_DELAY = 60000;
 
 // MEM_HISTOGRAMS lists the memory reporters we turn into histograms.
 //
-// Note that we currently handle only vanilla memory reporters, not memory
-// multi-reporters.
-//
 // test_TelemetryPing.js relies on some of these memory reporters
 // being here.  If you remove any of the following histograms from
 // MEM_HISTOGRAMS, you'll have to modify test_TelemetryPing.js:
 //
 //   * MEMORY_JS_GC_HEAP, and
 //   * MEMORY_JS_COMPARTMENTS_SYSTEM.
 //
 // We used to measure "explicit" too, but it could cause hangs, and the data
 // was always really noisy anyway.  See bug 859657.
 const MEM_HISTOGRAMS = {
   "js-main-runtime-gc-heap": "MEMORY_JS_GC_HEAP",
-  "redundant/js-main-runtime-compartments/system": "MEMORY_JS_COMPARTMENTS_SYSTEM",
-  "redundant/js-main-runtime-compartments/user": "MEMORY_JS_COMPARTMENTS_USER",
   "js-main-runtime-temporary-peak": "MEMORY_JS_MAIN_RUNTIME_TEMPORARY_PEAK",
-  "redundant/resident-fast": "MEMORY_RESIDENT",
   "vsize": "MEMORY_VSIZE",
   "storage-sqlite": "MEMORY_STORAGE_SQLITE",
   "images-content-used-uncompressed":
     "MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED",
   "heap-allocated": "MEMORY_HEAP_ALLOCATED",
   "heap-overhead": "MEMORY_HEAP_COMMITTED_UNUSED",
   "heap-overhead-ratio": "MEMORY_HEAP_COMMITTED_UNUSED_RATIO",
   "page-faults-hard": "PAGE_FAULTS_HARD",
@@ -442,16 +436,18 @@ TelemetryPing.prototype = {
             getService(Ci.nsIMemoryReporterManager);
     } catch (e) {
       // OK to skip memory reporters in xpcshell
       return;
     }
 
     let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS");
     let startTime = new Date();
+
+    // Get memory measurements from reporters.
     let e = mgr.enumerateReporters();
     while (e.hasMoreElements()) {
       let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
       let id = MEM_HISTOGRAMS[mr.name];
       if (!id) {
         continue;
       }
 
@@ -475,16 +471,27 @@ TelemetryPing.prototype = {
                       "reporter " + mr.name + " has made more than one report");
           }
         }
         mr.collectReports(h, null);
       }
       catch (e) {
       }
     }
+
+    // Get memory measurements from distinguished amount attributes.
+    let h = this.handleMemoryReport.bind(this);
+    let b = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_BYTES, n);
+    let c = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT, n);
+    let p = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_PERCENTAGE, n);
+
+    try { b("MEMORY_RESIDENT", mgr.residentFast); } catch (e) {}
+    try { c("MEMORY_JS_COMPARTMENTS_SYSTEM", mgr.JSMainRuntimeCompartmentsSystem); } catch (e) {}
+    try { c("MEMORY_JS_COMPARTMENTS_USER", mgr.JSMainRuntimeCompartmentsUser); } catch (e) {}
+
     histogram.add(new Date() - startTime);
   },
 
   handleMemoryReport: function(id, units, amount) {
     let val;
     if (units == Ci.nsIMemoryReporter.UNITS_BYTES) {
       val = Math.floor(amount / 1024);
     }
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -138,21 +138,16 @@ interface nsIMemoryReporterCallback : ns
  * - There must be an "explicit" tree.  It represents non-overlapping
  *   regions of memory that have been explicitly allocated with an
  *   OS-level allocation (e.g. mmap/VirtualAlloc/vm_allocate) or a
  *   heap-level allocation (e.g. malloc/calloc/operator new).  Reporters
  *   in this tree must have kind HEAP or NONHEAP, units BYTES, and a
  *   description that is a sentence (i.e. starts with a capital letter and
  *   ends with a period, or similar).
  *
- * - The "redundant" tree is optional, and can be used for reports that are
- *   redundant w.r.t. other reports.  These are useful for telemetry, and are
- *   not shown in about:memory.  Reports in this tree are entirely
- *   unconstrained.
- *
  * - All other reports are unconstrained except that they must have a
  *   description that is a sentence.
  */
 [scriptable, uuid(53248304-124b-43cd-99dc-6e5797b91618)]
 interface nsIMemoryReporter : nsISupports
 {
   /*
    * The name of the reporter.  Useful when only one reporter needs to be run.
@@ -184,58 +179,73 @@ interface nsIMemoryReporter : nsISupport
 };
 
 [scriptable, builtinclass, uuid(4db7040a-16f9-4249-879b-fe72729c7ef5)]
 interface nsIMemoryReporterManager : nsISupports
 {
   /*
    * Return an enumerator of nsIMemoryReporters that are currently registered.
    */
-  nsISimpleEnumerator enumerateReporters ();
+  nsISimpleEnumerator enumerateReporters();
 
   /*
    * Register the given nsIMemoryReporter.  After a reporter is registered,
    * it will be available via enumerateReporters().  The Manager service
    * will hold a strong reference to the given reporter.
    */
-  void registerReporter (in nsIMemoryReporter reporter);
+  void registerReporter(in nsIMemoryReporter reporter);
 
   /*
    * Unregister the given memory reporter.
    */
-  void unregisterReporter (in nsIMemoryReporter reporter);
+  void unregisterReporter(in nsIMemoryReporter reporter);
 
-  /**
+  /*
    * These functions should only be used for testing purposes.
    */
   void blockRegistration();
   void unblockRegistration();
   void registerReporterEvenIfBlocked(in nsIMemoryReporter aReporter);
 
   /*
    * Initialize.
    */
-  void init ();
+  void init();
 
   /*
-   * Get the resident size (aka. RSS, physical memory used).  This reporter
-   * is special-cased because it's interesting and is available on most
-   * platforms.  Accesses can fail.
-   */
-  readonly attribute int64_t resident;
-
-  /*
-   * Get the total size of explicit memory allocations, both at the OS-level
-   * (eg. via mmap, VirtualAlloc) and at the heap level (eg. via malloc,
-   * calloc, operator new).  (Nb: it covers all heap allocations, but will
-   * miss any OS-level ones not covered by memory reporters.)  This reporter
-   * is special-cased because it's interesting, and is difficult to compute
-   * from JavaScript code.  Accesses can fail.
+   * The memory reporter manager, for the most part, treats reporters
+   * registered with it as a black box.  However, there are some
+   * "distinguished" amounts (as could be reported by a memory reporter) that
+   * the manager provides as attributes, because they are sufficiently
+   * interesting that we want external code to be able to rely on them.
+   *
+   * Note that these are not reporters and so enumerateReporters() does not
+   * look at them.  However, they can be embedded in a reporter.
+   *
+   * |explicit| (UNIT_BYTES)  The total size of explicit memory allocations,
+   * both at the OS-level (eg. via mmap, VirtualAlloc) and at the heap level
+   * (eg. via malloc, calloc, operator new).  It covers all heap allocations,
+   * but will miss any OS-level ones not covered by memory reporters.
+   *
+   * |resident| (UNIT_BYTES)  The resident size (a.k.a. RSS or physical memory
+   * used).
+   *
+   * |residentFast| (UNIT_BYTES)  This is like |resident|, but on Mac OS
+   * |resident| can purge pages, which is slow.  It also affects the result of
+   * |residentFast|, and so |resident| and |residentFast| should not be used
+   * together.  It is used by telemetry.
+   *
+   * |JSMainRuntimeCompartments{System,User}| (UNIT_COUNTS)  The number of
+   * {system,user} compartments in the main JS runtime.
    */
   readonly attribute int64_t explicit;
+  readonly attribute int64_t resident;
+  readonly attribute int64_t residentFast;
+  readonly attribute int64_t JSMainRuntimeCompartmentsSystem;
+  readonly attribute int64_t JSMainRuntimeCompartmentsUser;
 
   /*
    * This attribute indicates if moz_malloc_usable_size() works.
    */
   [infallible] readonly attribute boolean hasMozMallocUsableSize;
 
   /*
    * Run a series of GC/CC's in an attempt to minimize the application's memory
@@ -251,16 +261,37 @@ interface nsIMemoryReporterManager : nsI
 
 // Note that the memory reporters are held in an nsCOMArray, which means
 // that individual reporters should be referenced with |nsIMemoryReporter *|
 // instead of nsCOMPtr<nsIMemoryReporter>.
 
 XPCOM_API(nsresult) NS_RegisterMemoryReporter(nsIMemoryReporter* aReporter);
 XPCOM_API(nsresult) NS_UnregisterMemoryReporter(nsIMemoryReporter* aReporter);
 
+namespace mozilla {
+
+// The memory reporter manager provides access to several distinguished
+// amounts via attributes.  Some of these amounts are provided by Gecko
+// components that cannot be accessed directly from XPCOM code.  So we provide
+// the following functions for those components to be registered with the
+// manager.
+
+typedef int64_t (*InfallibleAmountFn)();
+typedef nsresult (*FallibleAmountFn)(int64_t* aAmount);
+
+#define REGISTER_DISTINGUISHED_AMOUNT(kind, name) \
+    nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn);
+
+REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem)
+REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser)
+
+#undef REGISTER_DISTINGUISHED_AMOUNT
+
+}
+
 #if defined(MOZ_DMD)
 namespace mozilla {
 namespace dmd {
 // This runs all the memory reporters but does nothing with the results;  i.e.
 // it does the minimal amount of work possible for DMD to do its thing.
 void RunReporters();
 }
 }
@@ -388,10 +419,9 @@ protected:
   const nsCString mNameAndPath;
   const int32_t   mKind;
   const int32_t   mUnits;
   const nsCString mDescription;
 };
 
 } // namespace mozilla
 
-
 %}
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -11,19 +11,20 @@
 #include "nsServiceManagerUtils.h"
 #include "nsMemoryReporterManager.h"
 #include "nsISimpleEnumerator.h"
 #include "nsThreadUtils.h"
 #include "nsIObserverService.h"
 #if defined(XP_LINUX)
 #include "nsMemoryInfoDumper.h"
 #endif
+#include "mozilla/Attributes.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/Services.h"
 
 #ifndef XP_WIN
 #include <unistd.h>
 #endif
 
 using namespace mozilla;
 
 #if defined(MOZ_MEMORY)
@@ -421,36 +422,16 @@ public:
 "considering the memory resources used by the process, but it depends both on "
 "other processes being run and details of the OS kernel and so is best used "
 "for comparing the memory usage of a single process at different points in "
 "time.")
     {}
 
     NS_IMETHOD GetAmount(int64_t* aAmount) { return GetResident(aAmount); }
 };
-
-// This is a "redundant/"-prefixed reporter, which means it's ignored by
-// about:memory.  This is good because the "resident" reporter can purge pages
-// on MacOS, which affects the "resident-fast" results, and we don't want the
-// measurements shown in about:memory to be affected by the (arbitrary) order
-// of memory reporter execution.  This reporter is used by telemetry.
-class ResidentFastReporter MOZ_FINAL : public MemoryUniReporter
-{
-public:
-    ResidentFastReporter()
-      : MemoryUniReporter("redundant/resident-fast", KIND_OTHER, UNITS_BYTES,
-"This is the same measurement as 'resident', but it tries to be as fast as "
-"possible at the expense of accuracy.  On most platforms this is identical to "
-"the 'resident' measurement, but on Mac it may over-count.  You should use "
-"'resident-fast' where you care about latency of collection (e.g. in "
-"telemetry).  Otherwise you should use 'resident'.")
-    {}
-
-    NS_IMETHOD GetAmount(int64_t* aAmount) { return GetResidentFast(aAmount); }
-};
 #endif  // HAVE_VSIZE_AND_RESIDENT_REPORTERS
 
 #ifdef XP_UNIX
 
 #include <sys/resource.h>
 
 #define HAVE_PAGE_FAULT_REPORTERS 1
 
@@ -747,17 +728,16 @@ nsMemoryReporterManager::Init()
     RegisterReporter(new HeapOverheadPageCacheReporter);
     RegisterReporter(new HeapCommittedReporter);
     RegisterReporter(new HeapOverheadRatioReporter);
 #endif
 
 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
     RegisterReporter(new VsizeReporter);
     RegisterReporter(new ResidentReporter);
-    RegisterReporter(new ResidentFastReporter);
 #endif
 
 #ifdef HAVE_RESIDENT_UNIQUE_REPORTER
     RegisterReporter(new ResidentUniqueReporter);
 #endif
 
 #ifdef HAVE_PAGE_FAULT_REPORTERS
     RegisterReporter(new PageFaultsSoftReporter);
@@ -843,16 +823,17 @@ HashtableEnumerator::GetNext(nsISupports
 }
 
 } // anonymous namespace
 
 nsMemoryReporterManager::nsMemoryReporterManager()
   : mMutex("nsMemoryReporterManager::mMutex"),
     mIsRegistrationBlocked(false)
 {
+    PodZero(&mAmountFns);
 }
 
 nsMemoryReporterManager::~nsMemoryReporterManager()
 {
 }
 
 NS_IMETHODIMP
 nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator** aResult)
@@ -959,27 +940,16 @@ nsMemoryReporterManager::UnblockRegistra
     mozilla::MutexAutoLock autoLock(mMutex);
     if (!mIsRegistrationBlocked) {
         return NS_ERROR_FAILURE;
     }
     mIsRegistrationBlocked = false;
     return NS_OK;
 }
 
-NS_IMETHODIMP
-nsMemoryReporterManager::GetResident(int64_t* aResident)
-{
-#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
-    return ::GetResident(aResident);
-#else
-    *aResident = 0;
-    return NS_ERROR_NOT_AVAILABLE;
-#endif
-}
-
 // This is just a wrapper for int64_t that implements nsISupports, so it can be
 // passed to nsIMemoryReporter::CollectReports.
 class Int64Wrapper MOZ_FINAL : public nsISupports {
 public:
     NS_DECL_ISUPPORTS
     Int64Wrapper() : mValue(0) { }
     int64_t mValue;
 };
@@ -1004,20 +974,20 @@ public:
             wrappedInt64->mValue += aAmount;
         }
         return NS_OK;
     }
 };
 NS_IMPL_ISUPPORTS1(ExplicitCallback, nsIMemoryReporterCallback)
 
 NS_IMETHODIMP
-nsMemoryReporterManager::GetExplicit(int64_t* aExplicit)
+nsMemoryReporterManager::GetExplicit(int64_t* aAmount)
 {
-    NS_ENSURE_ARG_POINTER(aExplicit);
-    *aExplicit = 0;
+    NS_ENSURE_ARG_POINTER(aAmount);
+    *aAmount = 0;
 #ifndef HAVE_JEMALLOC_STATS
     return NS_ERROR_NOT_AVAILABLE;
 #else
     bool more;
 
     // For each reporter we call CollectReports and filter out the
     // non-explicit, non-NONHEAP measurements (except for "heap-allocated").
     // That's lots of wasted work, and we used to have a GetExplicitNonHeap()
@@ -1030,23 +1000,70 @@ nsMemoryReporterManager::GetExplicit(int
     nsCOMPtr<nsISimpleEnumerator> e;
     EnumerateReporters(getter_AddRefs(e));
     while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsIMemoryReporter> r;
         e->GetNext(getter_AddRefs(r));
         r->CollectReports(cb, wrappedExplicitSize);
     }
 
-    *aExplicit = wrappedExplicitSize->mValue;
+    *aAmount = wrappedExplicitSize->mValue;
 
     return NS_OK;
 #endif // HAVE_JEMALLOC_STATS
 }
 
 NS_IMETHODIMP
+nsMemoryReporterManager::GetResident(int64_t* aAmount)
+{
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+    return ::GetResident(aAmount);
+#else
+    *aAmount = 0;
+    return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetResidentFast(int64_t* aAmount)
+{
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+    return ::GetResidentFast(aAmount);
+#else
+    *aAmount = 0;
+    return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+static nsresult
+GetInfallibleAmount(InfallibleAmountFn aAmountFn, int64_t* aAmount)
+{
+    if (aAmountFn) {
+        *aAmount = aAmountFn();
+        return NS_OK;
+    }
+    *aAmount = 0;
+    return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount)
+{
+    return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem,
+                               aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount)
+{
+    return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser,
+                               aAmount);
+}
+
+NS_IMETHODIMP
 nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas)
 {
     void* p = malloc(16);
     if (!p) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
     size_t usable = moz_malloc_usable_size(p);
     free(p);
@@ -1158,16 +1175,42 @@ NS_UnregisterMemoryReporter(nsIMemoryRep
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
     if (!mgr) {
         return NS_ERROR_FAILURE;
     }
     return mgr->UnregisterReporter(aReporter);
 }
 
+namespace mozilla {
+
+// Macro for generating functions that register distinguished amount functions
+// with the memory reporter manager.
+#define REGISTER_DISTINGUISHED_AMOUNT(kind, name)                             \
+    nsresult                                                                  \
+    Register##name##DistinguishedAmount(kind##AmountFn aAmountFn)             \
+    {                                                                         \
+        nsCOMPtr<nsIMemoryReporterManager> imgr =                             \
+            do_GetService("@mozilla.org/memory-reporter-manager;1");          \
+        nsRefPtr<nsMemoryReporterManager> mgr =                               \
+            static_cast<nsMemoryReporterManager*>(imgr.get());                \
+        if (!mgr) {                                                           \
+            return NS_ERROR_FAILURE;                                          \
+        }                                                                     \
+        mgr->mAmountFns.m##name = aAmountFn;                                  \
+        return NS_OK;                                                         \
+    }
+
+REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem)
+REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser)
+
+#undef REGISTER_DISTINGUISHED_AMOUNT
+
+}
+
 #if defined(MOZ_DMD)
 
 namespace mozilla {
 namespace dmd {
 
 class NullReporterCallback : public nsIMemoryReporterCallback
 {
 public:
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -15,18 +15,26 @@ class nsMemoryReporterManager : public n
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTERMANAGER
 
   nsMemoryReporterManager();
   virtual ~nsMemoryReporterManager();
 
+  // Functions that (a) implement distinguished amounts, and (b) are outside of
+  // this module.
+  struct AmountFns {
+    mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem;
+    mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsUser;
+  };
+  AmountFns mAmountFns;
+
 private:
-  nsresult RegisterReporterHelper(nsIMemoryReporter *reporter, bool aForce);
+  nsresult RegisterReporterHelper(nsIMemoryReporter *aReporter, bool aForce);
 
   nsTHashtable<nsISupportsHashKey> mReporters;
   Mutex mMutex;
   bool mIsRegistrationBlocked;
 };
 
 #define NS_MEMORY_REPORTER_MANAGER_CID \
 { 0xfb97e4f5, 0x32dd, 0x497a, \