Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / nsMemoryInfoDumper.cpp
blob28af408178e49cd420f5374083a48e52402f64b2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/JSONWriter.h"
8 #include "mozilla/UniquePtr.h"
9 #include "mozilla/nsMemoryInfoDumper.h"
10 #include "mozilla/DebugOnly.h"
11 #include "nsDumpUtils.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/dom/ContentParent.h"
15 #include "mozilla/dom/ContentChild.h"
16 #include "nsIConsoleService.h"
17 #include "nsCycleCollector.h"
18 #include "nsICycleCollectorListener.h"
19 #include "nsIMemoryReporter.h"
20 #include "nsDirectoryServiceDefs.h"
21 #include "nsGZFileWriter.h"
22 #include "nsJSEnvironment.h"
23 #include "nsPrintfCString.h"
24 #include "nsServiceManagerUtils.h"
25 #include "nsIFile.h"
27 #ifdef XP_WIN
28 # include <process.h>
29 # ifndef getpid
30 # define getpid _getpid
31 # endif
32 #else
33 # include <unistd.h>
34 #endif
36 #ifdef XP_UNIX
37 # define MOZ_SUPPORTS_FIFO 1
38 #endif
40 // Some Android devices seem to send RT signals to Firefox so we want to avoid
41 // consuming those as they're not user triggered.
42 #if !defined(ANDROID) && (defined(XP_LINUX) || defined(__FreeBSD__))
43 # define MOZ_SUPPORTS_RT_SIGNALS 1
44 #endif
46 #if defined(MOZ_SUPPORTS_RT_SIGNALS)
47 # include <fcntl.h>
48 # include <sys/types.h>
49 # include <sys/stat.h>
50 #endif
52 #if defined(MOZ_SUPPORTS_FIFO)
53 # include "mozilla/Preferences.h"
54 #endif
56 using namespace mozilla;
57 using namespace mozilla::dom;
59 namespace {
61 class DumpMemoryInfoToTempDirRunnable : public Runnable {
62 public:
63 DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, bool aAnonymize,
64 bool aMinimizeMemoryUsage)
65 : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"),
66 mIdentifier(aIdentifier),
67 mAnonymize(aAnonymize),
68 mMinimizeMemoryUsage(aMinimizeMemoryUsage) {}
70 NS_IMETHOD Run() override {
71 nsCOMPtr<nsIMemoryInfoDumper> dumper =
72 do_GetService("@mozilla.org/memory-info-dumper;1");
73 dumper->DumpMemoryInfoToTempDir(mIdentifier, mAnonymize,
74 mMinimizeMemoryUsage);
75 return NS_OK;
78 private:
79 const nsString mIdentifier;
80 const bool mAnonymize;
81 const bool mMinimizeMemoryUsage;
84 class GCAndCCLogDumpRunnable final : public Runnable,
85 public nsIDumpGCAndCCLogsCallback {
86 public:
87 NS_DECL_ISUPPORTS_INHERITED
89 GCAndCCLogDumpRunnable(const nsAString& aIdentifier, bool aDumpAllTraces,
90 bool aDumpChildProcesses)
91 : mozilla::Runnable("GCAndCCLogDumpRunnable"),
92 mIdentifier(aIdentifier),
93 mDumpAllTraces(aDumpAllTraces),
94 mDumpChildProcesses(aDumpChildProcesses) {}
96 NS_IMETHOD Run() override {
97 nsCOMPtr<nsIMemoryInfoDumper> dumper =
98 do_GetService("@mozilla.org/memory-info-dumper;1");
100 dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces,
101 mDumpChildProcesses, this);
102 return NS_OK;
105 NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
106 return NS_OK;
109 NS_IMETHOD OnFinish() override { return NS_OK; }
111 private:
112 ~GCAndCCLogDumpRunnable() = default;
114 const nsString mIdentifier;
115 const bool mDumpAllTraces;
116 const bool mDumpChildProcesses;
119 NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable,
120 nsIDumpGCAndCCLogsCallback)
122 } // namespace
124 #if defined(MOZ_SUPPORTS_RT_SIGNALS) // {
125 namespace {
128 * The following code supports dumping about:memory upon receiving a signal.
130 * We listen for the following signals:
132 * - SIGRTMIN: Dump our memory reporters (and those of our child
133 * processes),
134 * - SIGRTMIN + 1: Dump our memory reporters (and those of our child
135 * processes) after minimizing memory usage, and
136 * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes.
138 * When we receive one of these signals, we write the signal number to a pipe.
139 * The IO thread then notices that the pipe has been written to, and kicks off
140 * the appropriate task on the main thread.
142 * This scheme is similar to using signalfd(), except it's portable and it
143 * doesn't require the use of sigprocmask, which is problematic because it
144 * masks signals received by child processes.
146 * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
147 * But that uses libevent, which does not handle the realtime signals (bug
148 * 794074).
151 // It turns out that at least on some systems, SIGRTMIN is not a compile-time
152 // constant, so these have to be set at runtime.
153 static uint8_t sDumpAboutMemorySignum; // SIGRTMIN
154 static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1
155 static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2
157 void doMemoryReport(const uint8_t aRecvSig) {
158 // Dump our memory reports (but run this on the main thread!).
159 bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum;
160 LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig);
161 RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
162 new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns,
163 /* anonymize = */ false, minimize);
164 NS_DispatchToMainThread(runnable);
167 void doGCCCDump(const uint8_t aRecvSig) {
168 LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig);
169 // Dump GC and CC logs (from the main thread).
170 RefPtr<GCAndCCLogDumpRunnable> runnable =
171 new GCAndCCLogDumpRunnable(/* identifier = */ u""_ns,
172 /* allTraces = */ true,
173 /* dumpChildProcesses = */ true);
174 NS_DispatchToMainThread(runnable);
177 } // namespace
178 #endif // MOZ_SUPPORTS_RT_SIGNALS }
180 #if defined(MOZ_SUPPORTS_FIFO) // {
181 namespace {
183 void doMemoryReport(const nsCString& aInputStr) {
184 bool minimize = aInputStr.EqualsLiteral("minimize memory report");
185 LOG("FifoWatcher(command:%s) dispatching memory report runnable.",
186 aInputStr.get());
187 RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
188 new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns,
189 /* anonymize = */ false, minimize);
190 NS_DispatchToMainThread(runnable);
193 void doGCCCDump(const nsCString& aInputStr) {
194 bool doAllTracesGCCCDump = aInputStr.EqualsLiteral("gc log");
195 LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.",
196 aInputStr.get());
197 RefPtr<GCAndCCLogDumpRunnable> runnable = new GCAndCCLogDumpRunnable(
198 /* identifier = */ u""_ns, doAllTracesGCCCDump,
199 /* dumpChildProcesses = */ true);
200 NS_DispatchToMainThread(runnable);
203 bool SetupFifo() {
204 # ifdef DEBUG
205 static bool fifoCallbacksRegistered = false;
206 # endif
208 if (!FifoWatcher::MaybeCreate()) {
209 return false;
212 MOZ_ASSERT(!fifoCallbacksRegistered,
213 "FifoWatcher callbacks should be registered only once");
215 FifoWatcher* fw = FifoWatcher::GetSingleton();
216 // Dump our memory reports (but run this on the main thread!).
217 fw->RegisterCallback("memory report"_ns, doMemoryReport);
218 fw->RegisterCallback("minimize memory report"_ns, doMemoryReport);
219 // Dump GC and CC logs (from the main thread).
220 fw->RegisterCallback("gc log"_ns, doGCCCDump);
221 fw->RegisterCallback("abbreviated gc log"_ns, doGCCCDump);
223 # ifdef DEBUG
224 fifoCallbacksRegistered = true;
225 # endif
226 return true;
229 void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) {
230 LOG("%s changed", FifoWatcher::kPrefName);
231 if (SetupFifo()) {
232 Preferences::UnregisterCallback(OnFifoEnabledChange,
233 FifoWatcher::kPrefName);
237 } // namespace
238 #endif // MOZ_SUPPORTS_FIFO }
240 NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper)
242 nsMemoryInfoDumper::nsMemoryInfoDumper() = default;
244 nsMemoryInfoDumper::~nsMemoryInfoDumper() = default;
246 /* static */
247 void nsMemoryInfoDumper::Initialize() {
248 #if defined(MOZ_SUPPORTS_RT_SIGNALS)
249 SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
251 // Dump memory reporters (and those of our child processes)
252 sDumpAboutMemorySignum = SIGRTMIN;
253 sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport);
254 // Dump our memory reporters after minimizing memory usage
255 sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
256 sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport);
257 // Dump the GC and CC logs in this and our child processes.
258 sGCAndCCDumpSignum = SIGRTMIN + 2;
259 sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump);
260 #endif
262 #if defined(MOZ_SUPPORTS_FIFO)
263 if (!SetupFifo()) {
264 // NB: This gets loaded early enough that it's possible there is a user pref
265 // set to enable the fifo watcher that has not been loaded yet. Register
266 // to attempt to initialize if the fifo watcher becomes enabled by
267 // a user pref.
268 Preferences::RegisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName);
270 #endif
273 static void EnsureNonEmptyIdentifier(nsAString& aIdentifier) {
274 if (!aIdentifier.IsEmpty()) {
275 return;
278 // If the identifier is empty, set it to the number of whole seconds since the
279 // epoch. This identifier will appear in the files that this process
280 // generates and also the files generated by this process's children, allowing
281 // us to identify which files are from the same memory report request.
282 aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
285 // Use XPCOM refcounting to fire |onFinish| when all reference-holders
286 // (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself)
287 // have gone away.
288 class nsDumpGCAndCCLogsCallbackHolder final
289 : public nsIDumpGCAndCCLogsCallback {
290 public:
291 NS_DECL_ISUPPORTS
293 explicit nsDumpGCAndCCLogsCallbackHolder(
294 nsIDumpGCAndCCLogsCallback* aCallback)
295 : mCallback(aCallback) {}
297 NS_IMETHOD OnFinish() override { return NS_ERROR_UNEXPECTED; }
299 NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
300 return mCallback->OnDump(aGCLog, aCCLog, aIsParent);
303 private:
304 ~nsDumpGCAndCCLogsCallbackHolder() { Unused << mCallback->OnFinish(); }
306 nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
309 NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback)
311 NS_IMETHODIMP
312 nsMemoryInfoDumper::DumpGCAndCCLogsToFile(
313 const nsAString& aIdentifier, bool aDumpAllTraces, bool aDumpChildProcesses,
314 nsIDumpGCAndCCLogsCallback* aCallback) {
315 nsString identifier(aIdentifier);
316 EnsureNonEmptyIdentifier(identifier);
317 nsCOMPtr<nsIDumpGCAndCCLogsCallback> callbackHolder =
318 new nsDumpGCAndCCLogsCallbackHolder(aCallback);
320 if (aDumpChildProcesses) {
321 nsTArray<ContentParent*> children;
322 ContentParent::GetAll(children);
323 for (uint32_t i = 0; i < children.Length(); i++) {
324 ContentParent* cp = children[i];
325 nsCOMPtr<nsICycleCollectorLogSink> logSink =
326 nsCycleCollector_createLogSink();
328 logSink->SetFilenameIdentifier(identifier);
329 logSink->SetProcessIdentifier(cp->Pid());
331 Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink,
332 callbackHolder);
336 nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
338 if (aDumpAllTraces) {
339 nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
340 logger->AllTraces(getter_AddRefs(allTracesLogger));
341 logger = allTracesLogger;
344 nsCOMPtr<nsICycleCollectorLogSink> logSink;
345 logger->GetLogSink(getter_AddRefs(logSink));
347 logSink->SetFilenameIdentifier(identifier);
349 nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger);
351 nsCOMPtr<nsIFile> gcLog, ccLog;
352 logSink->GetGcLog(getter_AddRefs(gcLog));
353 logSink->GetCcLog(getter_AddRefs(ccLog));
354 callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true);
356 return NS_OK;
359 NS_IMETHODIMP
360 nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
361 nsICycleCollectorLogSink* aSink) {
362 nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
364 if (aDumpAllTraces) {
365 nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
366 logger->AllTraces(getter_AddRefs(allTracesLogger));
367 logger = allTracesLogger;
370 logger->SetLogSink(aSink);
372 nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger);
374 return NS_OK;
377 static void MakeFilename(const char* aPrefix, const nsAString& aIdentifier,
378 int aPid, const char* aSuffix, nsACString& aResult) {
379 aResult =
380 nsPrintfCString("%s-%s-%d.%s", aPrefix,
381 NS_ConvertUTF16toUTF8(aIdentifier).get(), aPid, aSuffix);
384 // This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
385 // the following two problems:
386 // - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
387 // - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
388 class GZWriterWrapper final : public JSONWriteFunc {
389 public:
390 explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {}
392 void Write(const Span<const char>& aStr) final {
393 // Ignore any failure because JSONWriteFunc doesn't have a mechanism for
394 // handling errors.
395 Unused << mGZWriter->Write(aStr.data(), aStr.size());
398 nsresult Finish() { return mGZWriter->Finish(); }
400 private:
401 RefPtr<nsGZFileWriter> mGZWriter;
404 // We need two callbacks: one that handles reports, and one that is called at
405 // the end of reporting. Both the callbacks need access to the same JSONWriter,
406 // so we implement both of them in this one class.
407 class HandleReportAndFinishReportingCallbacks final
408 : public nsIHandleReportCallback,
409 public nsIFinishReportingCallback {
410 public:
411 NS_DECL_ISUPPORTS
413 HandleReportAndFinishReportingCallbacks(
414 UniquePtr<JSONWriter> aWriter, nsIFinishDumpingCallback* aFinishDumping,
415 nsISupports* aFinishDumpingData)
416 : mWriter(std::move(aWriter)),
417 mFinishDumping(aFinishDumping),
418 mFinishDumpingData(aFinishDumpingData) {}
420 // This is the callback for nsIHandleReportCallback.
421 NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
422 int32_t aKind, int32_t aUnits, int64_t aAmount,
423 const nsACString& aDescription,
424 nsISupports* aData) override {
425 nsAutoCString process;
426 if (aProcess.IsEmpty()) {
427 // If the process is empty, the report originated with the process doing
428 // the dumping. In that case, generate the process identifier, which is
429 // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we
430 // don't have a process name. If we're the main process, we let
431 // $PROCESS_NAME be "Main Process".
433 // `appendAboutMemoryMain()` in aboutMemory.js does much the same thing
434 // for live memory reports.
435 if (XRE_IsParentProcess()) {
436 // We're the main process.
437 process.AssignLiteral("Main Process");
438 } else if (ContentChild* cc = ContentChild::GetSingleton()) {
439 // Try to get the process name from ContentChild.
440 cc->GetProcessName(process);
442 ContentChild::AppendProcessId(process);
444 } else {
445 // Otherwise, the report originated with another process and already has a
446 // process name. Just use that.
447 process = aProcess;
450 mWriter->StartObjectElement();
452 mWriter->StringProperty("process", process);
453 mWriter->StringProperty("path", PromiseFlatCString(aPath));
454 mWriter->IntProperty("kind", aKind);
455 mWriter->IntProperty("units", aUnits);
456 mWriter->IntProperty("amount", aAmount);
457 mWriter->StringProperty("description", PromiseFlatCString(aDescription));
459 mWriter->EndObject();
461 return NS_OK;
464 // This is the callback for nsIFinishReportingCallback.
465 NS_IMETHOD Callback(nsISupports* aData) override {
466 mWriter->EndArray(); // end of "reports" array
467 mWriter->End();
469 // The call to Finish() deallocates the memory allocated by the first Write
470 // call. Because that memory was live while the memory reporters ran and
471 // was measured by them -- by "heap-allocated" if nothing else -- we want
472 // DMD to see it as well. So we deliberately don't call Finish() until
473 // after DMD finishes.
474 nsresult rv = static_cast<GZWriterWrapper&>(mWriter->WriteFunc()).Finish();
475 NS_ENSURE_SUCCESS(rv, rv);
477 if (!mFinishDumping) {
478 return NS_OK;
481 return mFinishDumping->Callback(mFinishDumpingData);
484 private:
485 ~HandleReportAndFinishReportingCallbacks() = default;
487 UniquePtr<JSONWriter> mWriter;
488 nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
489 nsCOMPtr<nsISupports> mFinishDumpingData;
492 NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
493 nsIHandleReportCallback, nsIFinishReportingCallback)
495 class TempDirFinishCallback final : public nsIFinishDumpingCallback {
496 public:
497 NS_DECL_ISUPPORTS
499 TempDirFinishCallback(nsIFile* aReportsTmpFile,
500 const nsCString& aReportsFinalFilename)
501 : mReportsTmpFile(aReportsTmpFile),
502 mReportsFilename(aReportsFinalFilename) {}
504 NS_IMETHOD Callback(nsISupports* aData) override {
505 // Rename the memory reports file, now that we're done writing all the
506 // files. Its final name is "memory-report<-identifier>-<pid>.json.gz".
508 nsCOMPtr<nsIFile> reportsFinalFile;
509 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
510 getter_AddRefs(reportsFinalFile));
511 if (NS_WARN_IF(NS_FAILED(rv))) {
512 return rv;
515 #ifdef ANDROID
516 rv = reportsFinalFile->AppendNative("memory-reports"_ns);
517 if (NS_WARN_IF(NS_FAILED(rv))) {
518 return rv;
520 #endif
522 rv = reportsFinalFile->AppendNative(mReportsFilename);
523 if (NS_WARN_IF(NS_FAILED(rv))) {
524 return rv;
527 rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
528 if (NS_WARN_IF(NS_FAILED(rv))) {
529 return rv;
532 nsAutoString reportsFinalFilename;
533 rv = reportsFinalFile->GetLeafName(reportsFinalFilename);
534 if (NS_WARN_IF(NS_FAILED(rv))) {
535 return rv;
538 rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, reportsFinalFilename);
539 if (NS_WARN_IF(NS_FAILED(rv))) {
540 return rv;
543 // Write a message to the console.
545 nsCOMPtr<nsIConsoleService> cs =
546 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
547 if (NS_WARN_IF(NS_FAILED(rv))) {
548 return rv;
551 nsString path;
552 mReportsTmpFile->GetPath(path);
553 if (NS_WARN_IF(NS_FAILED(rv))) {
554 return rv;
557 nsString msg = u"nsIMemoryInfoDumper dumped reports to "_ns;
558 msg.Append(path);
559 return cs->LogStringMessage(msg.get());
562 private:
563 ~TempDirFinishCallback() = default;
565 nsCOMPtr<nsIFile> mReportsTmpFile;
566 nsCString mReportsFilename;
569 NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback)
571 static nsresult DumpMemoryInfoToFile(nsIFile* aReportsFile,
572 nsIFinishDumpingCallback* aFinishDumping,
573 nsISupports* aFinishDumpingData,
574 bool aAnonymize, bool aMinimizeMemoryUsage,
575 nsAString& aDMDIdentifier) {
576 RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
577 nsresult rv = gzWriter->Init(aReportsFile);
578 if (NS_WARN_IF(NS_FAILED(rv))) {
579 return rv;
581 auto jsonWriter =
582 MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
584 nsCOMPtr<nsIMemoryReporterManager> mgr =
585 do_GetService("@mozilla.org/memory-reporter-manager;1");
587 // This is the first write to the file, and it causes |aWriter| to allocate
588 // over 200 KiB of memory.
589 jsonWriter->Start();
591 // Increment this number if the format changes.
592 jsonWriter->IntProperty("version", 1);
593 jsonWriter->BoolProperty("hasMozMallocUsableSize",
594 mgr->GetHasMozMallocUsableSize());
595 jsonWriter->StartArrayProperty("reports");
598 RefPtr<HandleReportAndFinishReportingCallbacks>
599 handleReportAndFinishReporting =
600 new HandleReportAndFinishReportingCallbacks(
601 std::move(jsonWriter), aFinishDumping, aFinishDumpingData);
602 rv = mgr->GetReportsExtended(
603 handleReportAndFinishReporting, nullptr, handleReportAndFinishReporting,
604 nullptr, aAnonymize, aMinimizeMemoryUsage, aDMDIdentifier);
605 return rv;
608 NS_IMETHODIMP
609 nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
610 const nsAString& aFilename, nsIFinishDumpingCallback* aFinishDumping,
611 nsISupports* aFinishDumpingData, bool aAnonymize,
612 bool aMinimizeMemoryUsage) {
613 MOZ_ASSERT(!aFilename.IsEmpty());
615 // Create the file.
617 nsCOMPtr<nsIFile> reportsFile;
618 nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(reportsFile));
619 if (NS_WARN_IF(NS_FAILED(rv))) {
620 return rv;
623 reportsFile->InitWithPath(aFilename);
624 if (NS_WARN_IF(NS_FAILED(rv))) {
625 return rv;
628 bool exists;
629 rv = reportsFile->Exists(&exists);
630 if (NS_WARN_IF(NS_FAILED(rv))) {
631 return rv;
634 if (!exists) {
635 rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
636 if (NS_WARN_IF(NS_FAILED(rv))) {
637 return rv;
641 nsString dmdIdent;
642 return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData,
643 aAnonymize, aMinimizeMemoryUsage, dmdIdent);
646 NS_IMETHODIMP
647 nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
648 bool aAnonymize,
649 bool aMinimizeMemoryUsage) {
650 nsString identifier(aIdentifier);
651 EnsureNonEmptyIdentifier(identifier);
653 // Open a new file named something like
655 // incomplete-memory-report-<identifier>-<pid>.json.gz
657 // in NS_OS_TEMP_DIR for writing. When we're finished writing the report,
658 // we'll rename this file and get rid of the "incomplete-" prefix.
660 // We do this because we don't want scripts which poll the filesystem
661 // looking for memory report dumps to grab a file before we're finished
662 // writing to it.
664 // The "unified" indicates that we merge the memory reports from all
665 // processes and write out one file, rather than a separate file for
666 // each process as was the case before bug 946407. This is so that
667 // the get_about_memory.py script in the B2G repository can
668 // determine when it's done waiting for files to appear.
669 nsCString reportsFinalFilename;
670 MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
671 reportsFinalFilename);
673 nsCOMPtr<nsIFile> reportsTmpFile;
674 nsresult rv;
675 // In Android case, this function will open a file named aFilename under
676 // specific folder (/data/local/tmp/memory-reports). Otherwise, it will
677 // open a file named aFilename under "NS_OS_TEMP_DIR".
678 rv = nsDumpUtils::OpenTempFile("incomplete-"_ns + reportsFinalFilename,
679 getter_AddRefs(reportsTmpFile),
680 "memory-reports"_ns);
681 if (NS_WARN_IF(NS_FAILED(rv))) {
682 return rv;
685 RefPtr<TempDirFinishCallback> finishDumping =
686 new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
688 return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr,
689 aAnonymize, aMinimizeMemoryUsage, identifier);
692 #ifdef MOZ_DMD
693 dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton;
695 nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
696 FILE** aOutFile) {
697 if (!dmd::IsRunning()) {
698 *aOutFile = nullptr;
699 return NS_OK;
702 // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used
703 // if DMD is enabled.
704 nsCString dmdFilename;
705 MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
707 // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
708 // and dump DMD output to it. This must occur after the memory reporters
709 // have been run (above), but before the memory-reports file has been
710 // renamed (so scripts can detect the DMD file, if present).
712 nsresult rv;
713 nsCOMPtr<nsIFile> dmdFile;
714 rv = nsDumpUtils::OpenTempFile(dmdFilename, getter_AddRefs(dmdFile),
715 "memory-reports"_ns);
716 if (NS_WARN_IF(NS_FAILED(rv))) {
717 return rv;
719 rv = dmdFile->OpenANSIFileDesc("wb", aOutFile);
720 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed");
722 // Print the path, because on some platforms (e.g. Mac) it's not obvious.
723 dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().get());
725 return rv;
728 nsresult nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) {
729 RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
730 nsresult rv = gzWriter->InitANSIFileDesc(aFile);
731 if (NS_WARN_IF(NS_FAILED(rv))) {
732 return rv;
735 // Dump DMD's memory reports analysis to the file.
736 dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter));
738 rv = gzWriter->Finish();
739 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed");
740 return rv;
742 #endif // MOZ_DMD