Bug 800486 - Part 1: Dump a GC and CC log upon receiving SIGRTMIN + 2. r=mccr8,njn,cjones
authorJustin Lebar <justin.lebar@gmail.com>
Mon, 15 Oct 2012 22:12:14 -0400
changeset 110496 e676a99a7a8dddc52583c391e0ac0a9d92320d50
parent 110495 a10052ea8e7263e3c9d689e3e98de54964dd5ad9
child 110497 295110f8c8922817f9e0e001a6b47d7cc9beb9a3
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersmccr8, njn, cjones
bugs800486
milestone19.0a1
Bug 800486 - Part 1: Dump a GC and CC log upon receiving SIGRTMIN + 2. r=mccr8,njn,cjones
dom/base/Makefile.in
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsICycleCollectorListener.idl
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -61,16 +61,17 @@ EXPORTS = \
   nsIScriptContext.h	\
   nsIScriptExternalNameSet.h \
   nsIScriptGlobalObject.h \
   nsIScriptGlobalObjectOwner.h \
   nsIScriptNameSpaceManager.h \
   nsIScriptObjectPrincipal.h \
   nsIScriptRuntime.h \
   nsIScriptTimeoutHandler.h \
+  nsJSEnvironment.h \
   nsJSUtils.h \
   nsPIDOMWindow.h \
   nsPIWindowRoot.h \
   nsFocusManager.h \
   nsWrapperCache.h \
   nsWrapperCacheInlines.h \
   nsContentPermissionHelper.h \
   nsStructuredCloneContainer.h \
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -451,16 +451,27 @@ ContentChild::RecvDumpMemoryReportsToFil
         do_GetService("@mozilla.org/memory-reporter-manager;1");
     NS_ENSURE_TRUE(mgr, true);
     mgr->DumpMemoryReportsToFile(aIdentifier,
                                  aMinimizeMemoryUsage,
                                  aDumpChildProcesses);
     return true;
 }
 
+bool
+ContentChild::RecvDumpGCAndCCLogsToFile(const nsString& aIdentifier,
+                                        const bool& aDumpChildProcesses)
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr =
+        do_GetService("@mozilla.org/memory-reporter-manager;1");
+    NS_ENSURE_TRUE(mgr, true);
+    mgr->DumpGCAndCCLogsToFile(aIdentifier, 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
@@ -106,19 +106,22 @@ public:
 
     virtual bool
     DeallocPMemoryReportRequest(PMemoryReportRequestChild* actor);
 
     virtual bool
     RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child);
 
     virtual bool
-    RecvDumpMemoryReportsToFile(const nsString& identifier,
+    RecvDumpMemoryReportsToFile(const nsString& aIdentifier,
                                 const bool& aMinimizeMemoryUsage,
                                 const bool& aDumpChildProcesses);
+    virtual bool
+    RecvDumpGCAndCCLogsToFile(const nsString& aIdentifier,
+                              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&);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -203,16 +203,25 @@ child:
      *
      * For documentation on the args, see
      * nsIMemoryReporterManager::dumpMemoryReportsToFile.
      */
     async DumpMemoryReportsToFile(nsString identifier,
                                   bool minimizeMemoryUsage,
                                   bool dumpChildProcesses);
 
+    /**
+     * Dump this process's GC and CC logs.
+     *
+     * For documentation on the args, see
+     * nsIMemoryReporterManager::dumpGCAndCCLogsToFile.
+     */
+    async DumpGCAndCCLogsToFile(nsString identifier,
+                                bool dumpChildProcesses);
+
     PTestShell();
 
     RegisterChrome(ChromePackage[] packages, ResourceMapping[] resources,
                    OverrideMapping[] overrides, nsCString locale);
 
     async SetOffline(bool offline);
 
     async NotifyVisited(URIParams uri);
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -116,16 +116,18 @@
 #include "nsThreadUtils.h"
 #include "nsTArray.h"
 #include "mozilla/Services.h"
 #include "mozilla/Attributes.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIXPConnect.h"
 #include "nsIJSRuntimeService.h"
 #include "nsIMemoryReporter.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
 #include "xpcpublic.h"
 #include "nsXPCOMPrivate.h"
 #include "sampler.h"
 #include <stdio.h>
 #include <string.h>
 #ifdef WIN32
 #include <io.h>
 #include <process.h>
@@ -1288,77 +1290,83 @@ public:
     }
 
     NS_IMETHOD SetWantAfterProcessing(bool aWantAfterProcessing)
     {
         mWantAfterProcessing = aWantAfterProcessing;
         return NS_OK;
     }
 
+    NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier)
+    {
+        aIdentifier = mFilenameIdentifier;
+        return NS_OK;
+    }
+
+    NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier)
+    {
+        mFilenameIdentifier = aIdentifier;
+        return NS_OK;
+    }
+
     NS_IMETHOD Begin()
     {
         mCurrentAddress.AssignLiteral("0x");
         mDescribers.Clear();
         mNextIndex = 0;
         if (mDisableLog) {
             return NS_OK;
         }
-        char basename[MAXPATHLEN] = {'\0'};
-        char ccname[MAXPATHLEN] = {'\0'};
-        char* env;
-        if ((env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY"))) {
-            strcpy(basename, env);
-        } else {
-#ifdef XP_WIN
-            // On Windows, tmpnam returns useless stuff, such as "\\s164.".
-            // Therefore we need to call the APIs directly.
-            GetTempPathA(mozilla::ArrayLength(basename), basename);
-#else
-            tmpnam(basename);
-            char *lastSlash = strrchr(basename, XPCOM_FILE_PATH_SEPARATOR[0]);
-            if (lastSlash) {
-                *lastSlash = '\0';
-            }
-#endif
-        }
-
-        ++gLogCounter;
+
+        // Initially create the log in a file starting with
+        // "incomplete-gc-edges".  We'll move the file and strip off the
+        // "incomplete-" once the dump completes.  (We do this because we don't
+        // want scripts which poll the filesystem looking for gc/cc dumps to
+        // grab a file before we're finished writing to it.)
+        nsCOMPtr<nsIFile> gcLogFile = CreateTempFile("incomplete-gc-edges");
+        NS_ENSURE_STATE(gcLogFile);
 
         // Dump the JS heap.
-        char gcname[MAXPATHLEN] = {'\0'};
-        sprintf(gcname, "%s%sgc-edges-%d.%d.log", basename,
-                XPCOM_FILE_PATH_SEPARATOR,
-                gLogCounter, base::GetCurrentProcId());
-
-        FILE* gcDumpFile = fopen(gcname, "w");
-        if (!gcDumpFile)
-            return NS_ERROR_FAILURE;
-        xpc::DumpJSHeap(gcDumpFile);
-        fclose(gcDumpFile);
-
-        // Open a file for dumping the CC graph.
-        sprintf(ccname, "%s%scc-edges-%d.%d.log", basename,
-                XPCOM_FILE_PATH_SEPARATOR,
-                gLogCounter, base::GetCurrentProcId());
-        mStream = fopen(ccname, "w");
-        if (!mStream)
-            return NS_ERROR_FAILURE;
-
+        FILE* gcLogANSIFile = nullptr;
+        gcLogFile->OpenANSIFileDesc("w", &gcLogANSIFile);
+        NS_ENSURE_STATE(gcLogANSIFile);
+        xpc::DumpJSHeap(gcLogANSIFile);
+        fclose(gcLogANSIFile);
+
+        // Strip off "incomplete-".
+        nsCOMPtr<nsIFile> gcLogFileFinalDestination =
+            CreateTempFile("gc-edges");
+        NS_ENSURE_STATE(gcLogFileFinalDestination);
+
+        nsAutoString gcLogFileFinalDestinationName;
+        gcLogFileFinalDestination->GetLeafName(gcLogFileFinalDestinationName);
+        NS_ENSURE_STATE(!gcLogFileFinalDestinationName.IsEmpty());
+
+        gcLogFile->MoveTo(/* directory */ nullptr, gcLogFileFinalDestinationName);
+
+        // Log to the error console.
         nsCOMPtr<nsIConsoleService> cs =
             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
-            nsString msg = NS_LITERAL_STRING("Cycle Collector log dumped to ");
-            AppendUTF8toUTF16(ccname, msg);
-            cs->LogStringMessage(msg.get());
-
-            msg = NS_LITERAL_STRING("Garbage Collector log dumped to ");
-            AppendUTF8toUTF16(gcname, msg);
+            nsAutoString gcLogPath;
+            gcLogFileFinalDestination->GetPath(gcLogPath);
+
+            nsString msg = NS_LITERAL_STRING("Garbage Collector log dumped to ") +
+                           gcLogPath;
             cs->LogStringMessage(msg.get());
         }
 
+        // Open a file for dumping the CC graph.  We again prefix with
+        // "incomplete-".
+        mOutFile = CreateTempFile("incomplete-cc-edges");
+        NS_ENSURE_STATE(mOutFile);
+        MOZ_ASSERT(!mStream);
+        mOutFile->OpenANSIFileDesc("w", &mStream);
+        NS_ENSURE_STATE(mStream);
+
         return NS_OK;
     }
     NS_IMETHOD NoteRefCountedObject(uint64_t aAddress, uint32_t refCount,
                                     const char *aObjectDescription)
     {
         if (!mDisableLog) {
             fprintf(mStream, "%p [rc=%u] %s\n", (void*)aAddress, refCount,
                     aObjectDescription);
@@ -1441,22 +1449,49 @@ public:
             d->mType = CCGraphDescriber::eGarbage;
             d->mAddress.AppendInt(aAddress, 16);
         }
         return NS_OK;
     }
     NS_IMETHOD End()
     {
         if (!mDisableLog) {
+            MOZ_ASSERT(mStream);
+            MOZ_ASSERT(mOutFile);
+
             fclose(mStream);
             mStream = nullptr;
+
+            // Strip off "incomplete-" from the log file's name.
+            nsCOMPtr<nsIFile> logFileFinalDestination =
+                CreateTempFile("cc-edges");
+            NS_ENSURE_STATE(logFileFinalDestination);
+
+            nsAutoString logFileFinalDestinationName;
+            logFileFinalDestination->GetLeafName(logFileFinalDestinationName);
+            NS_ENSURE_STATE(!logFileFinalDestinationName.IsEmpty());
+
+            mOutFile->MoveTo(/* directory = */ nullptr,
+                             logFileFinalDestinationName);
+            mOutFile = nullptr;
+
+            // Log to the error console.
+            nsCOMPtr<nsIConsoleService> cs =
+                do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+            if (cs) {
+                nsAutoString ccLogPath;
+                logFileFinalDestination->GetPath(ccLogPath);
+
+                nsString msg = NS_LITERAL_STRING("Cycle Collector log dumped to ") +
+                               ccLogPath;
+                cs->LogStringMessage(msg.get());
+            }
         }
         return NS_OK;
     }
-
     NS_IMETHOD ProcessNext(nsICycleCollectorHandler* aHandler,
                            bool* aCanContinue)
     {
         NS_ENSURE_STATE(aHandler && mWantAfterProcessing);
         if (mNextIndex < mDescribers.Length()) {
             CCGraphDescriber& d = mDescribers[mNextIndex++];
             switch (d.mType) {
                 case CCGraphDescriber::eRefCountedObject:
@@ -1491,30 +1526,66 @@ public:
         if (!(*aCanContinue = mNextIndex < mDescribers.Length())) {
             mCurrentAddress.AssignLiteral("0x");
             mDescribers.Clear();
             mNextIndex = 0;
         }
         return NS_OK;
     }
 private:
+    /**
+     * Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in
+     * $MOZ_CC_LOG_DIRECTORY or in the system's temp directory.  No existing
+     * file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll
+     * try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so
+     * on.
+     */
+    already_AddRefed<nsIFile>
+    CreateTempFile(const char* aPrefix)
+    {
+        nsPrintfCString filename("%s.%d%s%s.log",
+            aPrefix,
+            base::GetCurrentProcId(),
+            mFilenameIdentifier.IsEmpty() ? "" : ".",
+            NS_ConvertUTF16toUTF8(mFilenameIdentifier).get());
+
+        // Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from our
+        // platform's temp directory.
+        nsCOMPtr<nsIFile> logFile;
+        if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) {
+            NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true,
+                                  getter_AddRefs(logFile));
+        } else {
+            // Ask NSPR to point us to the temp directory.
+            NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(logFile));
+        }
+        NS_ENSURE_TRUE(logFile, nullptr);
+
+        nsresult rv = logFile->AppendNative(filename);
+        NS_ENSURE_SUCCESS(rv, nullptr);
+
+        rv = logFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+        NS_ENSURE_SUCCESS(rv, nullptr);
+
+        return logFile.forget();
+    }
+
     FILE *mStream;
+    nsCOMPtr<nsIFile> mOutFile;
     bool mWantAllTraces;
     bool mDisableLog;
     bool mWantAfterProcessing;
+    nsString mFilenameIdentifier;
     nsCString mCurrentAddress; 
     nsTArray<CCGraphDescriber> mDescribers;
     uint32_t mNextIndex;
-    static uint32_t gLogCounter;
 };
 
 NS_IMPL_ISUPPORTS1(nsCycleCollectorLogger, nsICycleCollectorListener)
 
-uint32_t nsCycleCollectorLogger::gLogCounter = 0;
-
 nsresult
 nsCycleCollectorLoggerConstructor(nsISupports* aOuter,
                                   const nsIID& aIID,
                                   void* *aInstancePtr)
 {
     NS_ENSURE_TRUE(!aOuter, NS_ERROR_NO_AGGREGATION);
 
     nsISupports *logger = new nsCycleCollectorLogger();
--- a/xpcom/base/nsICycleCollectorListener.idl
+++ b/xpcom/base/nsICycleCollectorListener.idl
@@ -29,28 +29,31 @@ interface nsICycleCollectorHandler : nsI
  * beginResults(); then a mixture of describeRoot() for ref counted
  * nodes the CC has identified as roots and describeGarbage() for
  * nodes the CC has identified as garbage.  Ref counted nodes that are
  * not identified as either roots or garbage are neither, and have a
  * known edges count equal to their ref count.  Finally, there will be
  * a call to end().  If begin() returns an error none of the other
  * functions will be called.
  */
-[scriptable, builtinclass, uuid(5d1c5d51-2022-4242-8c33-0a942b5fed06)]
+[scriptable, builtinclass, uuid(4282249c-7f80-4093-b620-96c573ad683e)]
 interface nsICycleCollectorListener : nsISupports
 {
     nsICycleCollectorListener allTraces();
     // false if allTraces() has not been called.
     readonly attribute boolean wantAllTraces;
 
     // The default implementation of this interface will print out
     // a log to a file unless disableLog is set to true.
     attribute boolean disableLog;
     attribute boolean wantAfterProcessing;
 
+    // This string will appear somewhere in the log's filename.
+    attribute AString filenameIdentifier;
+
     void begin();
     void noteRefCountedObject (in unsigned long long aAddress,
 			       in unsigned long aRefCount,
 			       in string aObjectDescription);
     void noteGCedObject (in unsigned long long aAddress,
 			 in boolean aMarked,
 			 in string aObjectDescription);
     void noteEdge(in unsigned long long aToAddress,
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -391,16 +391,23 @@ interface nsIMemoryReporterManager : nsI
    *       }
    *     }
    *   }
    * }
    */
   void dumpMemoryReportsToFile (in AString identifier,
                                 in bool minimizeMemoryUsage,
                                 in bool dumpChildProcesses);
+
+  /**
+   * Dump GC and CC logs to files in the OS's temp directory (or in
+   * $MOZ_CC_LOG_DIRECTORY, if that environment variable is specified).
+   */
+  void dumpGCAndCCLogsToFile (in AString identifier,
+                              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
@@ -15,16 +15,18 @@
 #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 "nsJSEnvironment.h"
+#include "nsICycleCollectorListener.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ClearOnShutdown.h"
@@ -670,44 +672,73 @@ public:
   }
 
 private:
   const nsString mIdentifier;
   const bool mMinimizeMemoryUsage;
   const bool mDumpChildProcesses;
 };
 
+class GCAndCCLogDumpRunnable : public nsRunnable
+{
+public:
+  GCAndCCLogDumpRunnable(const nsAString& aIdentifier,
+                         bool aDumpChildProcesses)
+    : mIdentifier(aIdentifier)
+    , mDumpChildProcesses(aDumpChildProcesses)
+  {}
+
+  NS_IMETHOD Run()
+  {
+      nsCOMPtr<nsIMemoryReporterManager> mgr =
+          do_GetService("@mozilla.org/memory-reporter-manager;1");
+      NS_ENSURE_STATE(mgr);
+      mgr->DumpGCAndCCLogsToFile(mIdentifier, mDumpChildProcesses);
+      return NS_OK;
+  }
+
+private:
+  const nsString mIdentifier;
+  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.)
+ * We listen for the following signals:
+ *
+ *  - SIGRTMIN:     Dump our memory reporters (and those of our child
+ *                  processes),
+ *  - SIGRTMIN + 1: Dump our memory reporters (and those of our child
+ *                  processes) after minimizing memory usage, and
+ *  - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes.
  *
  * 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.
+ * the appropriate 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
+static int sGCAndCCDumpSignum;             // SIGRTMIN + 2
 
 // 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)
 {
@@ -744,16 +775,17 @@ public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SignalPipeWatcher)
 
   bool Start()
   {
     MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 
     sDumpAboutMemorySignum = SIGRTMIN;
     sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
+    sGCAndCCDumpSignum = SIGRTMIN + 2;
 
     // 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;
     }
@@ -771,16 +803,19 @@ public:
     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.");
     }
+    if (sigaction(sGCAndCCDumpSignum, &action, nullptr)) {
+      NS_WARNING("Failed to register GC+CC dump 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);
   }
 
@@ -791,29 +826,37 @@ public:
     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;
+    if (signum == sDumpAboutMemorySignum ||
+        signum == sDumpAboutMemoryAfterMMUSignum) {
+      // Dump our memory reports (but run this on the main thread!).
+      nsRefPtr<DumpMemoryReportsRunnable> runnable =
+        new DumpMemoryReportsRunnable(
+            /* identifier = */ EmptyString(),
+            signum == sDumpAboutMemoryAfterMMUSignum,
+            /* dumpChildProcesses = */ true);
+      NS_DispatchToMainThread(runnable);
     }
-
-    // Dump about:memory (but run this on the main thread!).
-    nsRefPtr<DumpMemoryReportsRunnable> runnable =
-      new DumpMemoryReportsRunnable(
-          /* identifier = */ EmptyString(),
-          signum == sDumpAboutMemoryAfterMMUSignum,
-          /* dumpChildProcesses = */ true);
-    NS_DispatchToMainThread(runnable);
+    else if (signum == sGCAndCCDumpSignum) {
+      // Dump GC and CC logs (from the main thread).
+      nsRefPtr<GCAndCCLogDumpRunnable> runnable =
+        new GCAndCCLogDumpRunnable(
+            /* identifier = */ EmptyString(),
+            /* dumpChildProcesses = */ true);
+      NS_DispatchToMainThread(runnable);
+    }
+    else {
+      NS_WARNING("Got unexpected signum.");
+    }
   }
 
   virtual void OnFileCanWriteWithoutBlocking(int aFd)
   {}
 
 private:
   int mPipeReadFd;
   MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
@@ -1177,16 +1220,47 @@ nsMemoryReporterManager::DumpMemoryRepor
                                           /* minimizeMemoryUsage = */ false,
                                           /* dumpChildProcesses = */ false);
         return MinimizeMemoryUsage(callback);
     }
 
     return DumpMemoryReportsToFileImpl(identifier);
 }
 
+NS_IMETHODIMP
+nsMemoryReporterManager::DumpGCAndCCLogsToFile(
+    const nsAString& aIdentifier,
+    bool aDumpChildProcesses)
+{
+    // If the identifier is empty, set it to the number of whole seconds since
+    // the epoch.  This identifier will appear in our logs as well as our
+    // children's, allowing us to identify which files are from the same
+    // request.
+    nsString identifier(aIdentifier);
+    if (identifier.IsEmpty()) {
+        identifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
+    }
+
+    if (aDumpChildProcesses) {
+        nsTArray<ContentParent*> children;
+        ContentParent::GetAll(children);
+        for (uint32_t i = 0; i < children.Length(); i++) {
+            unused << children[i]->SendDumpGCAndCCLogsToFile(
+                identifier, aDumpChildProcesses);
+        }
+    }
+
+    nsCOMPtr<nsICycleCollectorListener> logger =
+      do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
+    logger->SetFilenameIdentifier(identifier);
+
+    nsJSContext::CycleCollectNow(logger);
+    return NS_OK;
+}
+
 #define DUMP(o, s) \
     do { \
         nsresult rv = (o)->Write(s); \
         NS_ENSURE_SUCCESS(rv, rv); \
     } while (0)
 
 static nsresult
 DumpReport(nsIGZFileWriter *aWriter, bool aIsFirst,