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/. */
6 
7 #include "mozilla/JSONWriter.h"
8 #include "mozilla/UniquePtr.h"
9 #include "mozilla/nsMemoryInfoDumper.h"
10 #include "mozilla/DebugOnly.h"
11 #include "nsDumpUtils.h"
12 
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"
26 
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
35 
36 #ifdef XP_UNIX
37 #  define MOZ_SUPPORTS_FIFO 1
38 #endif
39 
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
45 
46 #if defined(MOZ_SUPPORTS_RT_SIGNALS)
47 #  include <fcntl.h>
48 #  include <sys/types.h>
49 #  include <sys/stat.h>
50 #endif
51 
52 #if defined(MOZ_SUPPORTS_FIFO)
53 #  include "mozilla/Preferences.h"
54 #endif
55 
56 using namespace mozilla;
57 using namespace mozilla::dom;
58 
59 namespace {
60 
61 class DumpMemoryInfoToTempDirRunnable : public Runnable {
62  public:
DumpMemoryInfoToTempDirRunnable(const nsAString & aIdentifier,bool aAnonymize,bool aMinimizeMemoryUsage)63   DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, bool aAnonymize,
64                                   bool aMinimizeMemoryUsage)
65       : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"),
66         mIdentifier(aIdentifier),
67         mAnonymize(aAnonymize),
68         mMinimizeMemoryUsage(aMinimizeMemoryUsage) {}
69 
Run()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;
76   }
77 
78  private:
79   const nsString mIdentifier;
80   const bool mAnonymize;
81   const bool mMinimizeMemoryUsage;
82 };
83 
84 class GCAndCCLogDumpRunnable final : public Runnable,
85                                      public nsIDumpGCAndCCLogsCallback {
86  public:
87   NS_DECL_ISUPPORTS_INHERITED
88 
GCAndCCLogDumpRunnable(const nsAString & aIdentifier,bool aDumpAllTraces,bool aDumpChildProcesses)89   GCAndCCLogDumpRunnable(const nsAString& aIdentifier, bool aDumpAllTraces,
90                          bool aDumpChildProcesses)
91       : mozilla::Runnable("GCAndCCLogDumpRunnable"),
92         mIdentifier(aIdentifier),
93         mDumpAllTraces(aDumpAllTraces),
94         mDumpChildProcesses(aDumpChildProcesses) {}
95 
Run()96   NS_IMETHOD Run() override {
97     nsCOMPtr<nsIMemoryInfoDumper> dumper =
98         do_GetService("@mozilla.org/memory-info-dumper;1");
99 
100     dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces,
101                                   mDumpChildProcesses, this);
102     return NS_OK;
103   }
104 
OnDump(nsIFile * aGCLog,nsIFile * aCCLog,bool aIsParent)105   NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
106     return NS_OK;
107   }
108 
OnFinish()109   NS_IMETHOD OnFinish() override { return NS_OK; }
110 
111  private:
112   ~GCAndCCLogDumpRunnable() = default;
113 
114   const nsString mIdentifier;
115   const bool mDumpAllTraces;
116   const bool mDumpChildProcesses;
117 };
118 
119 NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable,
120                             nsIDumpGCAndCCLogsCallback)
121 
122 }  // namespace
123 
124 #if defined(MOZ_SUPPORTS_RT_SIGNALS)  // {
125 namespace {
126 
127 /*
128  * The following code supports dumping about:memory upon receiving a signal.
129  *
130  * We listen for the following signals:
131  *
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.
137  *
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.
141  *
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.
145  *
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).
149  */
150 
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
156 
doMemoryReport(const uint8_t aRecvSig)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);
165 }
166 
doGCCCDump(const uint8_t aRecvSig)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);
175 }
176 
177 }  // namespace
178 #endif  // MOZ_SUPPORTS_RT_SIGNALS }
179 
180 #if defined(MOZ_SUPPORTS_FIFO)  // {
181 namespace {
182 
doMemoryReport(const nsCString & aInputStr)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);
191 }
192 
doGCCCDump(const nsCString & aInputStr)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);
201 }
202 
SetupFifo()203 bool SetupFifo() {
204 #  ifdef DEBUG
205   static bool fifoCallbacksRegistered = false;
206 #  endif
207 
208   if (!FifoWatcher::MaybeCreate()) {
209     return false;
210   }
211 
212   MOZ_ASSERT(!fifoCallbacksRegistered,
213              "FifoWatcher callbacks should be registered only once");
214 
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);
222 
223 #  ifdef DEBUG
224   fifoCallbacksRegistered = true;
225 #  endif
226   return true;
227 }
228 
OnFifoEnabledChange(const char *,void *)229 void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) {
230   LOG("%s changed", FifoWatcher::kPrefName);
231   if (SetupFifo()) {
232     Preferences::UnregisterCallback(OnFifoEnabledChange,
233                                     FifoWatcher::kPrefName);
234   }
235 }
236 
237 }  // namespace
238 #endif  // MOZ_SUPPORTS_FIFO }
239 
240 NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper)
241 
242 nsMemoryInfoDumper::nsMemoryInfoDumper() = default;
243 
244 nsMemoryInfoDumper::~nsMemoryInfoDumper() = default;
245 
246 /* static */
Initialize()247 void nsMemoryInfoDumper::Initialize() {
248 #if defined(MOZ_SUPPORTS_RT_SIGNALS)
249   SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
250 
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
261 
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);
269   }
270 #endif
271 }
272 
EnsureNonEmptyIdentifier(nsAString & aIdentifier)273 static void EnsureNonEmptyIdentifier(nsAString& aIdentifier) {
274   if (!aIdentifier.IsEmpty()) {
275     return;
276   }
277 
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);
283 }
284 
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
292 
nsDumpGCAndCCLogsCallbackHolder(nsIDumpGCAndCCLogsCallback * aCallback)293   explicit nsDumpGCAndCCLogsCallbackHolder(
294       nsIDumpGCAndCCLogsCallback* aCallback)
295       : mCallback(aCallback) {}
296 
OnFinish()297   NS_IMETHOD OnFinish() override { return NS_ERROR_UNEXPECTED; }
298 
OnDump(nsIFile * aGCLog,nsIFile * aCCLog,bool aIsParent)299   NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
300     return mCallback->OnDump(aGCLog, aCCLog, aIsParent);
301   }
302 
303  private:
~nsDumpGCAndCCLogsCallbackHolder()304   ~nsDumpGCAndCCLogsCallbackHolder() { Unused << mCallback->OnFinish(); }
305 
306   nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
307 };
308 
NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder,nsIDumpGCAndCCLogsCallback)309 NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback)
310 
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);
319 
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();
327 
328       logSink->SetFilenameIdentifier(identifier);
329       logSink->SetProcessIdentifier(cp->Pid());
330 
331       Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink,
332                                          callbackHolder);
333     }
334   }
335 
336   nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
337 
338   if (aDumpAllTraces) {
339     nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
340     logger->AllTraces(getter_AddRefs(allTracesLogger));
341     logger = allTracesLogger;
342   }
343 
344   nsCOMPtr<nsICycleCollectorLogSink> logSink;
345   logger->GetLogSink(getter_AddRefs(logSink));
346 
347   logSink->SetFilenameIdentifier(identifier);
348 
349   nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger);
350 
351   nsCOMPtr<nsIFile> gcLog, ccLog;
352   logSink->GetGcLog(getter_AddRefs(gcLog));
353   logSink->GetCcLog(getter_AddRefs(ccLog));
354   callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true);
355 
356   return NS_OK;
357 }
358 
359 NS_IMETHODIMP
DumpGCAndCCLogsToSink(bool aDumpAllTraces,nsICycleCollectorLogSink * aSink)360 nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
361                                           nsICycleCollectorLogSink* aSink) {
362   nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
363 
364   if (aDumpAllTraces) {
365     nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
366     logger->AllTraces(getter_AddRefs(allTracesLogger));
367     logger = allTracesLogger;
368   }
369 
370   logger->SetLogSink(aSink);
371 
372   nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger);
373 
374   return NS_OK;
375 }
376 
MakeFilename(const char * aPrefix,const nsAString & aIdentifier,int aPid,const char * aSuffix,nsACString & aResult)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);
382 }
383 
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 : public JSONWriteFunc {
389  public:
GZWriterWrapper(nsGZFileWriter * aGZWriter)390   explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {}
391 
Write(const Span<const char> & aStr)392   void Write(const Span<const char>& aStr) override {
393     // Ignore any failure because JSONWriteFunc doesn't have a mechanism for
394     // handling errors.
395     Unused << mGZWriter->Write(aStr.data(), aStr.size());
396   }
397 
Finish()398   nsresult Finish() { return mGZWriter->Finish(); }
399 
400  private:
401   RefPtr<nsGZFileWriter> mGZWriter;
402 };
403 
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
412 
HandleReportAndFinishReportingCallbacks(UniquePtr<JSONWriter> aWriter,nsIFinishDumpingCallback * aFinishDumping,nsISupports * aFinishDumpingData)413   HandleReportAndFinishReportingCallbacks(
414       UniquePtr<JSONWriter> aWriter, nsIFinishDumpingCallback* aFinishDumping,
415       nsISupports* aFinishDumpingData)
416       : mWriter(std::move(aWriter)),
417         mFinishDumping(aFinishDumping),
418         mFinishDumpingData(aFinishDumpingData) {}
419 
420   // This is the callback for nsIHandleReportCallback.
Callback(const nsACString & aProcess,const nsACString & aPath,int32_t aKind,int32_t aUnits,int64_t aAmount,const nsACString & aDescription,nsISupports * aData)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".
432       //
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);
441       }
442       ContentChild::AppendProcessId(process);
443 
444     } else {
445       // Otherwise, the report originated with another process and already has a
446       // process name.  Just use that.
447       process = aProcess;
448     }
449 
450     mWriter->StartObjectElement();
451     {
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));
458     }
459     mWriter->EndObject();
460 
461     return NS_OK;
462   }
463 
464   // This is the callback for nsIFinishReportingCallback.
Callback(nsISupports * aData)465   NS_IMETHOD Callback(nsISupports* aData) override {
466     mWriter->EndArray();  // end of "reports" array
467     mWriter->End();
468 
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);
476 
477     if (!mFinishDumping) {
478       return NS_OK;
479     }
480 
481     return mFinishDumping->Callback(mFinishDumpingData);
482   }
483 
484  private:
485   ~HandleReportAndFinishReportingCallbacks() = default;
486 
487   UniquePtr<JSONWriter> mWriter;
488   nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
489   nsCOMPtr<nsISupports> mFinishDumpingData;
490 };
491 
492 NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
493                   nsIHandleReportCallback, nsIFinishReportingCallback)
494 
495 class TempDirFinishCallback final : public nsIFinishDumpingCallback {
496  public:
497   NS_DECL_ISUPPORTS
498 
TempDirFinishCallback(nsIFile * aReportsTmpFile,const nsCString & aReportsFinalFilename)499   TempDirFinishCallback(nsIFile* aReportsTmpFile,
500                         const nsCString& aReportsFinalFilename)
501       : mReportsTmpFile(aReportsTmpFile),
502         mReportsFilename(aReportsFinalFilename) {}
503 
Callback(nsISupports * aData)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".
507 
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;
513     }
514 
515 #ifdef ANDROID
516     rv = reportsFinalFile->AppendNative("memory-reports"_ns);
517     if (NS_WARN_IF(NS_FAILED(rv))) {
518       return rv;
519     }
520 #endif
521 
522     rv = reportsFinalFile->AppendNative(mReportsFilename);
523     if (NS_WARN_IF(NS_FAILED(rv))) {
524       return rv;
525     }
526 
527     rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
528     if (NS_WARN_IF(NS_FAILED(rv))) {
529       return rv;
530     }
531 
532     nsAutoString reportsFinalFilename;
533     rv = reportsFinalFile->GetLeafName(reportsFinalFilename);
534     if (NS_WARN_IF(NS_FAILED(rv))) {
535       return rv;
536     }
537 
538     rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, reportsFinalFilename);
539     if (NS_WARN_IF(NS_FAILED(rv))) {
540       return rv;
541     }
542 
543     // Write a message to the console.
544 
545     nsCOMPtr<nsIConsoleService> cs =
546         do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
547     if (NS_WARN_IF(NS_FAILED(rv))) {
548       return rv;
549     }
550 
551     nsString path;
552     mReportsTmpFile->GetPath(path);
553     if (NS_WARN_IF(NS_FAILED(rv))) {
554       return rv;
555     }
556 
557     nsString msg = u"nsIMemoryInfoDumper dumped reports to "_ns;
558     msg.Append(path);
559     return cs->LogStringMessage(msg.get());
560   }
561 
562  private:
563   ~TempDirFinishCallback() = default;
564 
565   nsCOMPtr<nsIFile> mReportsTmpFile;
566   nsCString mReportsFilename;
567 };
568 
NS_IMPL_ISUPPORTS(TempDirFinishCallback,nsIFinishDumpingCallback)569 NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback)
570 
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;
580   }
581   auto jsonWriter =
582       MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
583 
584   nsCOMPtr<nsIMemoryReporterManager> mgr =
585       do_GetService("@mozilla.org/memory-reporter-manager;1");
586 
587   // This is the first write to the file, and it causes |aWriter| to allocate
588   // over 200 KiB of memory.
589   jsonWriter->Start();
590   {
591     // Increment this number if the format changes.
592     jsonWriter->IntProperty("version", 1);
593     jsonWriter->BoolProperty("hasMozMallocUsableSize",
594                              mgr->GetHasMozMallocUsableSize());
595     jsonWriter->StartArrayProperty("reports");
596   }
597 
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;
606 }
607 
608 NS_IMETHODIMP
DumpMemoryReportsToNamedFile(const nsAString & aFilename,nsIFinishDumpingCallback * aFinishDumping,nsISupports * aFinishDumpingData,bool aAnonymize,bool aMinimizeMemoryUsage)609 nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
610     const nsAString& aFilename, nsIFinishDumpingCallback* aFinishDumping,
611     nsISupports* aFinishDumpingData, bool aAnonymize,
612     bool aMinimizeMemoryUsage) {
613   MOZ_ASSERT(!aFilename.IsEmpty());
614 
615   // Create the file.
616 
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;
621   }
622 
623   reportsFile->InitWithPath(aFilename);
624   if (NS_WARN_IF(NS_FAILED(rv))) {
625     return rv;
626   }
627 
628   bool exists;
629   rv = reportsFile->Exists(&exists);
630   if (NS_WARN_IF(NS_FAILED(rv))) {
631     return rv;
632   }
633 
634   if (!exists) {
635     rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
636     if (NS_WARN_IF(NS_FAILED(rv))) {
637       return rv;
638     }
639   }
640 
641   nsString dmdIdent;
642   return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData,
643                               aAnonymize, aMinimizeMemoryUsage, dmdIdent);
644 }
645 
646 NS_IMETHODIMP
DumpMemoryInfoToTempDir(const nsAString & aIdentifier,bool aAnonymize,bool aMinimizeMemoryUsage)647 nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
648                                             bool aAnonymize,
649                                             bool aMinimizeMemoryUsage) {
650   nsString identifier(aIdentifier);
651   EnsureNonEmptyIdentifier(identifier);
652 
653   // Open a new file named something like
654   //
655   //   incomplete-memory-report-<identifier>-<pid>.json.gz
656   //
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.
659   //
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.
663 
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);
672 
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;
683   }
684 
685   RefPtr<TempDirFinishCallback> finishDumping =
686       new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
687 
688   return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr,
689                               aAnonymize, aMinimizeMemoryUsage, identifier);
690 }
691 
692 #ifdef MOZ_DMD
693 dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton;
694 
OpenDMDFile(const nsAString & aIdentifier,int aPid,FILE ** aOutFile)695 nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
696                                          FILE** aOutFile) {
697   if (!dmd::IsRunning()) {
698     *aOutFile = nullptr;
699     return NS_OK;
700   }
701 
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);
706 
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).
711 
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;
718   }
719   rv = dmdFile->OpenANSIFileDesc("wb", aOutFile);
720   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed");
721 
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());
724 
725   return rv;
726 }
727 
DumpDMDToFile(FILE * aFile)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;
733   }
734 
735   // Dump DMD's memory reports analysis to the file.
736   dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter));
737 
738   rv = gzWriter->Finish();
739   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed");
740   return rv;
741 }
742 #endif  // MOZ_DMD
743