Bug 674290 - Expose contents of /proc/self/maps and smaps in about:memory. r=njn
☠☠ backed out by d01a282b5a40 ☠ ☠
authorJustin Lebar <justin.lebar@gmail.com>
Fri, 05 Aug 2011 18:22:11 -0400
changeset 77940 6cd3556fc807302d9965f477fac6ccaf47f82357
parent 77939 da3c0a9bd68812475efffa4371f8ec3910d8ce45
child 77941 9d0d13998ebb66a76d5f15d1889e8499b2d2b469
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs674290
milestone9.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 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,454 @@
+/* -*- 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 nsCString mozillaLibraries[] =
+{
+  NS_LITERAL_CSTRING("libfreebl3.so"),
+  NS_LITERAL_CSTRING("libmozalloc.so"),
+  NS_LITERAL_CSTRING("libmozsqlite3.so"),
+  NS_LITERAL_CSTRING("libnspr4.so"),
+  NS_LITERAL_CSTRING("libnss3.so"),
+  NS_LITERAL_CSTRING("libnssckbi.so"),
+  NS_LITERAL_CSTRING("libnssdbm3.so"),
+  NS_LITERAL_CSTRING("libnssutil3.so"),
+  NS_LITERAL_CSTRING("libplc4.so"),
+  NS_LITERAL_CSTRING("libplds4.so"),
+  NS_LITERAL_CSTRING("libsmime3.so"),
+  NS_LITERAL_CSTRING("libsoftokn3.so"),
+  NS_LITERAL_CSTRING("libssl3.so"),
+  NS_LITERAL_CSTRING("libxpcom.so"),
+  NS_LITERAL_CSTRING("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++) {
+    mMozillaLibraries.Put(mozillaLibraries[i]);
+  }
+}
+
+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