Bug 788021 - Part 2: Add and implement nsIMemoryReporter::DumpMemoryReportsToFile. r=njn,cjones
authorJustin Lebar <justin.lebar@gmail.com>
Tue, 02 Oct 2012 21:19:11 -0400
changeset 109084 deb340fb0b07cd36090f245080ada96d76fee869
parent 109083 bab50a5494bbeb876221eed7a93f5a166130f4ea
child 109085 a67a2a9abebb6b071b271b8086a71d40c28f5c4a
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersnjn, cjones
bugs788021
milestone18.0a1
Bug 788021 - Part 2: Add and implement nsIMemoryReporter::DumpMemoryReportsToFile. r=njn,cjones
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
xpcom/base/nsMemoryReporterManager.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -423,16 +423,30 @@ ContentChild::RecvPMemoryReportRequestCo
 
 bool
 ContentChild::DeallocPMemoryReportRequest(PMemoryReportRequestChild* actor)
 {
     delete actor;
     return true;
 }
 
+bool
+ContentChild::RecvDumpMemoryReportsToFile(const nsString& aIdentifier,
+                                          const bool& aMinimizeMemoryUsage,
+                                          const bool& aDumpChildProcesses)
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr =
+        do_GetService("@mozilla.org/memory-reporter-manager;1");
+    NS_ENSURE_TRUE(mgr, true);
+    mgr->DumpMemoryReportsToFile(aIdentifier,
+                                 aMinimizeMemoryUsage,
+                                 aDumpChildProcesses);
+    return true;
+}
+
 PCompositorChild*
 ContentChild::AllocPCompositor(mozilla::ipc::Transport* aTransport,
                                base::ProcessId aOtherProcess)
 {
     return CompositorChild::Create(aTransport, aOtherProcess);
 }
 
 PImageBridgeChild*
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -103,16 +103,21 @@ public:
     AllocPMemoryReportRequest();
 
     virtual bool
     DeallocPMemoryReportRequest(PMemoryReportRequestChild* actor);
 
     virtual bool
     RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child);
 
+    virtual bool
+    RecvDumpMemoryReportsToFile(const nsString& identifier,
+                                const bool& aMinimizeMemoryUsage,
+                                const bool& aDumpChildProcesses);
+
     virtual PTestShellChild* AllocPTestShell();
     virtual bool DeallocPTestShell(PTestShellChild*);
     virtual bool RecvPTestShellConstructor(PTestShellChild*);
 
     virtual PAudioChild* AllocPAudio(const int32_t&,
                                      const int32_t&,
                                      const int32_t&);
     virtual bool DeallocPAudio(PAudioChild*);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -193,16 +193,26 @@ both:
     // PBrowser as its |AppId|.
     async PBrowser(uint32_t chromeFlags, bool isBrowserElement, AppId appId);
 
     async PBlob(BlobConstructorParams params);
 
 child:
     PMemoryReportRequest();
 
+    /**
+     * Dump the contents of about:memory to a file in our temp directory.
+     *
+     * For documentation on the args, see
+     * nsIMemoryReporterManager::dumpMemoryReportsToFile.
+     */
+    async DumpMemoryReportsToFile(nsString identifier,
+                                  bool minimizeMemoryUsage,
+                                  bool dumpChildProcesses);
+
     PTestShell();
 
     RegisterChrome(ChromePackage[] packages, ResourceMapping[] resources,
                    OverrideMapping[] overrides, nsCString locale);
 
     async SetOffline(bool offline);
 
     async NotifyVisited(URIParams uri);
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "nsISupports.idl"
 
 interface nsISimpleEnumerator;
+interface nsIRunnable;
 
 /*
  * Memory reporters measure Firefox's memory usage.  They are mainly used to
  * generate the about:memory page.  You should read
  * https://wiki.mozilla.org/Memory_Reporting before writing a memory
  * reporter.
  */
 
@@ -220,17 +221,17 @@ interface nsIMemoryMultiReporter : nsISu
    * nsIMemoryReporterManager::explicit efficiently, which is important --
    * multi-reporters can special-case this operation so it's much faster
    * than getting all the reports, filtering out the unneeded ones, and
    * summing the remainder.
    */
   readonly attribute int64_t explicitNonHeap;
 };
 
-[scriptable, uuid(46a09443-ec1d-4aa8-ae40-28642f138a04)]
+[scriptable, uuid(7aa2fcfc-eb8f-4bc8-95cc-2bc5e3374df7)]
 interface nsIMemoryReporterManager : nsISupports
 {
   /*
    * Return an enumerator of nsIMemoryReporters that are currently registered.
    */
   nsISimpleEnumerator enumerateReporters ();
 
   /*
@@ -288,19 +289,46 @@ interface nsIMemoryReporterManager : nsI
   readonly attribute int64_t explicit;
 
   /*
    * This attribute indicates if moz_malloc_usable_size() works.
    */
   readonly attribute boolean hasMozMallocUsableSize;
 
   /*
-   * This dumps the memory reports for this process to a file in the tmp
-   * directory called memory-reports-<pid>.json (or something similar, such as
-   * memory-reports-<pid>-1.json;  no existing file will be overwritten).
+   * Run a series of GC/CC's in an attempt to minimize the application's memory
+   * usage.  When we're finished, we invoke the given runnable.
+   */
+  void minimizeMemoryUsage(in nsIRunnable callback);
+
+  /*
+   * This dumps the memory reports for this process and possibly all our child
+   * processes (and all their children, recursively) to a file in the tmp
+   * directory called memory-reports-<identifier>-<pid>.json.gz (or something
+   * similar, such as memory-reports-<identifier>-<pid>-1.json.gz;  no existing
+   * file will be overwritten).
+   *
+   * @param identifier this identifier will appear in the filename of our
+   *   about:memory dump and those of our children (if dumpChildProcesses is
+   *   true).
+   *
+   *   If the identifier is empty, the dumpMemoryReportsToFile implementation
+   *   may set it arbitrarily and use that new value for its own dump and the
+   *   dumps of its child processes.  For example, the dumpMemoryReportsToFile
+   *   implementation may set |identifier| to the number of seconds since the
+   *   epoch.
+   *
+   * @param minimizeMemoryUsage indicates whether we should run a series of
+   *   gc/cc's in an attempt to reduce our memory usage before collecting our
+   *   memory report.
+   *
+   * @param dumpChildProcesses indicates whether we should call
+   *   dumpMemoryReportsToFile in our child processes.  If so, the child
+   *   processes will also dump their children, and so on.
+   *
    *
    * Sample output:
    *
    * {
    *   "hasMozMallocUsableSize":true,
    *   "reports": [
    *     {"process":"", "path":"explicit/foo/bar", "kind":1, "units":0,
    *      "amount":2000000, "description":"Foo bar."},
@@ -360,17 +388,19 @@ interface nsIMemoryReporterManager : nsI
    *             "required": true
    *           }
    *         }
    *       }
    *     }
    *   }
    * }
    */
-  void dumpReports ();
+  void dumpMemoryReportsToFile (in AString identifier,
+                                in bool minimizeMemoryUsage,
+                                in bool dumpChildProcesses);
 };
 
 %{C++
 
 /*
  * Note that this defaults 'process' to "", which is usually what's desired.
  */
 #define NS_MEMORY_REPORTER_IMPLEMENT_HELPER(_classname, _path, _kind, _units, _amountFunction, _desc, _ts) \
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -11,27 +11,39 @@
 #include "nsServiceManagerUtils.h"
 #include "nsMemoryReporterManager.h"
 #include "nsArrayEnumerator.h"
 #include "nsIConsoleService.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIFile.h"
 #include "nsIFileStreams.h"
 #include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsThread.h"
+#include "nsGZFileWriter.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/unused.h"
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
 #else
 #include <unistd.h>
+#include <fcntl.h>
 #endif
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 #if defined(MOZ_MEMORY)
 #  define HAVE_JEMALLOC_STATS 1
 #  include "jemalloc.h"
 #endif  // MOZ_MEMORY
 
 #ifdef XP_UNIX
 
@@ -621,16 +633,211 @@ NS_MEMORY_REPORTER_IMPLEMENT(AtomTable,
     "Memory used by the dynamic and static atoms tables.")
 
 /**
  ** nsMemoryReporterManager implementation
  **/
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsMemoryReporterManager, nsIMemoryReporterManager)
 
+namespace {
+
+class DumpMemoryReportsRunnable : public nsRunnable
+{
+public:
+  DumpMemoryReportsRunnable(const nsAString& aIdentifier,
+                            bool aMinimizeMemoryUsage,
+                            bool aDumpChildProcesses)
+
+      : mIdentifier(aIdentifier)
+      , mMinimizeMemoryUsage(aMinimizeMemoryUsage)
+      , mDumpChildProcesses(aDumpChildProcesses)
+  {}
+
+  NS_IMETHOD Run()
+  {
+      nsCOMPtr<nsIMemoryReporterManager> mgr =
+          do_GetService("@mozilla.org/memory-reporter-manager;1");
+      NS_ENSURE_STATE(mgr);
+      mgr->DumpMemoryReportsToFile(mIdentifier,
+                                   mMinimizeMemoryUsage,
+                                   mDumpChildProcesses);
+      return NS_OK;
+  }
+
+private:
+  const nsString mIdentifier;
+  const bool mMinimizeMemoryUsage;
+  const bool mDumpChildProcesses;
+};
+
+} // anonymous namespace
+
+#ifdef XP_LINUX // {
+namespace {
+
+/*
+ * The following code supports dumping about:memory upon receiving a signal.
+ *
+ * We listen for the signals SIGRTMIN and SIGRTMIN +1.  (The latter causes us
+ * to minimize memory usage before dumping about:memory.)
+ *
+ * When we receive one of these signals, we write the signal number to a pipe.
+ * The IO thread then notices that the pipe has been written to, and kicks off
+ * a DumpMemoryReports task on the main thread.
+ *
+ * This scheme is similar to using signalfd(), except it's portable and it
+ * doesn't require the use of sigprocmask, which is problematic because it
+ * masks signals received by child processes.
+ *
+ * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
+ * But that uses libevent, which does not handle the realtime signals (bug
+ * 794074).
+ */
+
+// It turns out that at least on some systems, SIGRTMIN is not a compile-time
+// constant, so these have to be set at runtime.
+static int sDumpAboutMemorySignum;         // SIGRTMIN
+static int sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1
+
+// This is the write-end of a pipe that we use to notice when a
+// dump-about-memory signal occurs.
+static int sDumpAboutMemoryPipeWriteFd;
+
+void
+DumpAboutMemorySignalHandler(int aSignum)
+{
+  // This is a signal handler, so everything in here needs to be
+  // async-signal-safe.  Be careful!
+
+  if (sDumpAboutMemoryPipeWriteFd != 0) {
+    uint8_t signum = static_cast<int>(aSignum);
+    write(sDumpAboutMemoryPipeWriteFd, &signum, sizeof(signum));
+  }
+}
+
+class SignalPipeWatcher : public MessageLoopForIO::Watcher
+{
+public:
+  SignalPipeWatcher()
+  {}
+
+  ~SignalPipeWatcher()
+  {
+    // This is somewhat paranoid, but we want to avoid the race condition where
+    // we close sDumpAboutMemoryPipeWriteFd before setting it to 0, then we
+    // reuse that fd for some other file, and then the signal handler runs.
+    int pipeWriteFd = sDumpAboutMemoryPipeWriteFd;
+    PR_ATOMIC_SET(&sDumpAboutMemoryPipeWriteFd, 0);
+
+    // Stop watching the pipe's file descriptor /before/ we close it!
+    mReadWatcher.StopWatchingFileDescriptor();
+
+    close(pipeWriteFd);
+    close(mPipeReadFd);
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SignalPipeWatcher)
+
+  bool Start()
+  {
+    MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+    sDumpAboutMemorySignum = SIGRTMIN;
+    sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
+
+    // Create a pipe.  When we receive a signal in our signal handler, we'll
+    // write the signum to the write-end of this pipe.
+    int pipeFds[2];
+    if (pipe(pipeFds)) {
+        NS_WARNING("Failed to create pipe.");
+        return false;
+    }
+
+    // Close this pipe on calls to exec().
+    fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
+    fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC);
+
+    mPipeReadFd = pipeFds[0];
+    sDumpAboutMemoryPipeWriteFd = pipeFds[1];
+
+    struct sigaction action;
+    memset(&action, 0, sizeof(action));
+    sigemptyset(&action.sa_mask);
+    action.sa_handler = DumpAboutMemorySignalHandler;
+
+    if (sigaction(sDumpAboutMemorySignum, &action, nullptr)) {
+      NS_WARNING("Failed to register about:memory dump signal handler.");
+    }
+    if (sigaction(sDumpAboutMemoryAfterMMUSignum, &action, nullptr)) {
+      NS_WARNING("Failed to register about:memory dump after MMU signal handler.");
+    }
+
+    // Start watching the read end of the pipe on the IO thread.
+    return MessageLoopForIO::current()->WatchFileDescriptor(
+        mPipeReadFd, /* persistent = */ true,
+        MessageLoopForIO::WATCH_READ,
+        &mReadWatcher, this);
+  }
+
+  virtual void OnFileCanReadWithoutBlocking(int aFd)
+  {
+    MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+    uint8_t signum;
+    ssize_t numReceived = read(aFd, &signum, sizeof(signum));
+    if (numReceived != sizeof(signum)) {
+      NS_WARNING("Error reading from buffer in "
+                 "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
+      return;
+    }
+
+    if (signum != sDumpAboutMemorySignum &&
+        signum != sDumpAboutMemoryAfterMMUSignum) {
+      NS_WARNING("Got unexpected signum.");
+      return;
+    }
+
+    // Dump about:memory (but run this on the main thread!).
+    nsRefPtr<DumpMemoryReportsRunnable> runnable =
+      new DumpMemoryReportsRunnable(
+          /* identifier = */ EmptyString(),
+          signum == sDumpAboutMemoryAfterMMUSignum,
+          /* dumpChildProcesses = */ true);
+    NS_DispatchToMainThread(runnable);
+  }
+
+  virtual void OnFileCanWriteWithoutBlocking(int aFd)
+  {}
+
+private:
+  int mPipeReadFd;
+  MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
+};
+
+StaticRefPtr<SignalPipeWatcher> sSignalPipeWatcher;
+
+void
+InitializeDumpAboutMemoryWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!sSignalPipeWatcher);
+
+  sSignalPipeWatcher = new SignalPipeWatcher();
+  ClearOnShutdown(&sSignalPipeWatcher);
+
+  XRE_GetIOMessageLoop()->PostTask(
+      FROM_HERE,
+      NewRunnableMethod(sSignalPipeWatcher.get(),
+                        &SignalPipeWatcher::Start));
+}
+
+} // anonymous namespace
+#endif // } XP_LINUX
+
 NS_IMETHODIMP
 nsMemoryReporterManager::Init()
 {
 #if HAVE_JEMALLOC_STATS && defined(XP_LINUX)
     if (!jemalloc_stats)
         return NS_ERROR_FAILURE;
 #endif
 
@@ -666,16 +873,20 @@ nsMemoryReporterManager::Init()
     REGISTER(HeapDirty);
 #elif defined(HAVE_HEAP_ZONE0_REPORTERS)
     REGISTER(HeapZone0Committed);
     REGISTER(HeapZone0Used);
 #endif
 
     REGISTER(AtomTable);
 
+#if defined(XP_LINUX)
+    InitializeDumpAboutMemoryWatcher();
+#endif
+
     return NS_OK;
 }
 
 nsMemoryReporterManager::nsMemoryReporterManager()
   : mMutex("nsMemoryReporterManager::mMutex")
 {
 }
 
@@ -895,22 +1106,20 @@ nsMemoryReporterManager::GetExplicit(int
       r->CollectReports(cb, wrappedExplicitNonHeapMultiSize2);
     }
     int64_t explicitNonHeapMultiSize2 = wrappedExplicitNonHeapMultiSize2->mValue;
 
     // Check the two measurements give the same result.  This was an
     // NS_ASSERTION but they occasionally don't match due to races (bug
     // 728990).
     if (explicitNonHeapMultiSize != explicitNonHeapMultiSize2) {
-        char *msg = PR_smprintf("The two measurements of 'explicit' memory "
-                                "usage don't match (%lld vs %lld)",
-                                explicitNonHeapMultiSize,
-                                explicitNonHeapMultiSize2);
-        NS_WARNING(msg);
-        PR_smprintf_free(msg);
+        NS_WARNING(nsPrintfCString("The two measurements of 'explicit' memory "
+                                   "usage don't match (%lld vs %lld)",
+                                   explicitNonHeapMultiSize,
+                                   explicitNonHeapMultiSize2).get());
     }
 #endif  // DEBUG
 
     *aExplicit = heapAllocated + explicitNonHeapNormalSize + explicitNonHeapMultiSize;
     return NS_OK;
 #endif // HAVE_HEAP_ALLOCATED_AND_EXPLICIT_REPORTERS
 }
 
@@ -922,131 +1131,187 @@ nsMemoryReporterManager::GetHasMozMalloc
         return NS_ERROR_OUT_OF_MEMORY;
     }
     size_t usable = moz_malloc_usable_size(p);
     free(p);
     *aHas = !!(usable > 0);
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsMemoryReporterManager::DumpMemoryReportsToFile(
+    const nsAString& aIdentifier,
+    bool aMinimizeMemoryUsage,
+    bool aDumpChildProcesses)
+{
+    // If the identifier is empty, set it to the number of whole seconds since
+    // the epoch.  This identifier will appear in our memory report as well as
+    // our children's, allowing us to identify which files are from the same
+    // memory report request.
+    nsString identifier(aIdentifier);
+    if (identifier.IsEmpty()) {
+        identifier.AppendInt(PR_Now() / 1000000);
+    }
+
+    // Kick off memory report dumps in our child processes, if applicable.  We
+    // do this before doing our own report because writing a report may be I/O
+    // bound, in which case we want to busy the CPU with other reports while we
+    // work on our own.
+    if (aDumpChildProcesses) {
+        nsTArray<ContentParent*> children;
+        ContentParent::GetAll(children);
+        for (uint32_t i = 0; i < children.Length(); i++) {
+            unused << children[i]->SendDumpMemoryReportsToFile(
+                identifier, aMinimizeMemoryUsage, aDumpChildProcesses);
+        }
+    }
+
+    if (aMinimizeMemoryUsage) {
+        // Minimize memory usage, then run DumpMemoryReportsToFile again.
+        nsRefPtr<DumpMemoryReportsRunnable> callback =
+            new DumpMemoryReportsRunnable(identifier,
+                                          /* minimizeMemoryUsage = */ false,
+                                          /* dumpChildProcesses = */ false);
+        return MinimizeMemoryUsage(callback);
+    }
+
+    return DumpMemoryReportsToFileImpl(identifier);
+}
+
 #define DUMP(o, s) \
     do { \
-        const char* s2 = (s); \
-        uint32_t dummy; \
-        nsresult rv = (o)->Write((s2), strlen(s2), &dummy); \
+        nsresult rv = (o)->Write(s); \
         NS_ENSURE_SUCCESS(rv, rv); \
     } while (0)
 
 static nsresult
-DumpReport(nsIFileOutputStream *aOStream, bool isFirst,
+DumpReport(nsIGZFileWriter *aWriter, bool aIsFirst,
            const nsACString &aProcess, const nsACString &aPath, int32_t aKind,
            int32_t aUnits, int64_t aAmount, const nsACString &aDescription)
 {
-    DUMP(aOStream, isFirst ? "[" : ",");
+    DUMP(aWriter, aIsFirst ? "[" : ",");
 
     // We only want to dump reports for this process.  If |aProcess| is
     // non-NULL that means we've received it from another process in response
     // to a "child-memory-reporter-request" event;  ignore such reports.
     if (!aProcess.IsEmpty()) {
-        return NS_OK;    
+        return NS_OK;
     }
 
     unsigned pid = getpid();
     nsPrintfCString pidStr("Process %u", pid);
-    DUMP(aOStream, "\n    {\"process\": \"");
-    DUMP(aOStream, pidStr.get());
+    DUMP(aWriter, "\n    {\"process\": \"");
+    DUMP(aWriter, pidStr);
 
-    DUMP(aOStream, "\", \"path\": \"");
+    DUMP(aWriter, "\", \"path\": \"");
     nsCString path(aPath);
     path.ReplaceSubstring("\\", "\\\\");    // escape backslashes for JSON
-    DUMP(aOStream, path.get());
+    DUMP(aWriter, path);
 
-    DUMP(aOStream, "\", \"kind\": ");
-    DUMP(aOStream, nsPrintfCString("%d", aKind).get());
+    DUMP(aWriter, "\", \"kind\": ");
+    DUMP(aWriter, nsPrintfCString("%d", aKind));
 
-    DUMP(aOStream, ", \"units\": ");
-    DUMP(aOStream, nsPrintfCString("%d", aUnits).get());
+    DUMP(aWriter, ", \"units\": ");
+    DUMP(aWriter, nsPrintfCString("%d", aUnits));
 
-    DUMP(aOStream, ", \"amount\": ");
-    DUMP(aOStream, nsPrintfCString("%lld", aAmount).get());
+    DUMP(aWriter, ", \"amount\": ");
+    DUMP(aWriter, nsPrintfCString("%lld", aAmount));
 
     nsCString description(aDescription);
     description.ReplaceSubstring("\\", "\\\\");    /* <backslash> --> \\ */
     description.ReplaceSubstring("\"", "\\\"");    // " --> \"
     description.ReplaceSubstring("\n", "\\n");     // <newline> --> \n
-    DUMP(aOStream, ", \"description\": \"");
-    DUMP(aOStream, description.get());
-    DUMP(aOStream, "\"}");
+    DUMP(aWriter, ", \"description\": \"");
+    DUMP(aWriter, description);
+    DUMP(aWriter, "\"}");
 
     return NS_OK;
 }
 
 class DumpMultiReporterCallback MOZ_FINAL : public nsIMemoryMultiReporterCallback
 {
 public:
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
                         int32_t aKind, int32_t aUnits, int64_t aAmount,
                         const nsACString &aDescription,
                         nsISupports *aData)
     {
-        nsCOMPtr<nsIFileOutputStream> ostream = do_QueryInterface(aData);
-        if (!ostream)
-            return NS_ERROR_FAILURE;
+        nsCOMPtr<nsIGZFileWriter> writer = do_QueryInterface(aData);
+        NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE);
 
         // The |isFirst = false| assumes that at least one single reporter is
-        // present and so will have been processed in DumpReports() below.
-        return DumpReport(ostream, /* isFirst = */ false, aProcess, aPath,
+        // present and so will have been processed in
+        // DumpMemoryReportsToFileImpl() below.
+        return DumpReport(writer, /* isFirst = */ false, aProcess, aPath,
                           aKind, aUnits, aAmount, aDescription);
+        return NS_OK;
     }
 };
 
 NS_IMPL_ISUPPORTS1(
   DumpMultiReporterCallback
 , nsIMemoryMultiReporterCallback
 )
 
-NS_IMETHODIMP
-nsMemoryReporterManager::DumpReports()
+nsresult
+nsMemoryReporterManager::DumpMemoryReportsToFileImpl(
+    const nsAString& aIdentifier)
 {
-    // Open a file in NS_OS_TEMP_DIR for writing.
+    // Open a new file named something like
+    //
+    //   incomplete-memory-report-<-identifier>-<pid>-42.json.gz
+    //
+    // in NS_OS_TEMP_DIR for writing.  When we're finished writing the report,
+    // we'll rename this file and get rid of the "incomplete-" prefix.
+    //
+    // We do this because we don't want scripts which poll the filesystem
+    // looking for memory report dumps to grab a file before we're finished
+    // writing to it.
 
     nsCOMPtr<nsIFile> tmpFile;
     nsresult rv =
         NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
     NS_ENSURE_SUCCESS(rv, rv);
-   
-    // Basic filename form: "memory-reports-<pid>.json".
-    nsCString filename("memory-reports-");
+
+    // Note that |filename| is missing the "incomplete-" prefix; we'll tack
+    // that on in a moment.
+    nsAutoCString filename;
+    filename.AppendLiteral("memory-report");
+    if (!aIdentifier.IsEmpty()) {
+        filename.AppendLiteral("-");
+        filename.Append(NS_ConvertUTF16toUTF8(aIdentifier));
+    }
+    filename.AppendLiteral("-");
     filename.AppendInt(getpid());
-    filename.AppendLiteral(".json");
-    rv = tmpFile->AppendNative(filename);
+    filename.AppendLiteral(".json.gz");
+
+    rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("incomplete-") + filename);
     NS_ENSURE_SUCCESS(rv, rv);
    
-    rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); 
+    rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
     NS_ENSURE_SUCCESS(rv, rv);
-   
-    nsCOMPtr<nsIFileOutputStream> ostream =
-        do_CreateInstance("@mozilla.org/network/file-output-stream;1");
-    rv = ostream->Init(tmpFile, -1, -1, 0);
+
+    nsRefPtr<nsGZFileWriter> writer = new nsGZFileWriter();
+    rv = writer->Init(tmpFile);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Dump the memory reports to the file.
 
     // Increment this number if the format changes.
-    DUMP(ostream, "{\n  \"version\": 1,\n");
+    DUMP(writer, "{\n  \"version\": 1,\n");
 
-    DUMP(ostream, "  \"hasMozMallocUsableSize\": ");
+    DUMP(writer, "  \"hasMozMallocUsableSize\": ");
 
     bool hasMozMallocUsableSize;
     GetHasMozMallocUsableSize(&hasMozMallocUsableSize);
-    DUMP(ostream, hasMozMallocUsableSize ? "true" : "false");
-    DUMP(ostream, ",\n");
-    DUMP(ostream, "  \"reports\": ");
+    DUMP(writer, hasMozMallocUsableSize ? "true" : "false");
+    DUMP(writer, ",\n");
+    DUMP(writer, "  \"reports\": ");
 
     // Process single reporters.
     bool isFirst = true;
     bool more;
     nsCOMPtr<nsISimpleEnumerator> e;
     EnumerateReporters(getter_AddRefs(e));
     while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsIMemoryReporter> r;
@@ -1071,36 +1336,56 @@ nsMemoryReporterManager::DumpReports()
         int64_t amount;
         rv = r->GetAmount(&amount);
         NS_ENSURE_SUCCESS(rv, rv);
 
         nsCString description;
         rv = r->GetDescription(description);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        rv = DumpReport(ostream, isFirst, process, path, kind, units, amount,
+        rv = DumpReport(writer, isFirst, process, path, kind, units, amount,
                         description);
         NS_ENSURE_SUCCESS(rv, rv);
 
         isFirst = false;
     }
 
     // Process multi-reporters.
     nsCOMPtr<nsISimpleEnumerator> e2;
     EnumerateMultiReporters(getter_AddRefs(e2));
     nsRefPtr<DumpMultiReporterCallback> cb = new DumpMultiReporterCallback();
     while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
       nsCOMPtr<nsIMemoryMultiReporter> r;
       e2->GetNext(getter_AddRefs(r));
-      r->CollectReports(cb, ostream);
+      r->CollectReports(cb, writer);
     }
 
-    DUMP(ostream, "\n  ]\n}");
+    DUMP(writer, "\n  ]\n}");
+
+    rv = writer->Finish();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Rename the file, now that we're done dumping the report.  The file's
+    // ultimate destination is "memory-report<-identifier>-<pid>.json.gz".
+
+    nsCOMPtr<nsIFile> dstFile;
+    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dstFile));
+    NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = ostream->Close();
+    rv = dstFile->AppendNative(filename);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = dstFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsAutoString dstFileName;
+    rv = dstFile->GetLeafName(dstFileName);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = tmpFile->MoveTo(/* directory */ nullptr, dstFileName);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIConsoleService> cs =
         do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsString path;
     tmpFile->GetPath(path);
@@ -1109,16 +1394,78 @@ nsMemoryReporterManager::DumpReports()
     nsString msg =
         NS_LITERAL_STRING("nsIMemoryReporterManager::dumpReports() dumped reports to ");
     msg.Append(path);
     return cs->LogStringMessage(msg.get());
 }
 
 #undef DUMP
 
+namespace {
+
+/**
+ * This runnable lets us implement nsIMemoryReporterManager::MinimizeMemoryUsage().
+ * We fire a heap-minimize notification, spin the event loop, and repeat this
+ * process a few times.
+ *
+ * When this sequence finishes, we invoke the callback function passed to the
+ * runnable's constructor.
+ */
+class MinimizeMemoryUsageRunnable : public nsRunnable
+{
+public:
+  MinimizeMemoryUsageRunnable(nsIRunnable* aCallback)
+    : mCallback(aCallback)
+    , mRemainingIters(sNumIters)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    if (!os) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (mRemainingIters == 0) {
+      os->NotifyObservers(nullptr, "after-minimize-memory-usage",
+                          NS_LITERAL_STRING("MinimizeMemoryUsageRunnable").get());
+      if (mCallback) {
+        mCallback->Run();
+      }
+      return NS_OK;
+    }
+
+    os->NotifyObservers(nullptr, "memory-pressure",
+                        NS_LITERAL_STRING("heap-minimize").get());
+    mRemainingIters--;
+    NS_DispatchToMainThread(this);
+
+    return NS_OK;
+  }
+
+private:
+  // Send sNumIters heap-minimize notifications, spinning the event
+  // loop after each notification (see bug 610166 comment 12 for an
+  // explanation), because one notification doesn't cut it.
+  static const uint32_t sNumIters = 3;
+
+  nsCOMPtr<nsIRunnable> mCallback;
+  uint32_t mRemainingIters;
+};
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback)
+{
+  nsRefPtr<MinimizeMemoryUsageRunnable> runnable =
+    new MinimizeMemoryUsageRunnable(aCallback);
+  return NS_DispatchToMainThread(runnable);
+}
+
 NS_IMPL_ISUPPORTS1(nsMemoryReporter, nsIMemoryReporter)
 
 nsMemoryReporter::nsMemoryReporter(nsACString& process,
                                    nsACString& path,
                                    int32_t kind,
                                    int32_t units,
                                    int64_t amount,
                                    nsACString& desc)
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -41,16 +41,18 @@ class nsMemoryReporterManager : public n
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIMEMORYREPORTERMANAGER
 
     nsMemoryReporterManager();
     virtual ~nsMemoryReporterManager();
 
 private:
+    nsresult DumpMemoryReportsToFileImpl(const nsAString& aIdentifier);
+
     nsCOMArray<nsIMemoryReporter>      mReporters;
     nsCOMArray<nsIMemoryMultiReporter> mMultiReporters;
     Mutex                              mMutex;
 };
 
 #define NS_MEMORY_REPORTER_MANAGER_CID \
 { 0xfb97e4f5, 0x32dd, 0x497a, \
 { 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 } }