Bug 674290 - Expose contents of /proc/self/maps and smaps in about:memory. r=njn
authorJustin Lebar <justin.lebar@gmail.com>
Fri, 05 Aug 2011 18:22:11 -0400
changeset 76668 45b321b68c2f3196785f9c61b1d216501a6fd35b
parent 76667 16813bde78b9fc6c200605d543d4b1fd3cd35bf1
child 76669 b221c62a19c353c8c50cceed82da2dab0982a379
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersnjn
bugs674290
milestone9.0a1
Bug 674290 - Expose contents of /proc/self/maps and smaps in about:memory. r=njn
toolkit/components/aboutmemory/content/aboutMemory.css
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
xpcom/base/Makefile.in
xpcom/base/MapsMemoryReporter.cpp
xpcom/base/MapsMemoryReporter.h
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
xpcom/build/Makefile.in
xpcom/build/nsXPComInit.cpp
--- a/toolkit/components/aboutmemory/content/aboutMemory.css
+++ b/toolkit/components/aboutmemory/content/aboutMemory.css
@@ -64,8 +64,20 @@
   -moz-user-select: none;  /* no need to include this when cutting+pasting */
 }
 
 .legend {
   font-size: 80%;
   -moz-user-select: none;  /* no need to include this when cutting+pasting */
 }
 
+h2.tree {
+  cursor: pointer;
+  background: #ddd;
+  padding-left: .1em;
+}
+
+pre.tree {
+}
+
+pre.collapsed {
+  display: none;
+}
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -53,16 +53,65 @@ const KIND_HEAP    = Ci.nsIMemoryReporte
 const KIND_OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 const UNITS_BYTES  = Ci.nsIMemoryReporter.UNITS_BYTES;
 const UNITS_COUNT  = Ci.nsIMemoryReporter.UNITS_COUNT;
 const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
 const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
 
 const kUnknown = -1;    // used for _amount if a memory reporter failed
 
+const kTreeDescriptions = {
+  'explicit' :
+    "This tree covers explicit memory allocations by the application, " +
+    "both at the operating system level (via calls to functions such as " +
+    "VirtualAlloc, vm_allocate, and mmap), and at the heap allocation level " +
+    "(via functions such as malloc, calloc, realloc, memalign, operator " +
+    "new, and operator new[]).  It excludes memory that is mapped implicitly " +
+    "such as code and data segments, and thread stacks.  It also excludes " +
+    "heap memory that has been freed by the application but is still being " +
+    "held onto by the heap allocator.  It is not guaranteed to cover every " +
+    "explicit allocation, but it does cover most (including the entire " +
+    "heap), and therefore it is the single best number to focus on when " +
+    "trying to reduce memory usage.",
+
+  'resident':
+    "This tree shows how much space in physical memory each of the " +
+    "process's mappings is currently using (the mapping's 'resident set size', " +
+    "or 'RSS'). This is a good measure of the 'cost' of the mapping, although " +
+    "it does not take into account the fact that shared libraries may be mapped " +
+    "by multiple processes but appear only once in physical memory. " +
+    "Note that the 'resident' value here might not equal the value for " +
+    "'resident' under 'Other Measurements' because the two measurements are not " +
+    "taken at exactly the same time.",
+
+  'vsize':
+    "This tree shows how much virtual addres space each of the process's " +
+    "mappings takes up (the mapping's 'vsize').  A mapping may have a large " +
+    "vsize but use only a small amount of physical memory; the resident set size " +
+    "of a mapping is a better measure of the mapping's 'cost'. Note that the " +
+    "'vsize' value here might not equal the value for 'vsize' under 'Other " +
+    "Measurements' because the two measurements are not taken at exactly the " +
+    "same time.",
+
+  'swap':
+    "This tree shows how much space in the swap file each of the process's " +
+    "mappings is currently using. Mappings which are not in the swap file " +
+    "(i.e., nodes which would have a value of 0 in this tree) are omitted."
+};
+
+const kTreeNames = {
+  'explicit': 'Explicit Allocations',
+  'resident': 'Resident Set Size (RSS) Breakdown',
+  'vsize':    'Virtual Size Breakdown',
+  'swap':     'Swap Usage Breakdown',
+  'other':    'Other Measurements'
+};
+
+const kMapTreePaths = ['map/resident', 'map/vsize', 'map/swap'];
+
 function onLoad()
 {
   var os = Cc["@mozilla.org/observer-service;1"].
       getService(Ci.nsIObserverService);
   os.notifyObservers(null, "child-memory-reporter-request", null);
 
   os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
   gAddedObserver = true;
@@ -130,16 +179,27 @@ function sendHeapMinNotifications()
     else
       runSoon(update);
   }
 
   var j = 0;
   sendHeapMinNotificationsInner();
 }
 
+function toggleTreeVisibility(aEvent)
+{
+  var headerElem = aEvent.target;
+
+  // Replace "header-" with "pre-" in the header element's id to get the id of
+  // the corresponding pre element.
+  var treeElem = $(headerElem.id.replace(/^header-/, 'pre-'));
+
+  treeElem.classList.toggle('collapsed');
+}
+
 function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
 {
   this._path        = aPath;
   this._kind        = aKind;
   this._units       = aUnits;
   this._amount      = aAmount;
   this._description = aDescription;
   // this._nMerged is only defined if > 1
@@ -256,17 +316,18 @@ function update()
   text += "<div>";
   text += gVerbose
         ? "<span class='option'><a href='about:memory'>Less verbose</a></span>"
         : "<span class='option'><a href='about:memory?verbose'>More verbose</a></span>";
   text += "</div>";
 
   text += "<div>" +
           "<span class='legend'>Hover the pointer over the name of a memory " +
-          "reporter to see a detailed description of what it measures.</span>"
+          "reporter to see a detailed description of what it measures. Click a " +
+          "heading to expand or collapse its tree.</span>" +
           "</div>";
 
   var div = document.createElement("div");
   div.innerHTML = text;
   content.appendChild(div);
 }
 
 // There are two kinds of TreeNode.  Those that correspond to Reporters
@@ -307,33 +368,51 @@ TreeNode.compare = function(a, b) {
 };
 
 /**
  * From a list of memory reporters, builds a tree that mirrors the tree
  * structure that will be shown as output.
  *
  * @param aReporters
  *        The table of Reporters, indexed by path.
+ * @param aTreeName
+ *        The name of the tree being built.
  * @return The built tree.
  */
-function buildTree(aReporters)
+function buildTree(aReporters, aTreeName)
 {
-  const treeName = "explicit";
-
-  // We want to process all reporters that begin with |treeName|.  First we
+  // We want to process all reporters that begin with |aTreeName|.  First we
   // build the tree but only fill the properties that we can with a top-down
   // traversal.
+
+  // Is there any reporter which matches aTreeName?  If not, we'll create a
+  // dummy one.
+  var foundReporter = false;
+  for (var path in aReporters) {
+    if (aReporters[path].treeNameMatches(aTreeName)) {
+      foundReporter = true;
+      break;
+    }
+  }
+
+  if (!foundReporter) {
+    // We didn't find any reporters for this tree, so create an empty one.  Its
+    // description will be set later.
+    aReporters[aTreeName] =
+      new Reporter(aTreeName, KIND_NONHEAP, UNITS_BYTES, 0, '');
+  }
+
   var t = new TreeNode("falseRoot");
   for (var path in aReporters) {
     // Add any missing nodes in the tree implied by the path.
     var r = aReporters[path];
-    if (r.treeNameMatches(treeName)) {
+    if (r.treeNameMatches(aTreeName)) {
       assert(r._kind === KIND_HEAP || r._kind === KIND_NONHEAP,
              "reporters in the tree must have KIND_HEAP or KIND_NONHEAP");
-      assert(r._units === UNITS_BYTES);
+      assert(r._units === UNITS_BYTES, "r._units === UNITS_BYTES");
       var names = r._path.split('/');
       var u = t;
       for (var i = 0; i < names.length; i++) {
         var name = names[i];
         var uMatch = u.findKid(name);
         if (uMatch) {
           u = uMatch;
         } else {
@@ -344,28 +423,29 @@ function buildTree(aReporters)
       }
       // Fill in extra details from the Reporter.
       u._kind = r._kind;
       if (r._nMerged) {
         u._nMerged = r._nMerged;
       }
     }
   }
+
   // Using falseRoot makes the above code simpler.  Now discard it, leaving
-  // treeName at the root.
+  // aTreeName at the root.
   t = t._kids[0];
 
   // Next, fill in the remaining properties bottom-up.
   // Note that this function never returns kUnknown.
   function fillInTree(aT, aPrepath)
   {
     var path = aPrepath ? aPrepath + '/' + aT._name : aT._name;
     if (aT._kids.length === 0) {
       // Leaf node.  Must have a reporter.
-      assert(aT._kind !== undefined);
+      assert(aT._kind !== undefined, "aT._kind !== undefined");
       aT._description = getDescription(aReporters, path);
       var amount = getBytes(aReporters, path);
       if (amount !== kUnknown) {
         aT._amount = amount;
       } else {
         aT._amount = 0;
         aT._hasProblem = true;
       }
@@ -395,21 +475,42 @@ function buildTree(aReporters)
         }
       } else {
         // Non-leaf node without its own reporter.  Derive its size and
         // description entirely from its children.
         aT._amount = childrenBytes;
         aT._description = "The sum of all entries below '" + aT._name + "'.";
       }
     }
-    assert(aT._amount !== kUnknown);
+    assert(aT._amount !== kUnknown, "aT._amount !== kUnknown");
     return aT._amount;
   }
+
   fillInTree(t, "");
 
+  // Reduce the depth of the tree by the number of occurrences of '/' in
+  // aTreeName.  (Thus the tree named 'foo/bar/baz' will be rooted at 'baz'.)
+  var slashCount = 0;
+  for (var i = 0; i < aTreeName.length; i++) {
+    if (aTreeName[i] == '/') {
+      assert(t._kids.length == 1, "Not expecting multiple kids here.");
+      t = t._kids[0];
+    }
+  }
+
+  // Set the description on the root node.
+  t._description = kTreeDescriptions[t._name];
+
+  return t;
+}
+
+/**
+ * Do some work which only makes sense for the 'explicit' tree.
+ */
+function fixUpExplicitTree(aT, aReporters) {
   // Determine how many bytes are reported by heap reporters.  Be careful
   // with non-leaf reporters;  if we count a non-leaf reporter we don't want
   // to count any of its child reporters.
   var s = "";
   function getKnownHeapUsedBytes(aT)
   {
     if (aT._kind === KIND_HEAP) {
       return aT._amount;
@@ -424,37 +525,35 @@ function buildTree(aReporters)
 
   // A special case:  compute the derived "heap-unclassified" value.  Don't
   // mark "heap-allocated" when we get its size because we want it to appear
   // in the "Other Measurements" list.
   var heapUsedBytes = getBytes(aReporters, "heap-allocated", true);
   var unknownHeapUsedBytes = 0;
   var hasProblem = true;
   if (heapUsedBytes !== kUnknown) {
-    unknownHeapUsedBytes = heapUsedBytes - getKnownHeapUsedBytes(t);
+    unknownHeapUsedBytes = heapUsedBytes - getKnownHeapUsedBytes(aT);
     hasProblem = false;
   }
   var heapUnclassified = new TreeNode("heap-unclassified");
   // This kindToString() ensures the "(Heap)" prefix is set without having to
   // set the _kind property, which would mean that there is a corresponding
   // Reporter for this TreeNode (which isn't true).
   heapUnclassified._description =
       kindToString(KIND_HEAP) +
       "Memory not classified by a more specific reporter. This includes " +
       "waste due to internal fragmentation in the heap allocator (caused " +
       "when the allocator rounds up request sizes).";
   heapUnclassified._amount = unknownHeapUsedBytes;
   if (hasProblem) {
     heapUnclassified._hasProblem = true;
   }
 
-  t._kids.push(heapUnclassified);
-  t._amount += unknownHeapUsedBytes;
-
-  return t;
+  aT._kids.push(heapUnclassified);
+  aT._amount += unknownHeapUsedBytes;
 }
 
 /**
  * Sort all kid nodes from largest to smallest and aggregate insignificant
  * nodes.
  *
  * @param aTotalBytes
  *        The size of the tree's root node.
@@ -510,26 +609,36 @@ function filterTree(aTotalBytes, aT)
  * @param aProcess
  *        The name of the process.
  * @param aReporters
  *        Table of Reporters for this process, indexed by _path.
  * @return The generated text.
  */
 function genProcessText(aProcess, aReporters)
 {
-  var tree = buildTree(aReporters);
-  filterTree(tree._amount, tree);
+  var explicitTree = buildTree(aReporters, 'explicit');
+  fixUpExplicitTree(explicitTree, aReporters);
+  filterTree(explicitTree._amount, explicitTree);
+  var explicitText = genTreeText(explicitTree, aProcess);
 
-  // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
-  var text = "";
-  text += "<h1>" + aProcess + " Process</h1>\n\n";
-  text += genTreeText(tree);
-  text += genOtherText(aReporters);
-  text += "<hr></hr>";
-  return text;
+  var mapTreeText = '';
+  kMapTreePaths.forEach(function(t) {
+    var tree = buildTree(aReporters, t);
+    filterTree(tree._amount, tree);
+    mapTreeText += genTreeText(tree, aProcess);
+  });
+
+  // We have to call genOtherText after we process all the trees, because it
+  // looks at all the reporters which aren't part of a tree.
+  var otherText = genOtherText(aReporters, aProcess);
+
+  // The newlines give nice spacing if we cut+paste into a text buffer.
+  return "<h1>" + aProcess + " Process</h1>\n\n" +
+         explicitText + mapTreeText + otherText +
+         "<hr></hr>";
 }
 
 /**
  * Formats an int as a human-readable string.
  *
  * @param aN
  *        The integer to format.
  * @return A human-readable string representing the int.
@@ -730,22 +839,25 @@ function genMrNameText(aKind, aDesc, aNa
   return text + '\n';
 }
 
 /**
  * Generates the text for the tree, including its heading.
  *
  * @param aT
  *        The tree.
+ * @param aProcess
+ *        The process the tree corresponds to.
  * @return The generated text.
  */
-function genTreeText(aT)
+function genTreeText(aT, aProcess)
 {
   var treeBytes = aT._amount;
   var rootStringLength = aT.toString().length;
+  var isExplicitTree = aT._name == 'explicit';
 
   /**
    * Generates the text for a particular tree, without a heading.
    *
    * @param aT
    *        The tree.
    * @param aIndentGuide
    *        Records what indentation is required for this tree.  It has one
@@ -803,46 +915,37 @@ function genTreeText(aT)
     if (aT._amount === treeBytes) {
       perc = "100.0";
     } else {
       perc = (100 * aT._amount / treeBytes).toFixed(2);
       perc = pad(perc, 5, '0');
     }
     perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
 
+    // We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
+    // whole tree is non-heap.
+    var kind = isExplicitTree ? aT._kind : undefined;
     var text = indent + genMrValueText(tString) + " " + perc +
-               genMrNameText(aT._kind, aT._description, aT._name,
+               genMrNameText(kind, aT._description, aT._name,
                              aT._hasProblem, aT._nMerged);
 
     for (var i = 0; i < aT._kids.length; i++) {
       // 3 is the standard depth, the callee adjusts it if necessary.
       aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
       text += genTreeText2(aT._kids[i], aIndentGuide, tString.length);
       aIndentGuide.pop();
     }
     return text;
   }
 
   var text = genTreeText2(aT, [], rootStringLength);
-  // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
-  const desc =
-    "This tree covers explicit memory allocations by the application, " +
-    "both at the operating system level (via calls to functions such as " +
-    "VirtualAlloc, vm_allocate, and mmap), and at the heap allocation level " +
-    "(via functions such as malloc, calloc, realloc, memalign, operator " +
-    "new, and operator new[]).  It excludes memory that is mapped implicitly " +
-    "such as code and data segments, and thread stacks.  It also excludes " +
-    "heap memory that has been freed by the application but is still being " +
-    "held onto by the heap allocator.  It is not guaranteed to cover every " +
-    "explicit allocation, but it does cover most (including the entire " +
-    "heap), and therefore it is the single best number to focus on when " +
-    "trying to reduce memory usage.";
-               
-  return "<h2 class='hasDesc' title='" + escapeQuotes(desc) +
-         "'>Explicit Allocations</h2>\n" + "<pre>" + text + "</pre>\n";
+
+  // The explicit tree is not collapsed, but all other trees are, so pass
+  // !isExplicitTree for genSectionMarkup's aCollapsed parameter.
+  return genSectionMarkup(aProcess, aT._name, text, !isExplicitTree);
 }
 
 function OtherReporter(aPath, aUnits, aAmount, aDescription, 
                        aNMerged)
 {
   // Nb: _kind is not needed, it's always KIND_OTHER.
   this._path        = aPath;
   this._units       = aUnits;
@@ -875,19 +978,21 @@ OtherReporter.compare = function(a, b) {
          0;
 };
 
 /**
  * Generates the text for the "Other Measurements" section.
  *
  * @param aReportersByProcess
  *        Table of Reporters for this process, indexed by _path.
+ * @param aProcess
+ *        The process these reporters correspond to.
  * @return The generated text.
  */
-function genOtherText(aReportersByProcess)
+function genOtherText(aReportersByProcess, aProcess)
 {
   // Generate an array of Reporter-like elements, stripping out all the
   // Reporters that have already been handled.  Also find the width of the
   // widest element, so we can format things nicely.
   var maxStringLength = 0;
   var otherReporters = [];
   for (var path in aReportersByProcess) {
     var r = aReportersByProcess[path];
@@ -913,18 +1018,32 @@ function genOtherText(aReportersByProces
     var o = otherReporters[i];
     text += genMrValueText(pad(o.asString, maxStringLength, ' ')) + " ";
     text += genMrNameText(KIND_OTHER, o._description, o._path, o._hasProblem);
   }
 
   // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
   const desc = "This list contains other memory measurements that cross-cut " +
                "the requested memory measurements above."
-  return "<h2 class='hasDesc' title='" + desc + "'>Other Measurements</h2>\n" +
-         "<pre>" + text + "</pre>\n";
+
+  return genSectionMarkup(aProcess, 'other', text, false);
+}
+
+function genSectionMarkup(aProcess, aName, aText, aCollapsed)
+{
+  var headerId = 'header-' + aProcess + '-' + aName;
+  var preId = 'pre-' + aProcess + '-' + aName;
+  var elemClass = (aCollapsed ? 'collapsed' : '') + ' tree';
+
+  // Ugh.
+  return '<h2 id="' + headerId + '" class="' + elemClass + '" ' +
+         'onclick="toggleTreeVisibility(event)">' +
+           kTreeNames[aName] +
+         '</h2>\n' +
+         '<pre id="' + preId + '" class="' + elemClass + '">' + aText + '</pre>\n';
 }
 
 function assert(aCond, aMsg)
 {
   if (!aCond) {
     throw("assertion failed: " + aMsg);
   }
 }
--- a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
@@ -100,16 +100,27 @@
      { collectReports: function(cbObj, closure) {
           function f(p, k, u, a) { cbObj.callback("", p, k, u, a, "(desc)", closure); }
           f("explicit/g",       HEAP,    BYTES,  14 * MB); // internal
           f("other3",           OTHER,   COUNT, 777);
           f("other2",           OTHER,   BYTES, 222 * MB);
           f("perc2",            OTHER,   PERCENTAGE, 10000);
           f("perc1",            OTHER,   PERCENTAGE, 4567);
        }
+     },
+     { collectReports: function(cbObj, closure) {
+          // The amounts are given in pages, so multiply here by 4kb.
+          function f(p, a) { cbObj.callback("", p, NONHEAP, BYTES, a * 4 * KB, "(desc)", closure); }
+          f("map/vsize/a",     24);
+          f("map/swap/a",       1);
+          f("map/swap/a",       2);
+          f("map/vsize/a",      19);
+          f("map/swap/b/c",     10);
+          f("map/resident/a",   42);
+       }
      }
   ];
   for (var i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporter(fakeReporters[i]);
   }
   for (var i = 0; i < fakeMultiReporters.length; i++) {
     mgr.registerMultiReporter(fakeMultiReporters[i]);
   }
@@ -187,16 +198,30 @@ 623.58 MB (100.0%) -- explicit\n\
 │         └──20.00 MB (03.21%) -- i\n\
 ├───15.00 MB (02.41%) -- g [2]\n\
 │   ├───6.00 MB (00.96%) -- a\n\
 │   ├───5.00 MB (00.80%) -- b\n\
 │   └───4.00 MB (00.64%) -- other\n\
 ├───11.00 MB (01.76%) -- heap-unclassified\n\
 └────0.58 MB (00.09%) -- (2 omitted)\n\
 \n\
+Resident Set Size (RSS) Breakdown\n\
+0.16 MB (100.0%) -- resident\n\
+└──0.16 MB (100.0%) -- a\n\
+\n\
+Virtual Size Breakdown\n\
+0.17 MB (100.0%) -- vsize\n\
+└──0.17 MB (100.0%) -- a [2]\n\
+\n\
+Swap Usage Breakdown\n\
+0.05 MB (100.0%) -- swap\n\
+├──0.04 MB (76.92%) -- b\n\
+│  └──0.04 MB (76.92%) -- c\n\
+└──0.01 MB (23.08%) -- a [2]\n\
+\n\
 Other Measurements\n\
 500.00 MB -- heap-allocated\n\
 100.00 MB -- heap-unallocated\n\
 111.00 MB -- other1\n\
 222.00 MB -- other2\n\
       777 -- other3\n\
       888 -- other4\n\
    45.67% -- perc1\n\
@@ -208,32 +233,50 @@ Explicit Allocations\n\
 1,000.00 MB (100.0%) -- explicit\n\
 ├────499.00 MB (49.90%) -- a\n\
 │    └──499.00 MB (49.90%) -- b\n\
 │       └──499.00 MB (49.90%) -- c [3]\n\
 ├────200.00 MB (20.00%) -- flip/the/backslashes\n\
 ├────200.00 MB (20.00%) -- compartment(this-will-be-truncated-in-non-verbose-mo...)\n\
 └────101.00 MB (10.10%) -- heap-unclassified\n\
 \n\
+Resident Set Size (RSS) Breakdown\n\
+0.00 MB (100.0%) -- resident\n\
+\n\
+Virtual Size Breakdown\n\
+0.00 MB (100.0%) -- vsize\n\
+\n\
+Swap Usage Breakdown\n\
+0.00 MB (100.0%) -- swap\n\
+\n\
 Other Measurements\n\
   666.00 MB -- danger<script>window.alert(1)</script>\n\
 1,000.00 MB -- heap-allocated\n\
   100.00 MB -- heap-unallocated\n\
   111.00 MB -- other1\n\
 \n\
 3rd Process\n\
 \n\
 Explicit Allocations\n\
 777.00 MB (100.0%) -- explicit\n\
 ├──777.00 MB (100.0%) -- a [*]\n\
 │  ├──444.00 MB (57.14%) -- c [2]\n\
 │  ├──333.00 MB (42.86%) -- b\n\
 │  └────0.00 MB (00.00%) -- (1 omitted)\n\
 └────0.00 MB (00.00%) -- (2 omitted)\n\
 \n\
+Resident Set Size (RSS) Breakdown\n\
+0.00 MB (100.0%) -- resident\n\
+\n\
+Virtual Size Breakdown\n\
+0.00 MB (100.0%) -- vsize\n\
+\n\
+Swap Usage Breakdown\n\
+0.00 MB (100.0%) -- swap\n\
+\n\
 Other Measurements\n\
 0.00 MB -- heap-allocated [*]\n\
 0.00 MB -- other1 [*]\n\
 \n\
 ";
 
   var amvExpectedText =
 "\
@@ -259,16 +302,30 @@ 653,876,224 B (100.0%) -- explicit\n\
 ├───15,728,640 B (02.41%) -- g [2]\n\
 │   ├───6,291,456 B (00.96%) -- a\n\
 │   ├───5,242,880 B (00.80%) -- b\n\
 │   └───4,194,304 B (00.64%) -- other\n\
 ├───11,534,336 B (01.76%) -- heap-unclassified\n\
 ├──────510,976 B (00.08%) -- d\n\
 └──────102,400 B (00.02%) -- e\n\
 \n\
+Resident Set Size (RSS) Breakdown\n\
+172,032 B (100.0%) -- resident\n\
+└──172,032 B (100.0%) -- a\n\
+\n\
+Virtual Size Breakdown\n\
+176,128 B (100.0%) -- vsize\n\
+└──176,128 B (100.0%) -- a [2]\n\
+\n\
+Swap Usage Breakdown\n\
+53,248 B (100.0%) -- swap\n\
+├──40,960 B (76.92%) -- b\n\
+│  └──40,960 B (76.92%) -- c\n\
+└──12,288 B (23.08%) -- a [2]\n\
+\n\
 Other Measurements\n\
 524,288,000 B -- heap-allocated\n\
 104,857,600 B -- heap-unallocated\n\
 116,391,936 B -- other1\n\
 232,783,872 B -- other2\n\
           777 -- other3\n\
           888 -- other4\n\
        45.67% -- perc1\n\
@@ -280,16 +337,25 @@ Explicit Allocations\n\
 1,048,576,000 B (100.0%) -- explicit\n\
 ├────523,239,424 B (49.90%) -- a\n\
 │    └──523,239,424 B (49.90%) -- b\n\
 │       └──523,239,424 B (49.90%) -- c [3]\n\
 ├────209,715,200 B (20.00%) -- flip/the/backslashes\n\
 ├────209,715,200 B (20.00%) -- compartment(this-will-be-truncated-in-non-verbose-mode-abcdefghijklmnopqrstuvwxyz)\n\
 └────105,906,176 B (10.10%) -- heap-unclassified\n\
 \n\
+Resident Set Size (RSS) Breakdown\n\
+0 B (100.0%) -- resident\n\
+\n\
+Virtual Size Breakdown\n\
+0 B (100.0%) -- vsize\n\
+\n\
+Swap Usage Breakdown\n\
+0 B (100.0%) -- swap\n\
+\n\
 Other Measurements\n\
   698,351,616 B -- danger<script>window.alert(1)</script>\n\
 1,048,576,000 B -- heap-allocated\n\
   104,857,600 B -- heap-unallocated\n\
   116,391,936 B -- other1\n\
 \n\
 3rd Process\n\
 \n\
@@ -297,16 +363,25 @@ Explicit Allocations\n\
 814,743,552 B (100.0%) -- explicit\n\
 ├──814,743,552 B (100.0%) -- a [*]\n\
 │  ├──465,567,744 B (57.14%) -- c [2]\n\
 │  ├──349,175,808 B (42.86%) -- b\n\
 │  └────────────0 B (00.00%) -- d [*] [2]\n\
 ├────────────0 B (00.00%) -- b [*]\n\
 └────────────0 B (00.00%) -- heap-unclassified [*]\n\
 \n\
+Resident Set Size (RSS) Breakdown\n\
+0 B (100.0%) -- resident\n\
+\n\
+Virtual Size Breakdown\n\
+0 B (100.0%) -- vsize\n\
+\n\
+Swap Usage Breakdown\n\
+0 B (100.0%) -- swap\n\
+\n\
 Other Measurements\n\
 0 B -- heap-allocated [*]\n\
 0 B -- other1 [*]\n\
 \n\
 "
 
   function finish()
   {
@@ -323,32 +398,54 @@ 0 B -- other1 [*]\n\
       mgr.registerReporter(realReporters[i]);
     }
     for (var i = 0; i < realMultiReporters.length; i++) {
       mgr.registerMultiReporter(realMultiReporters[i]);
     }
     SimpleTest.finish();
   }
 
+  function check(actual, expected) {
+    var a = actual.QueryInterface(Ci.nsISupportsString).data;
+    if (a != expected) {
+      dump("*******ACTUAL*******\n");
+      dump(a);
+      dump("******EXPECTED******\n");
+      dump(expected);
+      dump("********************\n");
+      return false;
+    }
+    return true;
+  }
+
   // Cut+paste the entire page and check that the cut text matches what we
   // expect.  This tests the output in general and also that the cutting and
   // pasting works as expected.
   function test(aFrame, aExpectedText, aNext) {
-    document.querySelector("#" + aFrame).focus();
-    SimpleTest.waitForClipboard(aExpectedText,
-      function() {
-        synthesizeKey("A", {accelKey: true});
-        synthesizeKey("C", {accelKey: true});
-      },
-      aNext,
-      function() {
-        ok(false, "pasted text doesn't match for " + aFrame);
-        finish();
-      }
-    );
+    // Click all h2.collapsed elements so they expand.
+    var win = document.querySelector("#" + aFrame).contentWindow;
+    var nodes = win.document.querySelectorAll("pre.collapsed");
+    for (var i = 0; i < nodes.length; i++) {
+      nodes[i].classList.toggle('collapsed');
+    }
+
+    SimpleTest.executeSoon(function() {
+      document.querySelector("#" + aFrame).focus();
+      SimpleTest.waitForClipboard(function(actual) { return check(actual, aExpectedText) },
+        function() {
+          synthesizeKey("A", {accelKey: true});
+          synthesizeKey("C", {accelKey: true});
+        },
+        aNext,
+        function() {
+          ok(false, "pasted text doesn't match for " + aFrame);
+          finish();
+        }
+      );
+    });
   }
 
   addLoadEvent(function() {
     test(
       "amFrame",
       amExpectedText,
       function() {
         test(
--- a/xpcom/base/Makefile.in
+++ b/xpcom/base/Makefile.in
@@ -67,16 +67,20 @@ CPPSRCS		= \
 		nsUUIDGenerator.cpp \
 		nsSystemInfo.cpp \
 		nsCycleCollector.cpp \
 		nsStackWalk.cpp \
 		nsMemoryReporterManager.cpp \
 		FunctionTimer.cpp \
 		$(NULL)
 
+ifeq ($(OS_ARCH),Linux)
+CPPSRCS += MapsMemoryReporter.cpp
+endif
+
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS		+= nsMacUtilsImpl.cpp
 endif
 
 EXPORTS		= \
 		nsAgg.h \
 		nsAutoRef.h \
 		nsCom.h \
@@ -89,16 +93,17 @@ EXPORTS		= \
 		nsWeakPtr.h \
 		nsInterfaceRequestorAgg.h \
 		$(NULL)
 
 EXPORTS_NAMESPACES = mozilla
 
 EXPORTS_mozilla = \
 	FunctionTimer.h \
+	MapsMemoryReporter.h \
 	$(NULL)
 
 ifeq (windows,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += nsCrashOnException.cpp
 endif
 
 ifeq ($(OS_ARCH),WINNT)
 ifdef MOZ_DEBUG
new file mode 100644
--- /dev/null
+++ b/xpcom/base/MapsMemoryReporter.cpp
@@ -0,0 +1,456 @@
+/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 ci et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "mozilla/MapsMemoryReporter.h"
+#include "nsIMemoryReporter.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsHashSets.h"
+#include <stdio.h>
+
+namespace mozilla {
+namespace MapsMemoryReporter {
+
+#if !defined(XP_LINUX)
+#error "This doesn't have a prayer of working if we're not on Linux."
+#endif
+
+// mozillaLibraries is a list of all the shared libraries we build.  This list
+// is used for determining whether a library is a "Mozilla library" or a
+// "third-party library".  But even if this list is missing items, about:memory
+// will identify a library in the same directory as libxul.so as a "Mozilla
+// library".
+const char* mozillaLibraries[] =
+{
+  "libfreebl3.so",
+  "libmozalloc.so",
+  "libmozsqlite3.so",
+  "libnspr4.so",
+  "libnss3.so",
+  "libnssckbi.so",
+  "libnssdbm3.so",
+  "libnssutil3.so",
+  "libplc4.so",
+  "libplds4.so",
+  "libsmime3.so",
+  "libsoftokn3.so",
+  "libssl3.so",
+  "libxpcom.so",
+  "libxul.so"
+};
+
+namespace {
+
+bool EndsWithLiteral(const nsCString &aHaystack, const char *aNeedle)
+{
+  PRInt32 idx = aHaystack.RFind(aNeedle);
+  if (idx == -1) {
+    return false;
+  }
+
+  return idx + strlen(aNeedle) == aHaystack.Length();
+}
+
+void GetDirname(const nsCString &aPath, nsACString &aOut)
+{
+  PRInt32 idx = aPath.RFind("/");
+  if (idx == -1) {
+    aOut.Truncate();
+  }
+  else {
+    aOut.Assign(Substring(aPath, 0, idx));
+  }
+}
+
+void GetBasename(const nsCString &aPath, nsACString &aOut)
+{
+  PRInt32 idx = aPath.RFind("/");
+  if (idx == -1) {
+    aOut.Assign(aPath);
+  }
+  else {
+    aOut.Assign(Substring(aPath, idx + 1));
+  }
+}
+
+} // anonymous namespace
+
+class MapsReporter : public nsIMemoryMultiReporter
+{
+public:
+  MapsReporter();
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD
+  CollectReports(nsIMemoryMultiReporterCallback *aCallback,
+                 nsISupports *aClosure);
+
+private:
+  // Search through /proc/self/maps for libxul.so, and set mLibxulDir to the
+  // the directory containing libxul.
+  nsresult FindLibxul();
+
+  nsresult
+  ParseMapping(FILE *aFile,
+               nsIMemoryMultiReporterCallback *aCallback,
+               nsISupports *aClosure);
+
+  void
+  GetReporterNameAndDescription(const char *aPath,
+                                const char *aPermissions,
+                                nsACString &aName,
+                                nsACString &aDesc);
+
+  nsresult
+  ParseMapBody(FILE *aFile,
+               const nsACString &aName,
+               const nsACString &aDescription,
+               nsIMemoryMultiReporterCallback *aCallback,
+               nsISupports *aClosure);
+
+  nsCString mLibxulDir;
+  nsCStringHashSet mMozillaLibraries;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(MapsReporter, nsIMemoryMultiReporter)
+
+MapsReporter::MapsReporter()
+{
+  const PRUint32 len = NS_ARRAY_LENGTH(mozillaLibraries);
+  mMozillaLibraries.Init(len);
+  for (PRUint32 i = 0; i < len; i++) {
+    nsCAutoString str;
+    str.Assign(mozillaLibraries[i]);
+    mMozillaLibraries.Put(str);
+  }
+}
+
+NS_IMETHODIMP
+MapsReporter::CollectReports(nsIMemoryMultiReporterCallback *aCallback,
+                             nsISupports *aClosure)
+{
+  FILE *f = fopen("/proc/self/smaps", "r");
+  if (!f)
+    return NS_ERROR_FAILURE;
+
+  while (true) {
+    nsresult rv = ParseMapping(f, aCallback, aClosure);
+    if (NS_FAILED(rv))
+      break;
+  }
+
+  fclose(f);
+  return NS_OK;
+}
+
+nsresult
+MapsReporter::FindLibxul()
+{
+  mLibxulDir.Truncate();
+
+  // Note that we're scanning /proc/self/*maps*, not smaps, here.
+  FILE *f = fopen("/proc/self/maps", "r");
+  if (!f)
+    return NS_ERROR_FAILURE;
+
+  while (true) {
+    // Skip any number of non-slash characters, then capture starting with the
+    // slash to the newline.  This is the path part of /proc/self/maps.
+    char path[1025];
+    int numRead = fscanf(f, "%*[^/]%1024[^\n]", path);
+    if (numRead != 1) {
+      break;
+    }
+
+    nsCAutoString pathStr;
+    pathStr.Append(path);
+
+    nsCAutoString basename;
+    GetBasename(pathStr, basename);
+
+    if (basename.EqualsLiteral("libxul.so")) {
+      GetDirname(pathStr, mLibxulDir);
+      break;
+    }
+  }
+
+  fclose(f);
+  return mLibxulDir.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+MapsReporter::ParseMapping(
+  FILE *aFile,
+  nsIMemoryMultiReporterCallback *aCallback,
+  nsISupports *aClosure)
+{
+  // We need to use native types in order to get good warnings from fscanf, so
+  // let's make sure that the native types have the sizes we expect.
+  PR_STATIC_ASSERT(sizeof(long long) == sizeof(PRInt64));
+  PR_STATIC_ASSERT(sizeof(int) == sizeof(PRInt32));
+
+  if (mLibxulDir.IsEmpty()) {
+    NS_ENSURE_SUCCESS(FindLibxul(), NS_ERROR_FAILURE);
+  }
+
+  // The first line of an entry in /proc/self/smaps looks just like an entry
+  // in /proc/maps:
+  //
+  //   address           perms offset  dev   inode  pathname
+  //   02366000-025d8000 rw-p 00000000 00:00 0      [heap]
+
+  const int argCount = 8;
+
+  unsigned long long addrStart, addrEnd;
+  char perms[5];
+  unsigned long long offset;
+  unsigned int devMajor, devMinor, inode;
+  char path[1025];
+
+  // A path might not be present on this line; set it to the empty string.
+  path[0] = '\0';
+
+  // This is a bit tricky.  Whitespace in a scanf pattern matches *any*
+  // whitespace, including newlines.  We want this pattern to match a line
+  // with or without a path, but we don't want to look to a new line for the
+  // path.  Thus we have %u%1024[^\n] at the end of the pattern.  This will
+  // capture into the path some leading whitespace, which we'll later trim off.
+  int numRead = fscanf(aFile, "%llx-%llx %4s %llx %u:%u %u%1024[^\n]",
+                       &addrStart, &addrEnd, perms, &offset, &devMajor,
+                       &devMinor, &inode, path);
+
+  // Eat up any whitespace at the end of this line, including the newline.
+  fscanf(aFile, " ");
+
+  // We might or might not have a path, but the rest of the arguments should be
+  // there.
+  if (numRead != argCount && numRead != argCount - 1)
+    return NS_ERROR_FAILURE;
+
+  nsCAutoString name, description;
+  GetReporterNameAndDescription(path, perms, name, description);
+
+  while (true) {
+    nsresult rv = ParseMapBody(aFile, name, description, aCallback, aClosure);
+    if (NS_FAILED(rv))
+      break;
+  }
+
+  return NS_OK;
+}
+
+void
+MapsReporter::GetReporterNameAndDescription(
+  const char *aPath,
+  const char *aPerms,
+  nsACString &aName,
+  nsACString &aDesc)
+{
+  aName.Truncate();
+  aDesc.Truncate();
+
+  // If aPath points to a file, we have its absolute path, plus some
+  // whitespace.  Truncate this to its basename, and put the absolute path in
+  // the description.
+  nsCAutoString absPath;
+  absPath.Append(aPath);
+  absPath.StripChars(" ");
+
+  nsCAutoString basename;
+  GetBasename(absPath, basename);
+
+  if (basename.EqualsLiteral("[heap]")) {
+    aName.Append("anonymous/anonymous, within brk()");
+    aDesc.Append("Memory in anonymous mappings within the boundaries "
+                 "defined by brk() / sbrk().  This is likely to be just "
+                 "a portion of the application's heap; the remainder "
+                 "lives in other anonymous mappings. This node corresponds to "
+                 "'[heap]' in /proc/self/smaps.");
+  }
+  else if (basename.EqualsLiteral("[stack]")) {
+    aName.Append("main thread's stack");
+    aDesc.Append("The stack size of the process's main thread.  This node "
+                 "corresponds to '[stack]' in /proc/self/smaps.");
+  }
+  else if (basename.EqualsLiteral("[vdso]")) {
+    aName.Append("vdso");
+    aDesc.Append("The virtual dynamically-linked shared object, also known as "
+                 "the 'vsyscall page'. This is a memory region mapped by the "
+                 "operating system for the purpose of allowing processes to "
+                 "perform some privileged actions without the overhead of a "
+                 "syscall.");
+  }
+  else if (!basename.IsEmpty()) {
+    NS_ASSERTION(!mLibxulDir.IsEmpty(), "mLibxulDir should not be empty.");
+
+    nsCAutoString dirname;
+    GetDirname(absPath, dirname);
+
+    // Hack: A file is a shared library if the basename contains ".so" and its
+    // dirname contains "/lib", or if the basename ends with ".so".
+    if (EndsWithLiteral(basename, ".so") ||
+        (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
+      aName.Append("shared-libraries/");
+      if (dirname.Equals(mLibxulDir) || mMozillaLibraries.Contains(basename)) {
+        aName.Append("shared-libraries-mozilla/");
+      }
+      else {
+        aName.Append("shared-libraries-other/");
+      }
+    }
+    else {
+      aName.Append("other-files/");
+      if (EndsWithLiteral(basename, ".xpi")) {
+        aName.Append("extensions/");
+      }
+      else if (dirname.Find("/fontconfig") != -1) {
+        aName.Append("fontconfig/");
+      }
+    }
+
+    aName.Append(basename);
+    aDesc.Append(absPath);
+  }
+  else {
+    aName.Append("anonymous/anonymous, outside brk()");
+    aDesc.Append("Memory in anonymous mappings outside the boundaries defined "
+                 "by brk() / sbrk().");
+  }
+
+  aName.Append(" [");
+  aName.Append(aPerms);
+  aName.Append("]");
+
+  // Modify the description to include an explanation of the permissions.
+  aDesc.Append(" (");
+  if (strstr(aPerms, "rw")) {
+    aDesc.Append("read/write, ");
+  }
+  else if (strchr(aPerms, 'r')) {
+    aDesc.Append("read-only, ");
+  }
+  else if (strchr(aPerms, 'w')) {
+    aDesc.Append("write-only, ");
+  }
+  else {
+    aDesc.Append("not readable, not writable, ");
+  }
+
+  if (strchr(aPerms, 'x')) {
+    aDesc.Append("executable, ");
+  }
+  else {
+    aDesc.Append("not executable, ");
+  }
+
+  if (strchr(aPerms, 's')) {
+    aDesc.Append("shared");
+  }
+  else if (strchr(aPerms, 'p')) {
+    aDesc.Append("private");
+  }
+  else {
+    aDesc.Append("not shared or private??");
+  }
+  aDesc.Append(")");
+}
+
+nsresult
+MapsReporter::ParseMapBody(
+  FILE *aFile,
+  const nsACString &aName,
+  const nsACString &aDescription,
+  nsIMemoryMultiReporterCallback *aCallback,
+  nsISupports *aClosure)
+{
+  PR_STATIC_ASSERT(sizeof(long long) == sizeof(PRInt64));
+
+  const int argCount = 2;
+
+  char desc[1025];
+  unsigned long long size;
+  if (fscanf(aFile, "%1024[a-zA-Z_]: %llu kB\n",
+             desc, &size) != argCount) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Don't report nodes with size 0.
+  if (size == 0)
+    return NS_OK;
+
+  const char* category;
+  if (strcmp(desc, "Size") == 0) {
+    category = "vsize";
+  }
+  else if (strcmp(desc, "Rss") == 0) {
+    category = "resident";
+  }
+  else if (strcmp(desc, "Swap") == 0) {
+    category = "swap";
+  }
+  else {
+    // Don't report this category.
+    return NS_OK;
+  }
+
+  nsCAutoString path;
+  path.Append("map/");
+  path.Append(category);
+  path.Append("/");
+  path.Append(aName);
+
+  aCallback->Callback(NS_LITERAL_CSTRING(""),
+                      path,
+                      nsIMemoryReporter::KIND_NONHEAP,
+                      nsIMemoryReporter::UNITS_BYTES,
+                      PRInt64(size) * 1024, // convert from kB to bytes
+                      aDescription, aClosure);
+
+  return NS_OK;
+}
+
+void Init()
+{
+  nsCOMPtr<nsIMemoryMultiReporter> reporter = new MapsReporter();
+  NS_RegisterMemoryMultiReporter(reporter);
+}
+
+} // namespace MapsMemoryReporter
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/base/MapsMemoryReporter.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 ci et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_MapsMemoryReporter_h_
+#define mozilla_MapsMemoryReporter_h_
+
+namespace mozilla {
+namespace MapsMemoryReporter {
+
+// This only works on Linux, but to make callers' lives easier, we stub out
+// empty functions on other platforms.
+
+#if defined(XP_LINUX)
+  void Init();
+#else
+  void Init() {}
+#endif
+
+} // namespace MapsMemoryReporter
+} // namespace mozilla
+
+#endif
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -59,17 +59,17 @@ interface nsIMemoryReporter : nsISupport
    * (This is true even for reporters in a child process.)  When a reporter
    * from a child process is copied into the main process, the copy has its
    * 'process' field set appropriately.
    */
   readonly attribute ACString process;
 
   /*
    * The path that this memory usage should be reported under.  Paths are
-   * '/'-delimited, eg. "a/b/c".  There are two categories of paths.
+   * '/'-delimited, eg. "a/b/c".  There are three categories of paths.
    *
    * - Paths starting with "explicit" represent regions of memory that have
    *   been explicitly allocated with an OS-level allocation (eg.
    *   mmap/VirtualAlloc/vm_allocate) or a heap-level allocation (eg.
    *   malloc/calloc/operator new).
    *
    *   Each reporter can be viewed as representing a node in a tree rooted at
    *   "explicit".  Not all nodes of the tree need have an associated reporter.
@@ -86,16 +86,24 @@ interface nsIMemoryReporter : nsISupport
    *
    *   Nodes marked with a [*] have a reporter.  Notice that "explicit/a" is
    *   implicitly defined.
    *
    *   A node's children divide their parent's memory into disjoint pieces.
    *   So in the example above, |a| may not count any allocations counted by
    *   |d|, and vice versa.
    *
+   * - Paths starting with "map" represent regions of virtual memory that the
+   *   process has mapped.  The reporter immediately beneath "map" describes
+   *   the type of measurement; for instance, the reporter "map/rss/[stack]"
+   *   might report how much of the process's stack is currently in physical
+   *   memory.
+   *
+   *   Reporters in this category must have kind NONHEAP and units BYTES.
+   *
    * - All other paths represent cross-cutting values and may overlap with any
    *   other reporter.
    */
   readonly attribute AUTF8String path;
 
   /*
    * There are three categories of memory reporters:
    *
@@ -103,21 +111,21 @@ interface nsIMemoryReporter : nsISupport
    *    calloc, realloc, memalign, operator new, or operator new[].  Reporters
    *    in this category must have units UNITS_BYTES and must have a path
    *    starting with "explicit".
 
    *  - NONHEAP: 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
-   *    and must have a path starting with "explicit".
+   *    and must have a path starting with "explicit" or "map".
    *
    *  - OTHER: reporters which don't fit into either of these categories. Such
-   *    reporters must have a path that does not start with "explicit" and may
-   *    have any units.
+   *    reporters must have a path that does not start with "explicit" or "map"
+   *    and may have any units.
    */
   const PRInt32 KIND_NONHEAP = 0;
   const PRInt32 KIND_HEAP    = 1;
   const PRInt32 KIND_OTHER   = 2;
 
   /*
    * KIND_MAPPED is a deprecated synonym for KIND_NONHEAP.  We keep it around
    * to as not to break extensions which might use this interface, but we will
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -570,17 +570,20 @@ class MemoryReportCallback : public nsIM
 public:
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
                         PRInt32 aKind, PRInt32 aUnits, PRInt64 aAmount,
                         const nsACString &aDescription,
                         nsISupports *aWrappedMRs)
     {
-        if (aKind == nsIMemoryReporter::KIND_NONHEAP && aAmount != PRInt64(-1)) {
+        if (aKind == nsIMemoryReporter::KIND_NONHEAP &&
+            PromiseFlatCString(aPath).Find("explicit") == 0 &&
+            aAmount != PRInt64(-1)) {
+
             MemoryReportsWrapper *wrappedMRs =
                 static_cast<MemoryReportsWrapper *>(aWrappedMRs);
             MemoryReport mr(aPath, aAmount);
             wrappedMRs->mReports->AppendElement(mr);
         }
         return NS_OK;
     }
 };
@@ -603,79 +606,79 @@ isParent(const nsACString &path1, const 
 
 NS_IMETHODIMP
 nsMemoryReporterManager::GetExplicit(PRInt64 *aExplicit)
 {
     InfallibleTArray<MemoryReport> nonheap;
     PRInt64 heapUsed = PRInt64(-1);
 
     // Get "heap-allocated" and all the KIND_NONHEAP measurements from vanilla
-    // reporters.
+    // "explicit" reporters.
     nsCOMPtr<nsISimpleEnumerator> e;
     EnumerateReporters(getter_AddRefs(e));
 
     PRBool more;
     while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsIMemoryReporter> r;
         e->GetNext(getter_AddRefs(r));
 
         PRInt32 kind;
         nsresult rv = r->GetKind(&kind);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        if (kind == nsIMemoryReporter::KIND_NONHEAP) {
-            nsCString path;
-            rv = r->GetPath(path);
-            NS_ENSURE_SUCCESS(rv, rv);
+        nsCString path;
+        rv = r->GetPath(path);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // We're only interested in NONHEAP explicit reporters and
+        // the 'heap-allocated' reporter.
+        if (kind == nsIMemoryReporter::KIND_NONHEAP &&
+            path.Find("explicit") == 0) {
 
             PRInt64 amount;
             rv = r->GetAmount(&amount);
             NS_ENSURE_SUCCESS(rv, rv);
 
             // Just skip any NONHEAP reporters that fail, because
             // "heap-allocated" is the most important one.
             if (amount != PRInt64(-1)) {
                 MemoryReport mr(path, amount);
                 nonheap.AppendElement(mr);
             }
-        } else {
-            nsCString path;
-            rv = r->GetPath(path);
+        } else if (path.Equals("heap-allocated")) {
+            rv = r->GetAmount(&heapUsed);
             NS_ENSURE_SUCCESS(rv, rv);
-
-            if (path.Equals("heap-allocated")) {
-                rv = r->GetAmount(&heapUsed);
-                NS_ENSURE_SUCCESS(rv, rv);
-                // If "heap-allocated" fails, we give up, because the result
-                // would be horribly inaccurate.
-                if (heapUsed == PRInt64(-1)) {
-                    *aExplicit = PRInt64(-1);
-                    return NS_OK;
-                }
+            // If "heap-allocated" fails, we give up, because the result
+            // would be horribly inaccurate.
+            if (heapUsed == PRInt64(-1)) {
+                *aExplicit = PRInt64(-1);
+                return NS_OK;
             }
         }
     }
 
     // Get KIND_NONHEAP measurements from multi-reporters, too.
     nsCOMPtr<nsISimpleEnumerator> e2;
     EnumerateMultiReporters(getter_AddRefs(e2));
     nsRefPtr<MemoryReportsWrapper> wrappedMRs =
         new MemoryReportsWrapper(&nonheap);
+
+    // This callback adds only NONHEAP explicit reporters.
     nsRefPtr<MemoryReportCallback> cb = new MemoryReportCallback();
 
     while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
       nsCOMPtr<nsIMemoryMultiReporter> r;
       e2->GetNext(getter_AddRefs(r));
       r->CollectReports(cb, wrappedMRs);
     }
 
     // Ignore (by zeroing its amount) any reporter that is a child of another
     // reporter.  Eg. if we have "explicit/a" and "explicit/a/b", zero the
-    // latter.  This is quadratic in the number of NONHEAP reporters, but there
-    // shouldn't be many.
+    // latter.  This is quadratic in the number of explicit NONHEAP reporters,
+    // but there shouldn't be many.
     for (PRUint32 i = 0; i < nonheap.Length(); i++) {
         const nsCString &iPath = nonheap[i].path;
         for (PRUint32 j = i + 1; j < nonheap.Length(); j++) {
             const nsCString &jPath = nonheap[j].path;
             if (isParent(iPath, jPath)) {
                 nonheap[j].amount = 0;
             } else if (isParent(jPath, iPath)) {
                 nonheap[i].amount = 0;
--- a/xpcom/build/Makefile.in
+++ b/xpcom/build/Makefile.in
@@ -50,16 +50,20 @@ MODULE		= xpcom
 LIBRARY_NAME	= xpcom_core
 SHORT_LIBNAME	= xpcomcor
 LIBXUL_LIBRARY = 1
 EXPORT_LIBRARY = 1
 
 GRE_MODULE	= 1
 MOZILLA_INTERNAL_API = 1
 
+ifeq ($(OS_ARCH),Linux)
+DEFINES += -DXP_LINUX
+endif
+
 CPPSRCS		= \
 		$(XPCOM_GLUE_SRC_LCPPSRCS) \
 		$(XPCOM_GLUENS_SRC_LCPPSRCS) \
 		nsXPComInit.cpp \
 		nsXPCOMStrings.cpp \
 		Services.cpp \
 		Omnijar.cpp \
 		$(NULL)
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -145,16 +145,17 @@ extern nsresult nsStringInputStreamConst
 
 #include "mozilla/scache/StartupCache.h"
 
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/MapsMemoryReporter.h"
 
 using base::AtExitManager;
 using mozilla::ipc::BrowserProcessSubThread;
 
 namespace {
 
 static AtExitManager* sExitManager;
 static MessageLoop* sMessageLoop;
@@ -522,16 +523,18 @@ NS_InitXPCOM2(nsIServiceManager* *result
     // Notify observers of xpcom autoregistration start
     NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, 
                                   nsnull,
                                   NS_XPCOM_STARTUP_OBSERVER_ID);
 #ifdef XP_WIN
     ScheduleMediaCacheRemover();
 #endif
 
+    mozilla::MapsMemoryReporter::Init();
+
     return NS_OK;
 }
 
 
 //
 // NS_ShutdownXPCOM()
 //
 // The shutdown sequence for xpcom would be