Bug 945973 (part 4) - Add SystemMemoryReporter, which presents Linux-specific, system-wide memory measurements taken from the OS. r=glandium.
authorNicholas Nethercote <nnethercote@mozilla.com>
Wed, 04 Dec 2013 19:32:50 -0800
changeset 160755 37aa7ce35dc65464ad179e2a17ef7360d845bec9
parent 160754 0a276a083a26ee59a019ac0a61c8b5394aee9adc
child 160756 574c74c2f035155a2343c39fc36a71365b0eacbe
push id25848
push usercbook@mozilla.com
push dateTue, 17 Dec 2013 11:46:20 +0000
treeherdermozilla-central@03ff4ad4bc05 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs945973
milestone29.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 945973 (part 4) - Add SystemMemoryReporter, which presents Linux-specific, system-wide memory measurements taken from the OS. r=glandium.
b2g/app/b2g.js
modules/libpref/src/init/all.js
toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
xpcom/base/SystemMemoryReporter.cpp
xpcom/base/SystemMemoryReporter.h
xpcom/base/moz.build
xpcom/build/nsXPComInit.cpp
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -739,16 +739,19 @@ pref("jsloader.reuseGlobal", true);
 pref("font.size.inflation.minTwips", 120);
 // And disable it for lingering master-process UI.
 pref("font.size.inflation.disabledInMasterProcess", true);
 
 // Enable freeing dirty pages when minimizing memory; this reduces memory
 // consumption when applications are sent to the background.
 pref("memory.free_dirty_pages", true);
 
+// Enable the Linux-specific, system-wide memory reporter.
+pref("memory.system_memory_reporter", true);
+
 pref("layout.imagevisibility.enabled", true);
 pref("layout.imagevisibility.numscrollportwidths", 1);
 pref("layout.imagevisibility.numscrollportheights", 1);
 
 // Enable native identity (persona/browserid)
 pref("dom.identity.enabled", true);
 
 // Wait up to this much milliseconds when orientation changed
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4291,16 +4291,21 @@ pref("memory.low_memory_notification_int
 // How long must we wait before declaring that a window is a "ghost" (i.e., a
 // likely leak)?  This should be longer than it usually takes for an eligible
 // window to be collected via the GC/CC.
 pref("memory.ghost_window_timeout_seconds", 60);
 
 // Disable freeing dirty pages when minimizing memory.
 pref("memory.free_dirty_pages", false);
 
+// Disable the Linux-specific, system-wide memory reporter.
+#ifdef XP_LINUX
+pref("memory.system_memory_reporter", false);
+#endif
+
 pref("social.enabled", false);
 // comma separated list of domain origins (e.g. https://domain.com) for
 // providers that can install from their own website without user warnings.
 // entries are
 pref("social.whitelist", "https://mozsocial.cliqz.com,https://now.msn.com,https://mixi.jp");
 // omma separated list of domain origins (e.g. https://domain.com) for directory
 // websites (e.g. AMO) that can install providers for other sites
 pref("social.directories", "https://addons.mozilla.org");
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
@@ -63,20 +63,19 @@
   }
 
   // Load the given file into the frame, then copy+paste the entire frame and
   // check that the cut text matches what we expect.
   function test(aFilename, aFilename2, aExpected, aDumpFirst, aNext) {
     let frame = document.getElementById("amFrame");
     frame.focus();
 
-    let aIsVerbose = true;
     let doc = frame.contentWindow.document;
     let verbosity = doc.getElementById("verbose");
-    verbosity.checked = aIsVerbose;
+    verbosity.checked = true;
 
     function getFilePath(aFilename) {
       let file = Cc["@mozilla.org/file/directory_service;1"]
                  .getService(Components.interfaces.nsIProperties)
                  .get("CurWorkD", Components.interfaces.nsIFile);
       file.append("chrome");
       file.append("toolkit");
       file.append("components");
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
@@ -2,17 +2,20 @@
 <?xml-stylesheet type="text/css" href="chrome://global/skin"?>
 <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
 <window title="about:memory"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 
   <!-- This file tests the saving and loading of memory reports to/from file in
-       about:memory in the presence of child processes. -->
+       about:memory in the presence of child processes.  It is also notable
+       for being an about:memory test that uses the real reporters, rather
+       than fake deterministic ones, and so tends to show up problems in the
+       real reporters (like bogus negative values). -->
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml"></body>
 
   <iframe id="amFrame"  height="400" src="about:memory"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
@@ -24,17 +27,23 @@
             getService(Ci.nsIMemoryReporterManager);
 
   let numRemotes = 3;
   let numReady = 0;
 
   // Create some remote processes, and set up message-passing so that
   // we know when each child is fully initialized.
   let remotes = [];
-  SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 3]]}, function() {
+
+  let prefs = [
+    ["dom.ipc.processCount", 3],            // Allow up to 3 child processes
+    ["memory.system_memory_reporter", true] // Test SystemMemoryReporter
+  ];
+
+  SpecialPowers.pushPrefEnv({"set": prefs}, function() {
     for (let i = 0; i < numRemotes; i++) {
       let w = remotes[i] = window.open("remote.xul", "", "chrome");
 
       w.addEventListener("load", function loadHandler() {
         w.removeEventListener("load", loadHandler);
         let remoteBrowser = w.document.getElementById("remote");
         let mm = remoteBrowser.messageManager;
         mm.addMessageListener("test:ready", function readyHandler() {
@@ -99,24 +108,34 @@
         // differences.
         synthesizeKey("A", {accelKey: true});
         synthesizeKey("C", {accelKey: true});
         let actual = SpecialPowers.getClipboardData("text/unicode");
 
         // If we have more than 1000 chars, we've probably successfully
         // copy+pasted.
         if (actual.length > 1000) {
+
+          let good = true;
+
+          if (actual.match("End of System")) {
+            let m1 = actual.match("anonymous") &&
+                     actual.match("shared-libraries");
+            ok(m1, "system-wide reporter")
+            good = good && !!m1;
+          }
+
           // Note: Match "vsize" but not "vsize-max-contiguous".
           let vsizes = actual.match(/vsize[^-]/g);
           let endOfBrowsers  = actual.match(/End of Browser/g);
+          let m2 = (vsizes.length == 4 && endOfBrowsers.length == 3);
+          ok(m2, "three child processes present in loaded data");
+          good = good && !!m2;
 
-          if (vsizes.length == 4 && endOfBrowsers.length == 3) {
-            ok(true, "three child processes present in loaded data");
-          } else {
-            ok(false, "pasted text lacks four 'vsize' and three 'End of Browser' strings");
+          if (!good) {
             dump("*******ACTUAL*******\n");
             dump(actual);
             dump("********************\n");
           }
 
           // Close the remote processes.
           for (let i = 0; i < numRemotes; i++) {
             remotes[i].close();
new file mode 100644
--- /dev/null
+++ b/xpcom/base/SystemMemoryReporter.cpp
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SystemMemoryReporter.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/unused.h"
+
+#include "nsIMemoryReporter.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// This file implements a Linux-specific, system-wide memory reporter.  It
+// gathers all the useful memory measurements obtainable from the OS in a
+// single place, giving a high-level view of memory consumption for the entire
+// machine/device.
+//
+// Other memory reporters measure part of a single process's memory consumption.
+// This reporter is different in that it measures memory consumption of many
+// processes, and they end up in a single reports tree.  This is a slight abuse
+// of the memory reporting infrastructure, and therefore the results are given
+// their own "process" called "System", which means they show up in about:memory
+// in their own section, distinct from the per-process sections.
+
+namespace mozilla {
+namespace SystemMemoryReporter {
+
+#if !defined(XP_LINUX)
+#error "This won't work if we're not on Linux."
+#endif
+
+static bool
+EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle)
+{
+  int32_t idx = aHaystack.RFind(aNeedle);
+  return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length();
+}
+
+static void
+GetDirname(const nsCString& aPath, nsACString& aOut)
+{
+  int32_t idx = aPath.RFind("/");
+  if (idx == -1) {
+    aOut.Truncate();
+  } else {
+    aOut.Assign(Substring(aPath, 0, idx));
+  }
+}
+
+static void
+GetBasename(const nsCString& aPath, nsACString& aOut)
+{
+  nsCString out;
+  int32_t idx = aPath.RFind("/");
+  if (idx == -1) {
+    out.Assign(aPath);
+  } else {
+    out.Assign(Substring(aPath, idx + 1));
+  }
+
+  // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g.
+  // "/dev/ashmem/libxul.so(deleted)").  We don't care about this modifier, so
+  // cut it off when getting the entry's basename.
+  if (EndsWithLiteral(out, "(deleted)")) {
+    out.Assign(Substring(out, 0, out.RFind("(deleted)")));
+  }
+  out.StripChars(" ");
+
+  aOut.Assign(out);
+}
+
+static bool
+IsNumeric(const char* s)
+{
+  MOZ_ASSERT(*s);   // shouldn't see empty strings
+  while (*s) {
+    if (!isdigit(*s)) {
+      return false;
+    }
+    s++;
+  }
+  return true;
+}
+
+static bool
+IsAnonymous(const nsACString& aName)
+{
+  // Recent kernels (e.g. 3.5) have multiple [stack:nnnn] entries, where |nnnn|
+  // is a thread ID.  However, [stack:nnnn] entries count both stack memory
+  // *and* anonymous memory because the kernel only knows about the start of
+  // each thread stack, not its end.  So we treat such entries as anonymous
+  // memory instead of stack.  This is consistent with older kernels that don't
+  // even show [stack:nnnn] entries.
+  return aName.IsEmpty() ||
+         StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:"));
+}
+
+class SystemReporter MOZ_FINAL : public nsIMemoryReporter
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+#define REPORT(_path, _amount, _desc)                                         \
+  do {                                                                        \
+    size_t amount = _amount;  /* evaluate _amount only once */                \
+    if (amount > 0) {                                                         \
+      nsresult rv;                                                            \
+      rv = aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path,       \
+                                   KIND_NONHEAP, UNITS_BYTES, amount, _desc,  \
+                                   aData);                                    \
+      if (NS_WARN_IF(NS_FAILED(rv))) {                                        \
+        return rv;                                                            \
+      }                                                                       \
+    }                                                                         \
+  } while (0)
+
+  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+                            nsISupports* aData)
+  {
+    if (!Preferences::GetBool("memory.system_memory_reporter")) {
+      return NS_OK;
+    }
+
+    // Read relevant fields from /proc/meminfo.
+    int64_t memTotal = 0, memFree = 0;
+    nsresult rv = ReadMemInfo(&memTotal, &memFree);
+
+    // Collect per-process reports from /proc/<pid>/smaps.
+    int64_t totalPss = 0;
+    rv = CollectProcessReports(aHandleReport, aData, &totalPss);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Report the non-process numbers.
+    int64_t other = memTotal - memFree - totalPss;
+    REPORT(NS_LITERAL_CSTRING("mem/other"), other, NS_LITERAL_CSTRING(
+"Memory which is neither owned by any user-space process nor free. Note that "
+"this includes memory holding cached files from the disk which can be "
+"reclaimed by the OS at any time."));
+
+    REPORT(NS_LITERAL_CSTRING("mem/free"), memFree, NS_LITERAL_CSTRING(
+"Memory which is free and not being used for any purpose."));
+
+    return NS_OK;
+  }
+
+private:
+  // Keep this in sync with SystemReporter::kindPathSuffixes!
+  enum ProcessSizeKind {
+    AnonymousOutsideBrk  = 0,
+    AnonymousBrkHeap     = 1,
+    SharedLibrariesRX    = 2,
+    SharedLibrariesRW    = 3,
+    SharedLibrariesR     = 4,
+    SharedLibrariesOther = 5,
+    OtherFiles           = 6,
+    MainThreadStack      = 7,
+    Vdso                 = 8,
+
+    ProcessSizeKindLimit = 9  // must be last
+  };
+
+  static const char* kindPathSuffixes[ProcessSizeKindLimit];
+
+  // These are the cross-cutting measurements across all processes.
+  struct ProcessSizes
+  {
+    ProcessSizes() { memset(this, 0, sizeof(*this)); }
+
+    size_t mSizes[ProcessSizeKindLimit];
+  };
+
+  nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree)
+  {
+    FILE* f = fopen("/proc/meminfo", "r");
+    if (!f) {
+      return NS_ERROR_FAILURE;
+    }
+
+    int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal);
+    int n2 = fscanf(f, "MemFree: %"  SCNd64 " kB\n", aMemFree);
+
+    fclose(f);
+
+    if (n1 != 1 || n2 != 1) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // Convert from KB to B.
+    *aMemTotal *= 1024;
+    *aMemFree  *= 1024;
+
+    return NS_OK;
+  }
+
+  nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport,
+                                 nsISupports* aData,
+                                 int64_t* aTotalPss)
+  {
+    *aTotalPss = 0;
+    ProcessSizes processSizes;
+
+    DIR* d = opendir("/proc");
+    if (NS_WARN_IF(!d)) {
+      return NS_ERROR_FAILURE;
+    }
+    struct dirent* ent;
+    while ((ent = readdir(d))) {
+      struct stat statbuf;
+      const char* pidStr = ent->d_name;
+      // Don't check the return value of stat() -- it can return -1 for these
+      // directories even when it has succeeded, apparently.
+      stat(pidStr, &statbuf);
+      if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) {
+        nsCString processName("process(");
+
+        // Get the command name from cmdline.  If that fails, the pid is still
+        // shown.
+        nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr);
+        FILE* f = fopen(cmdlinePath.get(), "r");
+        if (f) {
+          static const size_t len = 256;
+          char buf[len];
+          if (fgets(buf, len, f)) {
+            processName.Append(buf);
+            // A hack: replace forward slashes with '\\' so they aren't treated
+            // as path separators.  Consumers of this reporter (such as
+            // about:memory) have to undo this change.
+            processName.ReplaceChar('/', '\\');
+            processName.Append(", ");
+          }
+          fclose(f);
+        }
+        processName.Append("pid=");
+        processName.Append(pidStr);
+        processName.Append(")");
+
+        // Read the PSS values from the smaps file.
+        nsPrintfCString smapsPath("/proc/%s/smaps", pidStr);
+        f = fopen(smapsPath.get(), "r");
+        if (!f) {
+          // Processes can terminate between the readdir() call above and now,
+          // so just skip if we can't open the file.
+          continue;
+        }
+        while (true) {
+          nsresult rv = ParseMapping(f, processName, aHandleReport, aData,
+                                     &processSizes, aTotalPss);
+          if (NS_FAILED(rv))
+            break;
+        }
+        fclose(f);
+      }
+    }
+    closedir(d);
+
+    // Report the "processes/" tree.
+
+    for (size_t i = 0; i < ProcessSizeKindLimit; i++) {
+      nsAutoCString path("processes/");
+      path.Append(kindPathSuffixes[i]);
+
+      nsAutoCString desc("This is the sum of all processes' '");
+      desc.Append(kindPathSuffixes[i]);
+      desc.Append("' numbers.");
+
+      REPORT(path, processSizes.mSizes[i], desc);
+    }
+
+    return NS_OK;
+  }
+
+  nsresult ParseMapping(FILE* aFile,
+                        const nsACString& aProcessName,
+                        nsIHandleReportCallback* aHandleReport,
+                        nsISupports* aData,
+                        ProcessSizes* aProcessSizes,
+                        int64_t* aTotalPss)
+  {
+    // The first line of an entry in /proc/<pid>/smaps looks just like an entry
+    // in /proc/<pid>/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;
+    // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and
+    // 20 bits for the minor device number.  Future kernels might allocate more.
+    // 64 bits ought to be enough for anybody.
+    char devMajor[17];
+    char devMinor[17];
+    unsigned int 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 n = fscanf(aFile,
+                   "%llx-%llx %4s %llx "
+                   "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u%1024[^\n]",
+                   &addrStart, &addrEnd, perms, &offset, devMajor,
+                   devMinor, &inode, path);
+
+    // Eat up any whitespace at the end of this line, including the newline.
+    unused << fscanf(aFile, " ");
+
+    // We might or might not have a path, but the rest of the arguments should
+    // be there.
+    if (n != argCount && n != argCount - 1) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsAutoCString name, description;
+    ProcessSizeKind kind;
+    GetReporterNameAndDescription(path, perms, name, description, &kind);
+
+    while (true) {
+      size_t pss;
+      nsresult rv = ParseMapBody(aFile, aProcessName, name, description,
+                                 aHandleReport, aData, &pss);
+      if (NS_FAILED(rv))
+        break;
+
+      // Increment the appropriate aProcessSizes values, and the total.
+      aProcessSizes->mSizes[kind] += pss;
+      *aTotalPss += pss;
+    }
+
+    return NS_OK;
+  }
+
+  void GetReporterNameAndDescription(const char* aPath,
+                                     const char* aPerms,
+                                     nsACString& aName,
+                                     nsACString& aDesc,
+                                     ProcessSizeKind* aProcessSizeKind)
+  {
+    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.
+    nsAutoCString absPath;
+    absPath.Append(aPath);
+    absPath.StripChars(" ");
+
+    nsAutoCString basename;
+    GetBasename(absPath, basename);
+
+    if (basename.EqualsLiteral("[heap]")) {
+      aName.Append("anonymous/brk-heap");
+      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 corresponds to "
+                   "'[heap]' in /proc/<pid>/smaps.");
+      *aProcessSizeKind = AnonymousBrkHeap;
+
+    } else if (basename.EqualsLiteral("[stack]")) {
+      aName.Append("main-thread-stack");
+      aDesc.Append("The stack size of the process's main thread.  This "
+                   "corresponds to '[stack]' in /proc/<pid>/smaps.");
+      *aProcessSizeKind = MainThreadStack;
+
+    } 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.");
+      *aProcessSizeKind = Vdso;
+
+    } else if (!IsAnonymous(basename)) {
+      nsAutoCString 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 (strncmp(aPerms, "r-x", 3) == 0) {
+          *aProcessSizeKind = SharedLibrariesRX;
+        } else if (strncmp(aPerms, "rw-", 3) == 0) {
+          *aProcessSizeKind = SharedLibrariesRW;
+        } else if (strncmp(aPerms, "r--", 3) == 0) {
+          *aProcessSizeKind = SharedLibrariesR;
+        } else {
+          *aProcessSizeKind = SharedLibrariesOther;
+        }
+
+      } else {
+        aName.Append("other-files/");
+        if (EndsWithLiteral(basename, ".xpi")) {
+          aName.Append("extensions/");
+        } else if (dirname.Find("/fontconfig") != -1) {
+          aName.Append("fontconfig/");
+        }
+        *aProcessSizeKind = OtherFiles;
+      }
+
+      aName.Append(basename);
+      aDesc.Append(absPath);
+
+    } else {
+      aName.Append("anonymous/outside-brk");
+      aDesc.Append("Memory in anonymous mappings outside the boundaries "
+                   "defined by brk() / sbrk().");
+      *aProcessSizeKind = AnonymousOutsideBrk;
+    }
+
+    aName.Append("/[");
+    aName.Append(aPerms);
+    aName.Append("]");
+
+    // Append the permissions.  This is useful for non-verbose mode in
+    // about:memory when the filename is long and goes of the right side of the
+    // window.
+    aDesc.Append(" [");
+    aDesc.Append(aPerms);
+    aDesc.Append("]");
+  }
+
+  nsresult ParseMapBody(
+    FILE* aFile,
+    const nsACString& aProcessName,
+    const nsACString& aName,
+    const nsACString& aDescription,
+    nsIHandleReportCallback* aHandleReport,
+    nsISupports* aData,
+    size_t* aPss)
+  {
+    // Most of the lines in the body look like this:
+    //
+    // Size:                132 kB
+    // Rss:                  20 kB
+    // Pss:                  20 kB
+    //
+    // We're only interested in Pss.  In newer kernels, the last line in the
+    // body has a different form:
+    //
+    // VmFlags: rd wr mr mw me dw ac
+    //
+    // The strings after "VmFlags: " vary.
+
+    char desc[1025];
+    int64_t sizeKB;
+    int n = fscanf(aFile, "%1024[a-zA-Z_]: %" SCNd64 " kB\n", desc, &sizeKB);
+    if (n == EOF || n == 0) {
+      return NS_ERROR_FAILURE;
+    } else if (n == 1 && strcmp(desc, "VmFlags") == 0) {
+      // This is the "VmFlags:" line.  Chew up the rest of it.
+      fscanf(aFile, "%*1024[a-z ]\n");
+      return NS_ERROR_FAILURE;
+    }
+
+    // Only report "Pss" values.
+    if (strcmp(desc, "Pss") == 0) {
+      *aPss = sizeKB * 1024;
+
+      // Don't report zero values.
+      if (*aPss == 0) {
+        return NS_OK;
+      }
+
+      nsAutoCString path("mem/processes/");
+      path.Append(aProcessName);
+      path.Append("/");
+      path.Append(aName);
+
+      REPORT(path, *aPss, aDescription);
+    } else {
+      *aPss = 0;
+    }
+
+    return NS_OK;
+  }
+
+#undef REPORT
+};
+
+NS_IMPL_ISUPPORTS1(SystemReporter, nsIMemoryReporter)
+
+// Keep this in sync with SystemReporter::ProcessSizeKind!
+const char* SystemReporter::kindPathSuffixes[] = {
+    "anonymous/outside-brk",
+    "anonymous/brk-heap",
+    "shared-libraries/read-executable",
+    "shared-libraries/read-write",
+    "shared-libraries/read-only",
+    "shared-libraries/other",
+    "other-files",
+    "main-thread-stack",
+    "vdso"
+};
+
+void Init()
+{
+  RegisterStrongMemoryReporter(new SystemReporter());
+}
+
+} // namespace SystemMemoryReporter
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/base/SystemMemoryReporter.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SystemMemoryReporter_h_
+#define mozilla_SystemMemoryReporter_h_
+
+namespace mozilla {
+namespace SystemMemoryReporter {
+
+// 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 SystemMemoryReporter
+} // namespace mozilla
+
+#endif
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -73,16 +73,17 @@ EXPORTS.mozilla += [
     'AvailableMemoryTracker.h',
     'ClearOnShutdown.h',
     'CycleCollectedJSRuntime.h',
     'Debug.h',
     'nsMemoryInfoDumper.h',
     'StackWalk.h',
     'StaticMutex.h',
     'StaticPtr.h',
+    'SystemMemoryReporter.h',
     'VisualEventTracer.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla += [
         'WindowsVersion.h',
     ]
 
@@ -112,16 +113,21 @@ UNIFIED_SOURCES += [
     'nsStackWalk.cpp',
     'nsSystemInfo.cpp',
     'nsTraceRefcntImpl.cpp',
     'nsUUIDGenerator.cpp',
     'nsVersionComparatorImpl.cpp',
     'VisualEventTracer.cpp',
 ]
 
+if CONFIG['OS_ARCH'] == 'Linux':
+    SOURCES += [
+        'SystemMemoryReporter.cpp',
+    ]
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     SOURCES += [
         'nsMacUtilsImpl.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     SOURCES += [
         'nsCrashOnException.cpp',
     ]
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -118,16 +118,17 @@ extern nsresult nsStringInputStreamConst
 
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/AvailableMemoryTracker.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SystemMemoryReporter.h"
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
 #include "mozilla/VisualEventTracer.h"
 #endif
 
 #include "GeckoProfiler.h"
 
 #include "jsapi.h"
@@ -576,16 +577,23 @@ NS_InitXPCOM2(nsIServiceManager* *result
     // Notify observers of xpcom autoregistration start
     NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY,
                                   nullptr,
                                   NS_XPCOM_STARTUP_OBSERVER_ID);
 #ifdef XP_WIN
     CreateAnonTempFileRemover();
 #endif
 
+    // We only want the SystemMemoryReporter running in one process, because it
+    // profiles the entire system.  The main process is the obvious place for
+    // it.
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+        mozilla::SystemMemoryReporter::Init();
+    }
+
     // The memory reporter manager is up and running -- register a reporter for
     // ICU's memory usage.
     RegisterStrongMemoryReporter(new ICUReporter());
 
     mozilla::Telemetry::Init();
 
     mozilla::HangMonitor::Startup();
     mozilla::BackgroundHangMonitor::Startup();