michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/nsMemoryInfoDumper.h" michael@0: #include "nsDumpUtils.h" michael@0: michael@0: #include "mozilla/unused.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsICycleCollectorListener.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsGZFileWriter.h" michael@0: #include "nsJSEnvironment.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIFile.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #define getpid _getpid michael@0: #else michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef XP_UNIX michael@0: #define MOZ_SUPPORTS_FIFO 1 michael@0: #endif michael@0: michael@0: #if defined(XP_LINUX) || defined(__FreeBSD__) michael@0: #define MOZ_SUPPORTS_RT_SIGNALS 1 michael@0: #endif michael@0: michael@0: #if defined(MOZ_SUPPORTS_RT_SIGNALS) michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #if defined(MOZ_SUPPORTS_FIFO) michael@0: #include "mozilla/Preferences.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace { michael@0: michael@0: class DumpMemoryInfoToTempDirRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, michael@0: bool aMinimizeMemoryUsage) michael@0: : mIdentifier(aIdentifier) michael@0: , mMinimizeMemoryUsage(aMinimizeMemoryUsage) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsCOMPtr dumper = do_GetService("@mozilla.org/memory-info-dumper;1"); michael@0: dumper->DumpMemoryInfoToTempDir(mIdentifier, mMinimizeMemoryUsage); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: const nsString mIdentifier; michael@0: const bool mMinimizeMemoryUsage; michael@0: }; michael@0: michael@0: class GCAndCCLogDumpRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: GCAndCCLogDumpRunnable(const nsAString& aIdentifier, michael@0: bool aDumpAllTraces, michael@0: bool aDumpChildProcesses) michael@0: : mIdentifier(aIdentifier) michael@0: , mDumpAllTraces(aDumpAllTraces) michael@0: , mDumpChildProcesses(aDumpChildProcesses) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsCOMPtr dumper = michael@0: do_GetService("@mozilla.org/memory-info-dumper;1"); michael@0: michael@0: nsString ccLogPath, gcLogPath; michael@0: dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces, michael@0: mDumpChildProcesses, gcLogPath, ccLogPath); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: const nsString mIdentifier; michael@0: const bool mDumpAllTraces; michael@0: const bool mDumpChildProcesses; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: #if defined(MOZ_SUPPORTS_RT_SIGNALS) // { michael@0: namespace { michael@0: michael@0: /* michael@0: * The following code supports dumping about:memory upon receiving a signal. michael@0: * michael@0: * We listen for the following signals: michael@0: * michael@0: * - SIGRTMIN: Dump our memory reporters (and those of our child michael@0: * processes), michael@0: * - SIGRTMIN + 1: Dump our memory reporters (and those of our child michael@0: * processes) after minimizing memory usage, and michael@0: * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes. michael@0: * michael@0: * When we receive one of these signals, we write the signal number to a pipe. michael@0: * The IO thread then notices that the pipe has been written to, and kicks off michael@0: * the appropriate task on the main thread. michael@0: * michael@0: * This scheme is similar to using signalfd(), except it's portable and it michael@0: * doesn't require the use of sigprocmask, which is problematic because it michael@0: * masks signals received by child processes. michael@0: * michael@0: * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. michael@0: * But that uses libevent, which does not handle the realtime signals (bug michael@0: * 794074). michael@0: */ michael@0: michael@0: // It turns out that at least on some systems, SIGRTMIN is not a compile-time michael@0: // constant, so these have to be set at runtime. michael@0: static uint8_t sDumpAboutMemorySignum; // SIGRTMIN michael@0: static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1 michael@0: static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2 michael@0: michael@0: void doMemoryReport(const uint8_t recvSig) michael@0: { michael@0: // Dump our memory reports (but run this on the main thread!). michael@0: bool doMMUFirst = recvSig == sDumpAboutMemoryAfterMMUSignum; michael@0: LOG("SignalWatcher(sig %d) dispatching memory report runnable.", recvSig); michael@0: nsRefPtr runnable = michael@0: new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(), michael@0: doMMUFirst); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: michael@0: void doGCCCDump(const uint8_t recvSig) michael@0: { michael@0: LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", recvSig); michael@0: // Dump GC and CC logs (from the main thread). michael@0: nsRefPtr runnable = michael@0: new GCAndCCLogDumpRunnable( michael@0: /* identifier = */ EmptyString(), michael@0: /* allTraces = */ true, michael@0: /* dumpChildProcesses = */ true); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: #endif // MOZ_SUPPORTS_RT_SIGNALS } michael@0: michael@0: #if defined(MOZ_SUPPORTS_FIFO) // { michael@0: namespace { michael@0: michael@0: void doMemoryReport(const nsCString& inputStr) michael@0: { michael@0: bool doMMUMemoryReport = inputStr == NS_LITERAL_CSTRING("minimize memory report"); michael@0: LOG("FifoWatcher(command:%s) dispatching memory report runnable.", inputStr.get()); michael@0: nsRefPtr runnable = michael@0: new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(), michael@0: doMMUMemoryReport); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: michael@0: void doGCCCDump(const nsCString& inputStr) michael@0: { michael@0: bool doAllTracesGCCCDump = inputStr == NS_LITERAL_CSTRING("gc log"); michael@0: LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.", inputStr.get()); michael@0: nsRefPtr runnable = michael@0: new GCAndCCLogDumpRunnable(/* identifier = */ EmptyString(), michael@0: doAllTracesGCCCDump, michael@0: /* dumpChildProcesses = */ true); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: michael@0: bool SetupFifo() michael@0: { michael@0: static bool fifoCallbacksRegistered = false; michael@0: michael@0: if (!FifoWatcher::MaybeCreate()) { michael@0: return false; michael@0: } michael@0: michael@0: MOZ_ASSERT(!fifoCallbacksRegistered, michael@0: "FifoWatcher callbacks should be registered only once"); michael@0: michael@0: FifoWatcher* fw = FifoWatcher::GetSingleton(); michael@0: // Dump our memory reports (but run this on the main thread!). michael@0: fw->RegisterCallback(NS_LITERAL_CSTRING("memory report"), michael@0: doMemoryReport); michael@0: fw->RegisterCallback(NS_LITERAL_CSTRING("minimize memory report"), michael@0: doMemoryReport); michael@0: // Dump GC and CC logs (from the main thread). michael@0: fw->RegisterCallback(NS_LITERAL_CSTRING("gc log"), michael@0: doGCCCDump); michael@0: fw->RegisterCallback(NS_LITERAL_CSTRING("abbreviated gc log"), michael@0: doGCCCDump); michael@0: michael@0: fifoCallbacksRegistered = true; michael@0: return true; michael@0: } michael@0: michael@0: void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) michael@0: { michael@0: LOG("%s changed", FifoWatcher::kPrefName); michael@0: if (SetupFifo()) { michael@0: Preferences::UnregisterCallback(OnFifoEnabledChange, michael@0: FifoWatcher::kPrefName, michael@0: nullptr); michael@0: } michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: #endif // MOZ_SUPPORTS_FIFO } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper) michael@0: michael@0: nsMemoryInfoDumper::nsMemoryInfoDumper() michael@0: { michael@0: } michael@0: michael@0: nsMemoryInfoDumper::~nsMemoryInfoDumper() michael@0: { michael@0: } michael@0: michael@0: /* static */ void michael@0: nsMemoryInfoDumper::Initialize() michael@0: { michael@0: #if defined(MOZ_SUPPORTS_RT_SIGNALS) michael@0: SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton(); michael@0: michael@0: // Dump memory reporters (and those of our child processes) michael@0: sDumpAboutMemorySignum = SIGRTMIN; michael@0: sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport); michael@0: // Dump our memory reporters after minimizing memory usage michael@0: sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1; michael@0: sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport); michael@0: // Dump the GC and CC logs in this and our child processes. michael@0: sGCAndCCDumpSignum = SIGRTMIN + 2; michael@0: sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump); michael@0: #endif michael@0: michael@0: #if defined(MOZ_SUPPORTS_FIFO) michael@0: if (!SetupFifo()) { michael@0: // NB: This gets loaded early enough that it's possible there is a user pref michael@0: // set to enable the fifo watcher that has not been loaded yet. Register michael@0: // to attempt to initialize if the fifo watcher becomes enabled by michael@0: // a user pref. michael@0: Preferences::RegisterCallback(OnFifoEnabledChange, michael@0: FifoWatcher::kPrefName, michael@0: nullptr); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static void michael@0: EnsureNonEmptyIdentifier(nsAString& aIdentifier) michael@0: { michael@0: if (!aIdentifier.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: // If the identifier is empty, set it to the number of whole seconds since the michael@0: // epoch. This identifier will appear in the files that this process michael@0: // generates and also the files generated by this process's children, allowing michael@0: // us to identify which files are from the same memory report request. michael@0: aIdentifier.AppendInt(static_cast(PR_Now()) / 1000000); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryInfoDumper::DumpGCAndCCLogsToFile(const nsAString& aIdentifier, michael@0: bool aDumpAllTraces, michael@0: bool aDumpChildProcesses, michael@0: nsAString& aGCLogPath, michael@0: nsAString& aCCLogPath) michael@0: { michael@0: nsString identifier(aIdentifier); michael@0: EnsureNonEmptyIdentifier(identifier); michael@0: michael@0: if (aDumpChildProcesses) { michael@0: nsTArray children; michael@0: ContentParent::GetAll(children); michael@0: for (uint32_t i = 0; i < children.Length(); i++) { michael@0: unused << children[i]->SendDumpGCAndCCLogsToFile( michael@0: identifier, aDumpAllTraces, aDumpChildProcesses); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr logger = michael@0: do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); michael@0: logger->SetFilenameIdentifier(identifier); michael@0: michael@0: if (aDumpAllTraces) { michael@0: nsCOMPtr allTracesLogger; michael@0: logger->AllTraces(getter_AddRefs(allTracesLogger)); michael@0: logger = allTracesLogger; michael@0: } michael@0: michael@0: nsJSContext::CycleCollectNow(logger); michael@0: michael@0: logger->GetGcLogPath(aGCLogPath); michael@0: logger->GetCcLogPath(aCCLogPath); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: #define DUMP(o, s) \ michael@0: do { \ michael@0: nsresult rv = (o)->Write(s); \ michael@0: if (NS_WARN_IF(NS_FAILED(rv))) \ michael@0: return rv; \ michael@0: } while (0) michael@0: michael@0: class DumpReportCallback MOZ_FINAL : public nsIHandleReportCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: DumpReportCallback(nsGZFileWriter* aWriter) michael@0: : mIsFirst(true) michael@0: , mWriter(aWriter) michael@0: {} michael@0: michael@0: NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath, michael@0: int32_t aKind, int32_t aUnits, int64_t aAmount, michael@0: const nsACString &aDescription, michael@0: nsISupports *aData) michael@0: { michael@0: if (mIsFirst) { michael@0: DUMP(mWriter, "["); michael@0: mIsFirst = false; michael@0: } else { michael@0: DUMP(mWriter, ","); michael@0: } michael@0: michael@0: nsAutoCString process; michael@0: if (aProcess.IsEmpty()) { michael@0: // If the process is empty, the report originated with the process doing michael@0: // the dumping. In that case, generate the process identifier, which is of michael@0: // the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we don't michael@0: // have a process name. If we're the main process, we let $PROCESS_NAME be michael@0: // "Main Process". michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // We're the main process. michael@0: process.AssignLiteral("Main Process"); michael@0: } else if (ContentChild *cc = ContentChild::GetSingleton()) { michael@0: // Try to get the process name from ContentChild. michael@0: cc->GetProcessName(process); michael@0: } michael@0: ContentChild::AppendProcessId(process); michael@0: michael@0: } else { michael@0: // Otherwise, the report originated with another process and already has a michael@0: // process name. Just use that. michael@0: process = aProcess; michael@0: } michael@0: michael@0: DUMP(mWriter, "\n {\"process\": \""); michael@0: DUMP(mWriter, process); michael@0: michael@0: DUMP(mWriter, "\", \"path\": \""); michael@0: nsCString path(aPath); michael@0: path.ReplaceSubstring("\\", "\\\\"); /* --> \\ */ michael@0: path.ReplaceSubstring("\"", "\\\""); // " --> \" michael@0: DUMP(mWriter, path); michael@0: michael@0: DUMP(mWriter, "\", \"kind\": "); michael@0: DUMP(mWriter, nsPrintfCString("%d", aKind)); michael@0: michael@0: DUMP(mWriter, ", \"units\": "); michael@0: DUMP(mWriter, nsPrintfCString("%d", aUnits)); michael@0: michael@0: DUMP(mWriter, ", \"amount\": "); michael@0: DUMP(mWriter, nsPrintfCString("%lld", aAmount)); michael@0: michael@0: nsCString description(aDescription); michael@0: description.ReplaceSubstring("\\", "\\\\"); /* --> \\ */ michael@0: description.ReplaceSubstring("\"", "\\\""); // " --> \" michael@0: description.ReplaceSubstring("\n", "\\n"); // --> \n michael@0: DUMP(mWriter, ", \"description\": \""); michael@0: DUMP(mWriter, description); michael@0: DUMP(mWriter, "\"}"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: bool mIsFirst; michael@0: nsRefPtr mWriter; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(DumpReportCallback, nsIHandleReportCallback) michael@0: michael@0: } // namespace mozilla michael@0: michael@0: static void michael@0: MakeFilename(const char *aPrefix, const nsAString &aIdentifier, michael@0: const char *aSuffix, nsACString &aResult) michael@0: { michael@0: aResult = nsPrintfCString("%s-%s-%d.%s", michael@0: aPrefix, michael@0: NS_ConvertUTF16toUTF8(aIdentifier).get(), michael@0: getpid(), aSuffix); michael@0: } michael@0: michael@0: #ifdef MOZ_DMD michael@0: struct DMDWriteState michael@0: { michael@0: static const size_t kBufSize = 4096; michael@0: char mBuf[kBufSize]; michael@0: nsRefPtr mGZWriter; michael@0: michael@0: DMDWriteState(nsGZFileWriter *aGZWriter) michael@0: : mGZWriter(aGZWriter) michael@0: {} michael@0: }; michael@0: michael@0: static void michael@0: DMDWrite(void* aState, const char* aFmt, va_list ap) michael@0: { michael@0: DMDWriteState *state = (DMDWriteState*)aState; michael@0: vsnprintf(state->mBuf, state->kBufSize, aFmt, ap); michael@0: unused << state->mGZWriter->Write(state->mBuf); michael@0: } michael@0: #endif michael@0: michael@0: static nsresult michael@0: DumpHeader(nsIGZFileWriter* aWriter) michael@0: { michael@0: // Increment this number if the format changes. michael@0: // michael@0: // This is the first write to the file, and it causes |aWriter| to allocate michael@0: // over 200 KiB of memory. michael@0: // michael@0: DUMP(aWriter, "{\n \"version\": 1,\n"); michael@0: michael@0: DUMP(aWriter, " \"hasMozMallocUsableSize\": "); michael@0: michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: if (NS_WARN_IF(!mgr)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: DUMP(aWriter, mgr->GetHasMozMallocUsableSize() ? "true" : "false"); michael@0: DUMP(aWriter, ",\n"); michael@0: DUMP(aWriter, " \"reports\": "); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: DumpFooter(nsIGZFileWriter* aWriter) michael@0: { michael@0: DUMP(aWriter, "\n ]\n}\n"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class TempDirMemoryFinishCallback MOZ_FINAL : public nsIFinishReportingCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: TempDirMemoryFinishCallback(nsGZFileWriter *aWriter, michael@0: nsIFile *aTmpFile, michael@0: const nsCString &aFilename, michael@0: const nsString &aIdentifier) michael@0: : mrWriter(aWriter) michael@0: , mrTmpFile(aTmpFile) michael@0: , mrFilename(aFilename) michael@0: , mIdentifier(aIdentifier) michael@0: {} michael@0: michael@0: NS_IMETHOD Callback(nsISupports *aData); michael@0: michael@0: private: michael@0: nsRefPtr mrWriter; michael@0: nsCOMPtr mrTmpFile; michael@0: nsCString mrFilename; michael@0: nsString mIdentifier; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(TempDirMemoryFinishCallback, nsIFinishReportingCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier, michael@0: bool aMinimizeMemoryUsage) michael@0: { michael@0: nsString identifier(aIdentifier); michael@0: EnsureNonEmptyIdentifier(identifier); michael@0: michael@0: #ifdef MOZ_DMD michael@0: // Clear DMD's reportedness state before running the memory reporters, to michael@0: // avoid spurious twice-reported warnings. michael@0: dmd::ClearReports(); michael@0: #endif michael@0: michael@0: // Open a new file named something like michael@0: // michael@0: // incomplete-memory-report--.json.gz michael@0: // michael@0: // in NS_OS_TEMP_DIR for writing. When we're finished writing the report, michael@0: // we'll rename this file and get rid of the "incomplete-" prefix. michael@0: // michael@0: // We do this because we don't want scripts which poll the filesystem michael@0: // looking for memory report dumps to grab a file before we're finished michael@0: // writing to it. michael@0: michael@0: // Note that |mrFilename| is missing the "incomplete-" prefix; we'll tack michael@0: // that on in a moment. michael@0: nsCString mrFilename; michael@0: // The "unified" indicates that we merge the memory reports from all michael@0: // processes and write out one file, rather than a separate file for michael@0: // each process as was the case before bug 946407. This is so that michael@0: // the get_about_memory.py script in the B2G repository can michael@0: // determine when it's done waiting for files to appear. michael@0: MakeFilename("unified-memory-report", identifier, "json.gz", mrFilename); michael@0: michael@0: nsCOMPtr mrTmpFile; michael@0: nsresult rv; michael@0: // In Android case, this function will open a file named aFilename under michael@0: // specific folder (/data/local/tmp/memory-reports). Otherwise, it will michael@0: // open a file named aFilename under "NS_OS_TEMP_DIR". michael@0: rv = nsDumpUtils::OpenTempFile(NS_LITERAL_CSTRING("incomplete-") + michael@0: mrFilename, michael@0: getter_AddRefs(mrTmpFile), michael@0: NS_LITERAL_CSTRING("memory-reports")); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: nsRefPtr mrWriter = new nsGZFileWriter(); michael@0: rv = mrWriter->Init(mrTmpFile); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Dump the memory reports to the file. michael@0: rv = DumpHeader(mrWriter); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Process reporters. michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: nsRefPtr dumpReport = new DumpReportCallback(mrWriter); michael@0: nsRefPtr finishReport = michael@0: new TempDirMemoryFinishCallback(mrWriter, mrTmpFile, mrFilename, identifier); michael@0: rv = mgr->GetReportsExtended(dumpReport, nullptr, michael@0: finishReport, nullptr, michael@0: aMinimizeMemoryUsage, michael@0: identifier); michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef MOZ_DMD michael@0: nsresult michael@0: nsMemoryInfoDumper::DumpDMD(const nsAString &aIdentifier) michael@0: { michael@0: if (!dmd::IsEnabled()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: // Create a filename like dmd--.txt.gz, which will be used michael@0: // if DMD is enabled. michael@0: nsCString dmdFilename; michael@0: MakeFilename("dmd", aIdentifier, "txt.gz", dmdFilename); michael@0: michael@0: // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing, michael@0: // and dump DMD output to it. This must occur after the memory reporters michael@0: // have been run (above), but before the memory-reports file has been michael@0: // renamed (so scripts can detect the DMD file, if present). michael@0: michael@0: nsCOMPtr dmdFile; michael@0: rv = nsDumpUtils::OpenTempFile(dmdFilename, michael@0: getter_AddRefs(dmdFile), michael@0: NS_LITERAL_CSTRING("memory-reports")); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: nsRefPtr dmdWriter = new nsGZFileWriter(); michael@0: rv = dmdWriter->Init(dmdFile); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Dump DMD output to the file. michael@0: michael@0: DMDWriteState state(dmdWriter); michael@0: dmd::Writer w(DMDWrite, &state); michael@0: dmd::Dump(w); michael@0: michael@0: rv = dmdWriter->Finish(); michael@0: NS_WARN_IF(NS_FAILED(rv)); michael@0: return rv; michael@0: } michael@0: #endif // MOZ_DMD michael@0: michael@0: NS_IMETHODIMP michael@0: TempDirMemoryFinishCallback::Callback(nsISupports *aData) michael@0: { michael@0: nsresult rv; michael@0: michael@0: rv = DumpFooter(mrWriter); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // The call to Finish() deallocates the memory allocated by mrWriter's first michael@0: // DUMP() call (within DumpProcessMemoryReportsToGZFileWriter()). Because michael@0: // that memory was live while the memory reporters ran and thus measured by michael@0: // them -- by "heap-allocated" if nothing else -- we want DMD to see it as michael@0: // well. So we deliberately don't call Finish() until after DMD finishes. michael@0: rv = mrWriter->Finish(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Rename the memory reports file, now that we're done writing all the files. michael@0: // Its final name is "memory-report<-identifier>-.json.gz". michael@0: michael@0: nsCOMPtr mrFinalFile; michael@0: rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mrFinalFile)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: #ifdef ANDROID michael@0: rv = mrFinalFile->AppendNative(NS_LITERAL_CSTRING("memory-reports")); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: #endif michael@0: michael@0: rv = mrFinalFile->AppendNative(mrFilename); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = mrFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: nsAutoString mrActualFinalFilename; michael@0: rv = mrFinalFile->GetLeafName(mrActualFinalFilename); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = mrTmpFile->MoveTo(/* directory */ nullptr, mrActualFinalFilename); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Write a message to the console. michael@0: michael@0: nsCOMPtr cs = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: nsString path; michael@0: mrTmpFile->GetPath(path); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: nsString msg = NS_LITERAL_STRING( michael@0: "nsIMemoryInfoDumper dumped reports to "); michael@0: msg.Append(path); michael@0: return cs->LogStringMessage(msg.get()); michael@0: } michael@0: michael@0: // This dumps the JSON footer and closes the file, and then calls the given michael@0: // nsIFinishDumpingCallback. michael@0: class FinishReportingCallback MOZ_FINAL : public nsIFinishReportingCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: FinishReportingCallback(nsIFinishDumpingCallback* aFinishDumping, michael@0: nsISupports* aFinishDumpingData) michael@0: : mFinishDumping(aFinishDumping) michael@0: , mFinishDumpingData(aFinishDumpingData) michael@0: {} michael@0: michael@0: NS_IMETHOD Callback(nsISupports* aData) michael@0: { michael@0: nsCOMPtr writer = do_QueryInterface(aData); michael@0: NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = DumpFooter(writer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = writer->Finish(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mFinishDumping) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mFinishDumping->Callback(mFinishDumpingData); michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mFinishDumping; michael@0: nsCOMPtr mFinishDumpingData; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(FinishReportingCallback, nsIFinishReportingCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryInfoDumper::DumpMemoryReportsToNamedFile( michael@0: const nsAString& aFilename, michael@0: nsIFinishDumpingCallback* aFinishDumping, michael@0: nsISupports* aFinishDumpingData) michael@0: { michael@0: MOZ_ASSERT(!aFilename.IsEmpty()); michael@0: michael@0: // Create the file. michael@0: michael@0: nsCOMPtr mrFile; michael@0: nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(mrFile)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: mrFile->InitWithPath(aFilename); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: bool exists; michael@0: rv = mrFile->Exists(&exists); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: if (!exists) { michael@0: rv = mrFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: } michael@0: michael@0: // Write the memory reports to the file. michael@0: michael@0: nsRefPtr mrWriter = new nsGZFileWriter(); michael@0: rv = mrWriter->Init(mrFile); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = DumpHeader(mrWriter); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Process reports and finish up. michael@0: nsRefPtr dumpReport = new DumpReportCallback(mrWriter); michael@0: nsRefPtr finishReporting = michael@0: new FinishReportingCallback(aFinishDumping, aFinishDumpingData); michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: return mgr->GetReports(dumpReport, nullptr, finishReporting, mrWriter); michael@0: } michael@0: michael@0: #undef DUMP