forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			742 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			742 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/JSONWriter.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "mozilla/nsMemoryInfoDumper.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "nsDumpUtils.h"
 | |
| 
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/dom/ContentParent.h"
 | |
| #include "mozilla/dom/ContentChild.h"
 | |
| #include "nsIConsoleService.h"
 | |
| #include "nsCycleCollector.h"
 | |
| #include "nsICycleCollectorListener.h"
 | |
| #include "nsIMemoryReporter.h"
 | |
| #include "nsDirectoryServiceDefs.h"
 | |
| #include "nsGZFileWriter.h"
 | |
| #include "nsJSEnvironment.h"
 | |
| #include "nsPrintfCString.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "nsIFile.h"
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| #  include <process.h>
 | |
| #  ifndef getpid
 | |
| #    define getpid _getpid
 | |
| #  endif
 | |
| #else
 | |
| #  include <unistd.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef XP_UNIX
 | |
| #  define MOZ_SUPPORTS_FIFO 1
 | |
| #endif
 | |
| 
 | |
| // Some Android devices seem to send RT signals to Firefox so we want to avoid
 | |
| // consuming those as they're not user triggered.
 | |
| #if !defined(ANDROID) && (defined(XP_LINUX) || defined(__FreeBSD__))
 | |
| #  define MOZ_SUPPORTS_RT_SIGNALS 1
 | |
| #endif
 | |
| 
 | |
| #if defined(MOZ_SUPPORTS_RT_SIGNALS)
 | |
| #  include <fcntl.h>
 | |
| #  include <sys/types.h>
 | |
| #  include <sys/stat.h>
 | |
| #endif
 | |
| 
 | |
| #if defined(MOZ_SUPPORTS_FIFO)
 | |
| #  include "mozilla/Preferences.h"
 | |
| #endif
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::dom;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| class DumpMemoryInfoToTempDirRunnable : public Runnable {
 | |
|  public:
 | |
|   DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, bool aAnonymize,
 | |
|                                   bool aMinimizeMemoryUsage)
 | |
|       : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"),
 | |
|         mIdentifier(aIdentifier),
 | |
|         mAnonymize(aAnonymize),
 | |
|         mMinimizeMemoryUsage(aMinimizeMemoryUsage) {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     nsCOMPtr<nsIMemoryInfoDumper> dumper =
 | |
|         do_GetService("@mozilla.org/memory-info-dumper;1");
 | |
|     dumper->DumpMemoryInfoToTempDir(mIdentifier, mAnonymize,
 | |
|                                     mMinimizeMemoryUsage);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const nsString mIdentifier;
 | |
|   const bool mAnonymize;
 | |
|   const bool mMinimizeMemoryUsage;
 | |
| };
 | |
| 
 | |
| class GCAndCCLogDumpRunnable final : public Runnable,
 | |
|                                      public nsIDumpGCAndCCLogsCallback {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS_INHERITED
 | |
| 
 | |
|   GCAndCCLogDumpRunnable(const nsAString& aIdentifier, bool aDumpAllTraces,
 | |
|                          bool aDumpChildProcesses)
 | |
|       : mozilla::Runnable("GCAndCCLogDumpRunnable"),
 | |
|         mIdentifier(aIdentifier),
 | |
|         mDumpAllTraces(aDumpAllTraces),
 | |
|         mDumpChildProcesses(aDumpChildProcesses) {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     nsCOMPtr<nsIMemoryInfoDumper> dumper =
 | |
|         do_GetService("@mozilla.org/memory-info-dumper;1");
 | |
| 
 | |
|     dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces,
 | |
|                                   mDumpChildProcesses, this);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD OnFinish() override { return NS_OK; }
 | |
| 
 | |
|  private:
 | |
|   ~GCAndCCLogDumpRunnable() = default;
 | |
| 
 | |
|   const nsString mIdentifier;
 | |
|   const bool mDumpAllTraces;
 | |
|   const bool mDumpChildProcesses;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable,
 | |
|                             nsIDumpGCAndCCLogsCallback)
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| #if defined(MOZ_SUPPORTS_RT_SIGNALS)  // {
 | |
| namespace {
 | |
| 
 | |
| /*
 | |
|  * The following code supports dumping about:memory upon receiving a signal.
 | |
|  *
 | |
|  * 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
 | |
|  * 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 uint8_t sDumpAboutMemorySignum;          // SIGRTMIN
 | |
| static uint8_t sDumpAboutMemoryAfterMMUSignum;  // SIGRTMIN + 1
 | |
| static uint8_t sGCAndCCDumpSignum;              // SIGRTMIN + 2
 | |
| 
 | |
| void doMemoryReport(const uint8_t aRecvSig) {
 | |
|   // Dump our memory reports (but run this on the main thread!).
 | |
|   bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum;
 | |
|   LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig);
 | |
|   RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
 | |
|       new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns,
 | |
|                                           /* anonymize = */ false, minimize);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
| }
 | |
| 
 | |
| void doGCCCDump(const uint8_t aRecvSig) {
 | |
|   LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig);
 | |
|   // Dump GC and CC logs (from the main thread).
 | |
|   RefPtr<GCAndCCLogDumpRunnable> runnable =
 | |
|       new GCAndCCLogDumpRunnable(/* identifier = */ u""_ns,
 | |
|                                  /* allTraces = */ true,
 | |
|                                  /* dumpChildProcesses = */ true);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| #endif  // MOZ_SUPPORTS_RT_SIGNALS }
 | |
| 
 | |
| #if defined(MOZ_SUPPORTS_FIFO)  // {
 | |
| namespace {
 | |
| 
 | |
| void doMemoryReport(const nsCString& aInputStr) {
 | |
|   bool minimize = aInputStr.EqualsLiteral("minimize memory report");
 | |
|   LOG("FifoWatcher(command:%s) dispatching memory report runnable.",
 | |
|       aInputStr.get());
 | |
|   RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
 | |
|       new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns,
 | |
|                                           /* anonymize = */ false, minimize);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
| }
 | |
| 
 | |
| void doGCCCDump(const nsCString& aInputStr) {
 | |
|   bool doAllTracesGCCCDump = aInputStr.EqualsLiteral("gc log");
 | |
|   LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.",
 | |
|       aInputStr.get());
 | |
|   RefPtr<GCAndCCLogDumpRunnable> runnable = new GCAndCCLogDumpRunnable(
 | |
|       /* identifier = */ u""_ns, doAllTracesGCCCDump,
 | |
|       /* dumpChildProcesses = */ true);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
| }
 | |
| 
 | |
| bool SetupFifo() {
 | |
| #  ifdef DEBUG
 | |
|   static bool fifoCallbacksRegistered = false;
 | |
| #  endif
 | |
| 
 | |
|   if (!FifoWatcher::MaybeCreate()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!fifoCallbacksRegistered,
 | |
|              "FifoWatcher callbacks should be registered only once");
 | |
| 
 | |
|   FifoWatcher* fw = FifoWatcher::GetSingleton();
 | |
|   // Dump our memory reports (but run this on the main thread!).
 | |
|   fw->RegisterCallback("memory report"_ns, doMemoryReport);
 | |
|   fw->RegisterCallback("minimize memory report"_ns, doMemoryReport);
 | |
|   // Dump GC and CC logs (from the main thread).
 | |
|   fw->RegisterCallback("gc log"_ns, doGCCCDump);
 | |
|   fw->RegisterCallback("abbreviated gc log"_ns, doGCCCDump);
 | |
| 
 | |
| #  ifdef DEBUG
 | |
|   fifoCallbacksRegistered = true;
 | |
| #  endif
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) {
 | |
|   LOG("%s changed", FifoWatcher::kPrefName);
 | |
|   if (SetupFifo()) {
 | |
|     Preferences::UnregisterCallback(OnFifoEnabledChange,
 | |
|                                     FifoWatcher::kPrefName);
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| #endif  // MOZ_SUPPORTS_FIFO }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper)
 | |
| 
 | |
| nsMemoryInfoDumper::nsMemoryInfoDumper() = default;
 | |
| 
 | |
| nsMemoryInfoDumper::~nsMemoryInfoDumper() = default;
 | |
| 
 | |
| /* static */
 | |
| void nsMemoryInfoDumper::Initialize() {
 | |
| #if defined(MOZ_SUPPORTS_RT_SIGNALS)
 | |
|   SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
 | |
| 
 | |
|   // Dump memory reporters (and those of our child processes)
 | |
|   sDumpAboutMemorySignum = SIGRTMIN;
 | |
|   sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport);
 | |
|   // Dump our memory reporters after minimizing memory usage
 | |
|   sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
 | |
|   sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport);
 | |
|   // Dump the GC and CC logs in this and our child processes.
 | |
|   sGCAndCCDumpSignum = SIGRTMIN + 2;
 | |
|   sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump);
 | |
| #endif
 | |
| 
 | |
| #if defined(MOZ_SUPPORTS_FIFO)
 | |
|   if (!SetupFifo()) {
 | |
|     // NB: This gets loaded early enough that it's possible there is a user pref
 | |
|     //     set to enable the fifo watcher that has not been loaded yet. Register
 | |
|     //     to attempt to initialize if the fifo watcher becomes enabled by
 | |
|     //     a user pref.
 | |
|     Preferences::RegisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName);
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static void EnsureNonEmptyIdentifier(nsAString& aIdentifier) {
 | |
|   if (!aIdentifier.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If the identifier is empty, set it to the number of whole seconds since the
 | |
|   // epoch.  This identifier will appear in the files that this process
 | |
|   // generates and also the files generated by this process's children, allowing
 | |
|   // us to identify which files are from the same memory report request.
 | |
|   aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
 | |
| }
 | |
| 
 | |
| // Use XPCOM refcounting to fire |onFinish| when all reference-holders
 | |
| // (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself)
 | |
| // have gone away.
 | |
| class nsDumpGCAndCCLogsCallbackHolder final
 | |
|     : public nsIDumpGCAndCCLogsCallback {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   explicit nsDumpGCAndCCLogsCallbackHolder(
 | |
|       nsIDumpGCAndCCLogsCallback* aCallback)
 | |
|       : mCallback(aCallback) {}
 | |
| 
 | |
|   NS_IMETHOD OnFinish() override { return NS_ERROR_UNEXPECTED; }
 | |
| 
 | |
|   NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
 | |
|     return mCallback->OnDump(aGCLog, aCCLog, aIsParent);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~nsDumpGCAndCCLogsCallbackHolder() { Unused << mCallback->OnFinish(); }
 | |
| 
 | |
|   nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsMemoryInfoDumper::DumpGCAndCCLogsToFile(
 | |
|     const nsAString& aIdentifier, bool aDumpAllTraces, bool aDumpChildProcesses,
 | |
|     nsIDumpGCAndCCLogsCallback* aCallback) {
 | |
|   nsString identifier(aIdentifier);
 | |
|   EnsureNonEmptyIdentifier(identifier);
 | |
|   nsCOMPtr<nsIDumpGCAndCCLogsCallback> callbackHolder =
 | |
|       new nsDumpGCAndCCLogsCallbackHolder(aCallback);
 | |
| 
 | |
|   if (aDumpChildProcesses) {
 | |
|     nsTArray<ContentParent*> children;
 | |
|     ContentParent::GetAll(children);
 | |
|     for (uint32_t i = 0; i < children.Length(); i++) {
 | |
|       ContentParent* cp = children[i];
 | |
|       nsCOMPtr<nsICycleCollectorLogSink> logSink =
 | |
|           nsCycleCollector_createLogSink();
 | |
| 
 | |
|       logSink->SetFilenameIdentifier(identifier);
 | |
|       logSink->SetProcessIdentifier(cp->Pid());
 | |
| 
 | |
|       Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink,
 | |
|                                          callbackHolder);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
 | |
| 
 | |
|   if (aDumpAllTraces) {
 | |
|     nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
 | |
|     logger->AllTraces(getter_AddRefs(allTracesLogger));
 | |
|     logger = allTracesLogger;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICycleCollectorLogSink> logSink;
 | |
|   logger->GetLogSink(getter_AddRefs(logSink));
 | |
| 
 | |
|   logSink->SetFilenameIdentifier(identifier);
 | |
| 
 | |
|   nsJSContext::CycleCollectNow(logger);
 | |
| 
 | |
|   nsCOMPtr<nsIFile> gcLog, ccLog;
 | |
|   logSink->GetGcLog(getter_AddRefs(gcLog));
 | |
|   logSink->GetCcLog(getter_AddRefs(ccLog));
 | |
|   callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
 | |
|                                           nsICycleCollectorLogSink* aSink) {
 | |
|   nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
 | |
| 
 | |
|   if (aDumpAllTraces) {
 | |
|     nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
 | |
|     logger->AllTraces(getter_AddRefs(allTracesLogger));
 | |
|     logger = allTracesLogger;
 | |
|   }
 | |
| 
 | |
|   logger->SetLogSink(aSink);
 | |
| 
 | |
|   nsJSContext::CycleCollectNow(logger);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| static void MakeFilename(const char* aPrefix, const nsAString& aIdentifier,
 | |
|                          int aPid, const char* aSuffix, nsACString& aResult) {
 | |
|   aResult =
 | |
|       nsPrintfCString("%s-%s-%d.%s", aPrefix,
 | |
|                       NS_ConvertUTF16toUTF8(aIdentifier).get(), aPid, aSuffix);
 | |
| }
 | |
| 
 | |
| // This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
 | |
| // the following two problems:
 | |
| // - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
 | |
| // - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
 | |
| class GZWriterWrapper : public JSONWriteFunc {
 | |
|  public:
 | |
|   explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {}
 | |
| 
 | |
|   void Write(const Span<const char>& aStr) override {
 | |
|     // Ignore any failure because JSONWriteFunc doesn't have a mechanism for
 | |
|     // handling errors.
 | |
|     Unused << mGZWriter->Write(aStr.data(), aStr.size());
 | |
|   }
 | |
| 
 | |
|   nsresult Finish() { return mGZWriter->Finish(); }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<nsGZFileWriter> mGZWriter;
 | |
| };
 | |
| 
 | |
| // We need two callbacks: one that handles reports, and one that is called at
 | |
| // the end of reporting. Both the callbacks need access to the same JSONWriter,
 | |
| // so we implement both of them in this one class.
 | |
| class HandleReportAndFinishReportingCallbacks final
 | |
|     : public nsIHandleReportCallback,
 | |
|       public nsIFinishReportingCallback {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   HandleReportAndFinishReportingCallbacks(
 | |
|       UniquePtr<JSONWriter> aWriter, nsIFinishDumpingCallback* aFinishDumping,
 | |
|       nsISupports* aFinishDumpingData)
 | |
|       : mWriter(std::move(aWriter)),
 | |
|         mFinishDumping(aFinishDumping),
 | |
|         mFinishDumpingData(aFinishDumpingData) {}
 | |
| 
 | |
|   // This is the callback for nsIHandleReportCallback.
 | |
|   NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
 | |
|                       int32_t aKind, int32_t aUnits, int64_t aAmount,
 | |
|                       const nsACString& aDescription,
 | |
|                       nsISupports* aData) override {
 | |
|     nsAutoCString process;
 | |
|     if (aProcess.IsEmpty()) {
 | |
|       // If the process is empty, the report originated with the process doing
 | |
|       // the dumping.  In that case, generate the process identifier, which is
 | |
|       // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we
 | |
|       // don't have a process name.  If we're the main process, we let
 | |
|       // $PROCESS_NAME be "Main Process".
 | |
|       //
 | |
|       // `appendAboutMemoryMain()` in aboutMemory.js does much the same thing
 | |
|       // for live memory reports.
 | |
|       if (XRE_IsParentProcess()) {
 | |
|         // We're the main process.
 | |
|         process.AssignLiteral("Main Process");
 | |
|       } else if (ContentChild* cc = ContentChild::GetSingleton()) {
 | |
|         // Try to get the process name from ContentChild.
 | |
|         cc->GetProcessName(process);
 | |
|       }
 | |
|       ContentChild::AppendProcessId(process);
 | |
| 
 | |
|     } else {
 | |
|       // Otherwise, the report originated with another process and already has a
 | |
|       // process name.  Just use that.
 | |
|       process = aProcess;
 | |
|     }
 | |
| 
 | |
|     mWriter->StartObjectElement();
 | |
|     {
 | |
|       mWriter->StringProperty("process", process);
 | |
|       mWriter->StringProperty("path", PromiseFlatCString(aPath));
 | |
|       mWriter->IntProperty("kind", aKind);
 | |
|       mWriter->IntProperty("units", aUnits);
 | |
|       mWriter->IntProperty("amount", aAmount);
 | |
|       mWriter->StringProperty("description", PromiseFlatCString(aDescription));
 | |
|     }
 | |
|     mWriter->EndObject();
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // This is the callback for nsIFinishReportingCallback.
 | |
|   NS_IMETHOD Callback(nsISupports* aData) override {
 | |
|     mWriter->EndArray();  // end of "reports" array
 | |
|     mWriter->End();
 | |
| 
 | |
|     // The call to Finish() deallocates the memory allocated by the first Write
 | |
|     // call. Because that memory was live while the memory reporters ran and
 | |
|     // was measured by them -- by "heap-allocated" if nothing else -- we want
 | |
|     // DMD to see it as well. So we deliberately don't call Finish() until
 | |
|     // after DMD finishes.
 | |
|     nsresult rv = static_cast<GZWriterWrapper*>(mWriter->WriteFunc())->Finish();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     if (!mFinishDumping) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     return mFinishDumping->Callback(mFinishDumpingData);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~HandleReportAndFinishReportingCallbacks() = default;
 | |
| 
 | |
|   UniquePtr<JSONWriter> mWriter;
 | |
|   nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
 | |
|   nsCOMPtr<nsISupports> mFinishDumpingData;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
 | |
|                   nsIHandleReportCallback, nsIFinishReportingCallback)
 | |
| 
 | |
| class TempDirFinishCallback final : public nsIFinishDumpingCallback {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   TempDirFinishCallback(nsIFile* aReportsTmpFile,
 | |
|                         const nsCString& aReportsFinalFilename)
 | |
|       : mReportsTmpFile(aReportsTmpFile),
 | |
|         mReportsFilename(aReportsFinalFilename) {}
 | |
| 
 | |
|   NS_IMETHOD Callback(nsISupports* aData) override {
 | |
|     // Rename the memory reports file, now that we're done writing all the
 | |
|     // files. Its final name is "memory-report<-identifier>-<pid>.json.gz".
 | |
| 
 | |
|     nsCOMPtr<nsIFile> reportsFinalFile;
 | |
|     nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
 | |
|                                          getter_AddRefs(reportsFinalFile));
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
| #ifdef ANDROID
 | |
|     rv = reportsFinalFile->AppendNative("memory-reports"_ns);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     rv = reportsFinalFile->AppendNative(mReportsFilename);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     nsAutoString reportsFinalFilename;
 | |
|     rv = reportsFinalFile->GetLeafName(reportsFinalFilename);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, reportsFinalFilename);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     // Write a message to the console.
 | |
| 
 | |
|     nsCOMPtr<nsIConsoleService> cs =
 | |
|         do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     nsString path;
 | |
|     mReportsTmpFile->GetPath(path);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     nsString msg = u"nsIMemoryInfoDumper dumped reports to "_ns;
 | |
|     msg.Append(path);
 | |
|     return cs->LogStringMessage(msg.get());
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~TempDirFinishCallback() = default;
 | |
| 
 | |
|   nsCOMPtr<nsIFile> mReportsTmpFile;
 | |
|   nsCString mReportsFilename;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback)
 | |
| 
 | |
| static nsresult DumpMemoryInfoToFile(nsIFile* aReportsFile,
 | |
|                                      nsIFinishDumpingCallback* aFinishDumping,
 | |
|                                      nsISupports* aFinishDumpingData,
 | |
|                                      bool aAnonymize, bool aMinimizeMemoryUsage,
 | |
|                                      nsAString& aDMDIdentifier) {
 | |
|   RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
 | |
|   nsresult rv = gzWriter->Init(aReportsFile);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
|   auto jsonWriter =
 | |
|       MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
 | |
| 
 | |
|   nsCOMPtr<nsIMemoryReporterManager> mgr =
 | |
|       do_GetService("@mozilla.org/memory-reporter-manager;1");
 | |
| 
 | |
|   // This is the first write to the file, and it causes |aWriter| to allocate
 | |
|   // over 200 KiB of memory.
 | |
|   jsonWriter->Start();
 | |
|   {
 | |
|     // Increment this number if the format changes.
 | |
|     jsonWriter->IntProperty("version", 1);
 | |
|     jsonWriter->BoolProperty("hasMozMallocUsableSize",
 | |
|                              mgr->GetHasMozMallocUsableSize());
 | |
|     jsonWriter->StartArrayProperty("reports");
 | |
|   }
 | |
| 
 | |
|   RefPtr<HandleReportAndFinishReportingCallbacks>
 | |
|       handleReportAndFinishReporting =
 | |
|           new HandleReportAndFinishReportingCallbacks(
 | |
|               std::move(jsonWriter), aFinishDumping, aFinishDumpingData);
 | |
|   rv = mgr->GetReportsExtended(
 | |
|       handleReportAndFinishReporting, nullptr, handleReportAndFinishReporting,
 | |
|       nullptr, aAnonymize, aMinimizeMemoryUsage, aDMDIdentifier);
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
 | |
|     const nsAString& aFilename, nsIFinishDumpingCallback* aFinishDumping,
 | |
|     nsISupports* aFinishDumpingData, bool aAnonymize,
 | |
|     bool aMinimizeMemoryUsage) {
 | |
|   MOZ_ASSERT(!aFilename.IsEmpty());
 | |
| 
 | |
|   // Create the file.
 | |
| 
 | |
|   nsCOMPtr<nsIFile> reportsFile;
 | |
|   nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(reportsFile));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   reportsFile->InitWithPath(aFilename);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   bool exists;
 | |
|   rv = reportsFile->Exists(&exists);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (!exists) {
 | |
|     rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsString dmdIdent;
 | |
|   return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData,
 | |
|                               aAnonymize, aMinimizeMemoryUsage, dmdIdent);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
 | |
|                                             bool aAnonymize,
 | |
|                                             bool aMinimizeMemoryUsage) {
 | |
|   nsString identifier(aIdentifier);
 | |
|   EnsureNonEmptyIdentifier(identifier);
 | |
| 
 | |
|   // Open a new file named something like
 | |
|   //
 | |
|   //   incomplete-memory-report-<identifier>-<pid>.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.
 | |
| 
 | |
|   // The "unified" indicates that we merge the memory reports from all
 | |
|   // processes and write out one file, rather than a separate file for
 | |
|   // each process as was the case before bug 946407.  This is so that
 | |
|   // the get_about_memory.py script in the B2G repository can
 | |
|   // determine when it's done waiting for files to appear.
 | |
|   nsCString reportsFinalFilename;
 | |
|   MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
 | |
|                reportsFinalFilename);
 | |
| 
 | |
|   nsCOMPtr<nsIFile> reportsTmpFile;
 | |
|   nsresult rv;
 | |
|   // In Android case, this function will open a file named aFilename under
 | |
|   // specific folder (/data/local/tmp/memory-reports). Otherwise, it will
 | |
|   // open a file named aFilename under "NS_OS_TEMP_DIR".
 | |
|   rv = nsDumpUtils::OpenTempFile("incomplete-"_ns + reportsFinalFilename,
 | |
|                                  getter_AddRefs(reportsTmpFile),
 | |
|                                  "memory-reports"_ns);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   RefPtr<TempDirFinishCallback> finishDumping =
 | |
|       new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
 | |
| 
 | |
|   return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr,
 | |
|                               aAnonymize, aMinimizeMemoryUsage, identifier);
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_DMD
 | |
| dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton;
 | |
| 
 | |
| nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
 | |
|                                          FILE** aOutFile) {
 | |
|   if (!dmd::IsRunning()) {
 | |
|     *aOutFile = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used
 | |
|   // if DMD is enabled.
 | |
|   nsCString dmdFilename;
 | |
|   MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
 | |
| 
 | |
|   // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
 | |
|   // and dump DMD output to it.  This must occur after the memory reporters
 | |
|   // have been run (above), but before the memory-reports file has been
 | |
|   // renamed (so scripts can detect the DMD file, if present).
 | |
| 
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIFile> dmdFile;
 | |
|   rv = nsDumpUtils::OpenTempFile(dmdFilename, getter_AddRefs(dmdFile),
 | |
|                                  "memory-reports"_ns);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
|   rv = dmdFile->OpenANSIFileDesc("wb", aOutFile);
 | |
|   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed");
 | |
| 
 | |
|   // Print the path, because on some platforms (e.g. Mac) it's not obvious.
 | |
|   dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().get());
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) {
 | |
|   RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
 | |
|   nsresult rv = gzWriter->InitANSIFileDesc(aFile);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // Dump DMD's memory reports analysis to the file.
 | |
|   dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter));
 | |
| 
 | |
|   rv = gzWriter->Finish();
 | |
|   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed");
 | |
|   return rv;
 | |
| }
 | |
| #endif  // MOZ_DMD
 | 
