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 "nsISimpleEnumerator.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsIFile.h"
27 
28 #ifdef XP_WIN
29 #include <process.h>
30 #ifndef getpid
31 #define getpid _getpid
32 #endif
33 #else
34 #include <unistd.h>
35 #endif
36 
37 #ifdef XP_UNIX
38 #define MOZ_SUPPORTS_FIFO 1
39 #endif
40 
41 // Some Android devices seem to send RT signals to Firefox so we want to avoid
42 // consuming those as they're not user triggered.
43 #if !defined(ANDROID) && (defined(XP_LINUX) || defined(__FreeBSD__))
44 #define MOZ_SUPPORTS_RT_SIGNALS 1
45 #endif
46 
47 #if defined(MOZ_SUPPORTS_RT_SIGNALS)
48 #include <fcntl.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #endif
52 
53 #if defined(MOZ_SUPPORTS_FIFO)
54 #include "mozilla/Preferences.h"
55 #endif
56 
57 using namespace mozilla;
58 using namespace mozilla::dom;
59 
60 namespace {
61 
62 class DumpMemoryInfoToTempDirRunnable : public Runnable {
63  public:
DumpMemoryInfoToTempDirRunnable(const nsAString & aIdentifier,bool aAnonymize,bool aMinimizeMemoryUsage)64   DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, bool aAnonymize,
65                                   bool aMinimizeMemoryUsage)
66       : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"),
67         mIdentifier(aIdentifier),
68         mAnonymize(aAnonymize),
69         mMinimizeMemoryUsage(aMinimizeMemoryUsage) {}
70 
Run()71   NS_IMETHOD Run() override {
72     nsCOMPtr<nsIMemoryInfoDumper> dumper =
73         do_GetService("@mozilla.org/memory-info-dumper;1");
74     dumper->DumpMemoryInfoToTempDir(mIdentifier, mAnonymize,
75                                     mMinimizeMemoryUsage);
76     return NS_OK;
77   }
78 
79  private:
80   const nsString mIdentifier;
81   const bool mAnonymize;
82   const bool mMinimizeMemoryUsage;
83 };
84 
85 class GCAndCCLogDumpRunnable final : public Runnable,
86                                      public nsIDumpGCAndCCLogsCallback {
87  public:
88   NS_DECL_ISUPPORTS_INHERITED
89 
GCAndCCLogDumpRunnable(const nsAString & aIdentifier,bool aDumpAllTraces,bool aDumpChildProcesses)90   GCAndCCLogDumpRunnable(const nsAString& aIdentifier, bool aDumpAllTraces,
91                          bool aDumpChildProcesses)
92       : mozilla::Runnable("GCAndCCLogDumpRunnable"),
93         mIdentifier(aIdentifier),
94         mDumpAllTraces(aDumpAllTraces),
95         mDumpChildProcesses(aDumpChildProcesses) {}
96 
Run()97   NS_IMETHOD Run() override {
98     nsCOMPtr<nsIMemoryInfoDumper> dumper =
99         do_GetService("@mozilla.org/memory-info-dumper;1");
100 
101     dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces,
102                                   mDumpChildProcesses, this);
103     return NS_OK;
104   }
105 
OnDump(nsIFile * aGCLog,nsIFile * aCCLog,bool aIsParent)106   NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
107     return NS_OK;
108   }
109 
OnFinish()110   NS_IMETHOD OnFinish() override { return NS_OK; }
111 
112  private:
~GCAndCCLogDumpRunnable()113   ~GCAndCCLogDumpRunnable() {}
114 
115   const nsString mIdentifier;
116   const bool mDumpAllTraces;
117   const bool mDumpChildProcesses;
118 };
119 
120 NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable,
121                             nsIDumpGCAndCCLogsCallback)
122 
123 }  // namespace
124 
125 #if defined(MOZ_SUPPORTS_RT_SIGNALS)  // {
126 namespace {
127 
128 /*
129  * The following code supports dumping about:memory upon receiving a signal.
130  *
131  * We listen for the following signals:
132  *
133  *  - SIGRTMIN:     Dump our memory reporters (and those of our child
134  *                  processes),
135  *  - SIGRTMIN + 1: Dump our memory reporters (and those of our child
136  *                  processes) after minimizing memory usage, and
137  *  - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes.
138  *
139  * When we receive one of these signals, we write the signal number to a pipe.
140  * The IO thread then notices that the pipe has been written to, and kicks off
141  * the appropriate task on the main thread.
142  *
143  * This scheme is similar to using signalfd(), except it's portable and it
144  * doesn't require the use of sigprocmask, which is problematic because it
145  * masks signals received by child processes.
146  *
147  * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
148  * But that uses libevent, which does not handle the realtime signals (bug
149  * 794074).
150  */
151 
152 // It turns out that at least on some systems, SIGRTMIN is not a compile-time
153 // constant, so these have to be set at runtime.
154 static uint8_t sDumpAboutMemorySignum;          // SIGRTMIN
155 static uint8_t sDumpAboutMemoryAfterMMUSignum;  // SIGRTMIN + 1
156 static uint8_t sGCAndCCDumpSignum;              // SIGRTMIN + 2
157 
doMemoryReport(const uint8_t aRecvSig)158 void doMemoryReport(const uint8_t aRecvSig) {
159   // Dump our memory reports (but run this on the main thread!).
160   bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum;
161   LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig);
162   RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
163       new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(),
164                                           /* anonymize = */ false, minimize);
165   NS_DispatchToMainThread(runnable);
166 }
167 
doGCCCDump(const uint8_t aRecvSig)168 void doGCCCDump(const uint8_t aRecvSig) {
169   LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig);
170   // Dump GC and CC logs (from the main thread).
171   RefPtr<GCAndCCLogDumpRunnable> runnable =
172       new GCAndCCLogDumpRunnable(/* identifier = */ EmptyString(),
173                                  /* allTraces = */ true,
174                                  /* dumpChildProcesses = */ true);
175   NS_DispatchToMainThread(runnable);
176 }
177 
178 }  // namespace
179 #endif  // MOZ_SUPPORTS_RT_SIGNALS }
180 
181 #if defined(MOZ_SUPPORTS_FIFO)  // {
182 namespace {
183 
doMemoryReport(const nsCString & aInputStr)184 void doMemoryReport(const nsCString& aInputStr) {
185   bool minimize = aInputStr.EqualsLiteral("minimize memory report");
186   LOG("FifoWatcher(command:%s) dispatching memory report runnable.",
187       aInputStr.get());
188   RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
189       new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(),
190                                           /* anonymize = */ false, minimize);
191   NS_DispatchToMainThread(runnable);
192 }
193 
doGCCCDump(const nsCString & aInputStr)194 void doGCCCDump(const nsCString& aInputStr) {
195   bool doAllTracesGCCCDump = aInputStr.EqualsLiteral("gc log");
196   LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.",
197       aInputStr.get());
198   RefPtr<GCAndCCLogDumpRunnable> runnable = new GCAndCCLogDumpRunnable(
199       /* identifier = */ EmptyString(), doAllTracesGCCCDump,
200       /* dumpChildProcesses = */ true);
201   NS_DispatchToMainThread(runnable);
202 }
203 
SetupFifo()204 bool SetupFifo() {
205 #ifdef DEBUG
206   static bool fifoCallbacksRegistered = false;
207 #endif
208 
209   if (!FifoWatcher::MaybeCreate()) {
210     return false;
211   }
212 
213   MOZ_ASSERT(!fifoCallbacksRegistered,
214              "FifoWatcher callbacks should be registered only once");
215 
216   FifoWatcher* fw = FifoWatcher::GetSingleton();
217   // Dump our memory reports (but run this on the main thread!).
218   fw->RegisterCallback(NS_LITERAL_CSTRING("memory report"), doMemoryReport);
219   fw->RegisterCallback(NS_LITERAL_CSTRING("minimize memory report"),
220                        doMemoryReport);
221   // Dump GC and CC logs (from the main thread).
222   fw->RegisterCallback(NS_LITERAL_CSTRING("gc log"), doGCCCDump);
223   fw->RegisterCallback(NS_LITERAL_CSTRING("abbreviated gc log"), doGCCCDump);
224 
225 #ifdef DEBUG
226   fifoCallbacksRegistered = true;
227 #endif
228   return true;
229 }
230 
OnFifoEnabledChange(const char *,void *)231 void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) {
232   LOG("%s changed", FifoWatcher::kPrefName);
233   if (SetupFifo()) {
234     Preferences::UnregisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName,
235                                     nullptr);
236   }
237 }
238 
239 }  // namespace
240 #endif  // MOZ_SUPPORTS_FIFO }
241 
NS_IMPL_ISUPPORTS(nsMemoryInfoDumper,nsIMemoryInfoDumper)242 NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper)
243 
244 nsMemoryInfoDumper::nsMemoryInfoDumper() {}
245 
~nsMemoryInfoDumper()246 nsMemoryInfoDumper::~nsMemoryInfoDumper() {}
247 
Initialize()248 /* static */ void nsMemoryInfoDumper::Initialize() {
249 #if defined(MOZ_SUPPORTS_RT_SIGNALS)
250   SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
251 
252   // Dump memory reporters (and those of our child processes)
253   sDumpAboutMemorySignum = SIGRTMIN;
254   sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport);
255   // Dump our memory reporters after minimizing memory usage
256   sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
257   sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport);
258   // Dump the GC and CC logs in this and our child processes.
259   sGCAndCCDumpSignum = SIGRTMIN + 2;
260   sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump);
261 #endif
262 
263 #if defined(MOZ_SUPPORTS_FIFO)
264   if (!SetupFifo()) {
265     // NB: This gets loaded early enough that it's possible there is a user pref
266     //     set to enable the fifo watcher that has not been loaded yet. Register
267     //     to attempt to initialize if the fifo watcher becomes enabled by
268     //     a user pref.
269     Preferences::RegisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName,
270                                   nullptr);
271   }
272 #endif
273 }
274 
EnsureNonEmptyIdentifier(nsAString & aIdentifier)275 static void EnsureNonEmptyIdentifier(nsAString& aIdentifier) {
276   if (!aIdentifier.IsEmpty()) {
277     return;
278   }
279 
280   // If the identifier is empty, set it to the number of whole seconds since the
281   // epoch.  This identifier will appear in the files that this process
282   // generates and also the files generated by this process's children, allowing
283   // us to identify which files are from the same memory report request.
284   aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
285 }
286 
287 // Use XPCOM refcounting to fire |onFinish| when all reference-holders
288 // (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself)
289 // have gone away.
290 class nsDumpGCAndCCLogsCallbackHolder final
291     : public nsIDumpGCAndCCLogsCallback {
292  public:
293   NS_DECL_ISUPPORTS
294 
nsDumpGCAndCCLogsCallbackHolder(nsIDumpGCAndCCLogsCallback * aCallback)295   explicit nsDumpGCAndCCLogsCallbackHolder(
296       nsIDumpGCAndCCLogsCallback* aCallback)
297       : mCallback(aCallback) {}
298 
OnFinish()299   NS_IMETHOD OnFinish() override { return NS_ERROR_UNEXPECTED; }
300 
OnDump(nsIFile * aGCLog,nsIFile * aCCLog,bool aIsParent)301   NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
302     return mCallback->OnDump(aGCLog, aCCLog, aIsParent);
303   }
304 
305  private:
~nsDumpGCAndCCLogsCallbackHolder()306   ~nsDumpGCAndCCLogsCallbackHolder() { Unused << mCallback->OnFinish(); }
307 
308   nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
309 };
310 
NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder,nsIDumpGCAndCCLogsCallback)311 NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback)
312 
313 NS_IMETHODIMP
314 nsMemoryInfoDumper::DumpGCAndCCLogsToFile(
315     const nsAString& aIdentifier, bool aDumpAllTraces, bool aDumpChildProcesses,
316     nsIDumpGCAndCCLogsCallback* aCallback) {
317   nsString identifier(aIdentifier);
318   EnsureNonEmptyIdentifier(identifier);
319   nsCOMPtr<nsIDumpGCAndCCLogsCallback> callbackHolder =
320       new nsDumpGCAndCCLogsCallbackHolder(aCallback);
321 
322   if (aDumpChildProcesses) {
323     nsTArray<ContentParent*> children;
324     ContentParent::GetAll(children);
325     for (uint32_t i = 0; i < children.Length(); i++) {
326       ContentParent* cp = children[i];
327       nsCOMPtr<nsICycleCollectorLogSink> logSink =
328           nsCycleCollector_createLogSink();
329 
330       logSink->SetFilenameIdentifier(identifier);
331       logSink->SetProcessIdentifier(cp->Pid());
332 
333       Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink,
334                                          callbackHolder);
335     }
336   }
337 
338   nsCOMPtr<nsICycleCollectorListener> logger =
339       do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
340 
341   if (aDumpAllTraces) {
342     nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
343     logger->AllTraces(getter_AddRefs(allTracesLogger));
344     logger = allTracesLogger;
345   }
346 
347   nsCOMPtr<nsICycleCollectorLogSink> logSink;
348   logger->GetLogSink(getter_AddRefs(logSink));
349 
350   logSink->SetFilenameIdentifier(identifier);
351 
352   nsJSContext::CycleCollectNow(logger);
353 
354   nsCOMPtr<nsIFile> gcLog, ccLog;
355   logSink->GetGcLog(getter_AddRefs(gcLog));
356   logSink->GetCcLog(getter_AddRefs(ccLog));
357   callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true);
358 
359   return NS_OK;
360 }
361 
362 NS_IMETHODIMP
DumpGCAndCCLogsToSink(bool aDumpAllTraces,nsICycleCollectorLogSink * aSink)363 nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
364                                           nsICycleCollectorLogSink* aSink) {
365   nsCOMPtr<nsICycleCollectorListener> logger =
366       do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
367 
368   if (aDumpAllTraces) {
369     nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
370     logger->AllTraces(getter_AddRefs(allTracesLogger));
371     logger = allTracesLogger;
372   }
373 
374   logger->SetLogSink(aSink);
375 
376   nsJSContext::CycleCollectNow(logger);
377 
378   return NS_OK;
379 }
380 
MakeFilename(const char * aPrefix,const nsAString & aIdentifier,int aPid,const char * aSuffix,nsACString & aResult)381 static void MakeFilename(const char* aPrefix, const nsAString& aIdentifier,
382                          int aPid, const char* aSuffix, nsACString& aResult) {
383   aResult =
384       nsPrintfCString("%s-%s-%d.%s", aPrefix,
385                       NS_ConvertUTF16toUTF8(aIdentifier).get(), aPid, aSuffix);
386 }
387 
388 // This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
389 // the following two problems:
390 // - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
391 // - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
392 class GZWriterWrapper : public JSONWriteFunc {
393  public:
GZWriterWrapper(nsGZFileWriter * aGZWriter)394   explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {}
395 
Write(const char * aStr)396   void Write(const char* aStr) override {
397     // Ignore any failure because JSONWriteFunc doesn't have a mechanism for
398     // handling errors.
399     Unused << mGZWriter->Write(aStr);
400   }
401 
Finish()402   nsresult Finish() { return mGZWriter->Finish(); }
403 
404  private:
405   RefPtr<nsGZFileWriter> mGZWriter;
406 };
407 
408 // We need two callbacks: one that handles reports, and one that is called at
409 // the end of reporting. Both the callbacks need access to the same JSONWriter,
410 // so we implement both of them in this one class.
411 class HandleReportAndFinishReportingCallbacks final
412     : public nsIHandleReportCallback,
413       public nsIFinishReportingCallback {
414  public:
415   NS_DECL_ISUPPORTS
416 
HandleReportAndFinishReportingCallbacks(UniquePtr<JSONWriter> aWriter,nsIFinishDumpingCallback * aFinishDumping,nsISupports * aFinishDumpingData)417   HandleReportAndFinishReportingCallbacks(
418       UniquePtr<JSONWriter> aWriter, nsIFinishDumpingCallback* aFinishDumping,
419       nsISupports* aFinishDumpingData)
420       : mWriter(Move(aWriter)),
421         mFinishDumping(aFinishDumping),
422         mFinishDumpingData(aFinishDumpingData) {}
423 
424   // 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)425   NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
426                       int32_t aKind, int32_t aUnits, int64_t aAmount,
427                       const nsACString& aDescription,
428                       nsISupports* aData) override {
429     nsAutoCString process;
430     if (aProcess.IsEmpty()) {
431       // If the process is empty, the report originated with the process doing
432       // the dumping.  In that case, generate the process identifier, which is
433       // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we
434       // don't have a process name.  If we're the main process, we let
435       // $PROCESS_NAME be "Main Process".
436       if (XRE_IsParentProcess()) {
437         // We're the main process.
438         process.AssignLiteral("Main Process");
439       } else if (ContentChild* cc = ContentChild::GetSingleton()) {
440         // Try to get the process name from ContentChild.
441         cc->GetProcessName(process);
442       }
443       ContentChild::AppendProcessId(process);
444 
445     } else {
446       // Otherwise, the report originated with another process and already has a
447       // process name.  Just use that.
448       process = aProcess;
449     }
450 
451     mWriter->StartObjectElement();
452     {
453       mWriter->StringProperty("process", process.get());
454       mWriter->StringProperty("path", PromiseFlatCString(aPath).get());
455       mWriter->IntProperty("kind", aKind);
456       mWriter->IntProperty("units", aUnits);
457       mWriter->IntProperty("amount", aAmount);
458       mWriter->StringProperty("description",
459                               PromiseFlatCString(aDescription).get());
460     }
461     mWriter->EndObject();
462 
463     return NS_OK;
464   }
465 
466   // This is the callback for nsIFinishReportingCallback.
Callback(nsISupports * aData)467   NS_IMETHOD Callback(nsISupports* aData) override {
468     mWriter->EndArray();  // end of "reports" array
469     mWriter->End();
470 
471     // The call to Finish() deallocates the memory allocated by the first Write
472     // call. Because that memory was live while the memory reporters ran and
473     // was measured by them -- by "heap-allocated" if nothing else -- we want
474     // DMD to see it as well. So we deliberately don't call Finish() until
475     // after DMD finishes.
476     nsresult rv = static_cast<GZWriterWrapper*>(mWriter->WriteFunc())->Finish();
477     NS_ENSURE_SUCCESS(rv, rv);
478 
479     if (!mFinishDumping) {
480       return NS_OK;
481     }
482 
483     return mFinishDumping->Callback(mFinishDumpingData);
484   }
485 
486  private:
~HandleReportAndFinishReportingCallbacks()487   ~HandleReportAndFinishReportingCallbacks() {}
488 
489   UniquePtr<JSONWriter> mWriter;
490   nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
491   nsCOMPtr<nsISupports> mFinishDumpingData;
492 };
493 
494 NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
495                   nsIHandleReportCallback, nsIFinishReportingCallback)
496 
497 class TempDirFinishCallback final : public nsIFinishDumpingCallback {
498  public:
499   NS_DECL_ISUPPORTS
500 
TempDirFinishCallback(nsIFile * aReportsTmpFile,const nsCString & aReportsFinalFilename)501   TempDirFinishCallback(nsIFile* aReportsTmpFile,
502                         const nsCString& aReportsFinalFilename)
503       : mReportsTmpFile(aReportsTmpFile),
504         mReportsFilename(aReportsFinalFilename) {}
505 
Callback(nsISupports * aData)506   NS_IMETHOD Callback(nsISupports* aData) override {
507     // Rename the memory reports file, now that we're done writing all the
508     // files. Its final name is "memory-report<-identifier>-<pid>.json.gz".
509 
510     nsCOMPtr<nsIFile> reportsFinalFile;
511     nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
512                                          getter_AddRefs(reportsFinalFile));
513     if (NS_WARN_IF(NS_FAILED(rv))) {
514       return rv;
515     }
516 
517 #ifdef ANDROID
518     rv = reportsFinalFile->AppendNative(NS_LITERAL_CSTRING("memory-reports"));
519     if (NS_WARN_IF(NS_FAILED(rv))) {
520       return rv;
521     }
522 #endif
523 
524     rv = reportsFinalFile->AppendNative(mReportsFilename);
525     if (NS_WARN_IF(NS_FAILED(rv))) {
526       return rv;
527     }
528 
529     rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
530     if (NS_WARN_IF(NS_FAILED(rv))) {
531       return rv;
532     }
533 
534     nsAutoString reportsFinalFilename;
535     rv = reportsFinalFile->GetLeafName(reportsFinalFilename);
536     if (NS_WARN_IF(NS_FAILED(rv))) {
537       return rv;
538     }
539 
540     rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, reportsFinalFilename);
541     if (NS_WARN_IF(NS_FAILED(rv))) {
542       return rv;
543     }
544 
545     // Write a message to the console.
546 
547     nsCOMPtr<nsIConsoleService> cs =
548         do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
549     if (NS_WARN_IF(NS_FAILED(rv))) {
550       return rv;
551     }
552 
553     nsString path;
554     mReportsTmpFile->GetPath(path);
555     if (NS_WARN_IF(NS_FAILED(rv))) {
556       return rv;
557     }
558 
559     nsString msg = NS_LITERAL_STRING("nsIMemoryInfoDumper dumped reports to ");
560     msg.Append(path);
561     return cs->LogStringMessage(msg.get());
562   }
563 
564  private:
~TempDirFinishCallback()565   ~TempDirFinishCallback() {}
566 
567   nsCOMPtr<nsIFile> mReportsTmpFile;
568   nsCString mReportsFilename;
569 };
570 
NS_IMPL_ISUPPORTS(TempDirFinishCallback,nsIFinishDumpingCallback)571 NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback)
572 
573 static nsresult DumpMemoryInfoToFile(nsIFile* aReportsFile,
574                                      nsIFinishDumpingCallback* aFinishDumping,
575                                      nsISupports* aFinishDumpingData,
576                                      bool aAnonymize, bool aMinimizeMemoryUsage,
577                                      nsAString& aDMDIdentifier) {
578   RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
579   nsresult rv = gzWriter->Init(aReportsFile);
580   if (NS_WARN_IF(NS_FAILED(rv))) {
581     return rv;
582   }
583   auto jsonWriter =
584       MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
585 
586   nsCOMPtr<nsIMemoryReporterManager> mgr =
587       do_GetService("@mozilla.org/memory-reporter-manager;1");
588 
589   // This is the first write to the file, and it causes |aWriter| to allocate
590   // over 200 KiB of memory.
591   jsonWriter->Start();
592   {
593     // Increment this number if the format changes.
594     jsonWriter->IntProperty("version", 1);
595     jsonWriter->BoolProperty("hasMozMallocUsableSize",
596                              mgr->GetHasMozMallocUsableSize());
597     jsonWriter->StartArrayProperty("reports");
598   }
599 
600   RefPtr<HandleReportAndFinishReportingCallbacks>
601       handleReportAndFinishReporting =
602           new HandleReportAndFinishReportingCallbacks(
603               Move(jsonWriter), aFinishDumping, aFinishDumpingData);
604   rv = mgr->GetReportsExtended(
605       handleReportAndFinishReporting, nullptr, handleReportAndFinishReporting,
606       nullptr, aAnonymize, aMinimizeMemoryUsage, aDMDIdentifier);
607   return rv;
608 }
609 
610 NS_IMETHODIMP
DumpMemoryReportsToNamedFile(const nsAString & aFilename,nsIFinishDumpingCallback * aFinishDumping,nsISupports * aFinishDumpingData,bool aAnonymize)611 nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
612     const nsAString& aFilename, nsIFinishDumpingCallback* aFinishDumping,
613     nsISupports* aFinishDumpingData, bool aAnonymize) {
614   MOZ_ASSERT(!aFilename.IsEmpty());
615 
616   // Create the file.
617 
618   nsCOMPtr<nsIFile> reportsFile;
619   nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(reportsFile));
620   if (NS_WARN_IF(NS_FAILED(rv))) {
621     return rv;
622   }
623 
624   reportsFile->InitWithPath(aFilename);
625   if (NS_WARN_IF(NS_FAILED(rv))) {
626     return rv;
627   }
628 
629   bool exists;
630   rv = reportsFile->Exists(&exists);
631   if (NS_WARN_IF(NS_FAILED(rv))) {
632     return rv;
633   }
634 
635   if (!exists) {
636     rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
637     if (NS_WARN_IF(NS_FAILED(rv))) {
638       return rv;
639     }
640   }
641 
642   nsString dmdIdent = EmptyString();
643   return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData,
644                               aAnonymize, /* minimizeMemoryUsage = */ false,
645                               dmdIdent);
646 }
647 
648 NS_IMETHODIMP
DumpMemoryInfoToTempDir(const nsAString & aIdentifier,bool aAnonymize,bool aMinimizeMemoryUsage)649 nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
650                                             bool aAnonymize,
651                                             bool aMinimizeMemoryUsage) {
652   nsString identifier(aIdentifier);
653   EnsureNonEmptyIdentifier(identifier);
654 
655   // Open a new file named something like
656   //
657   //   incomplete-memory-report-<identifier>-<pid>.json.gz
658   //
659   // in NS_OS_TEMP_DIR for writing.  When we're finished writing the report,
660   // we'll rename this file and get rid of the "incomplete-" prefix.
661   //
662   // We do this because we don't want scripts which poll the filesystem
663   // looking for memory report dumps to grab a file before we're finished
664   // writing to it.
665 
666   // The "unified" indicates that we merge the memory reports from all
667   // processes and write out one file, rather than a separate file for
668   // each process as was the case before bug 946407.  This is so that
669   // the get_about_memory.py script in the B2G repository can
670   // determine when it's done waiting for files to appear.
671   nsCString reportsFinalFilename;
672   MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
673                reportsFinalFilename);
674 
675   nsCOMPtr<nsIFile> reportsTmpFile;
676   nsresult rv;
677   // In Android case, this function will open a file named aFilename under
678   // specific folder (/data/local/tmp/memory-reports). Otherwise, it will
679   // open a file named aFilename under "NS_OS_TEMP_DIR".
680   rv = nsDumpUtils::OpenTempFile(
681       NS_LITERAL_CSTRING("incomplete-") + reportsFinalFilename,
682       getter_AddRefs(reportsTmpFile), NS_LITERAL_CSTRING("memory-reports"));
683   if (NS_WARN_IF(NS_FAILED(rv))) {
684     return rv;
685   }
686 
687   RefPtr<TempDirFinishCallback> finishDumping =
688       new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
689 
690   return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr,
691                               aAnonymize, aMinimizeMemoryUsage, identifier);
692 }
693 
694 #ifdef MOZ_DMD
695 dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton;
696 
OpenDMDFile(const nsAString & aIdentifier,int aPid,FILE ** aOutFile)697 nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
698                                          FILE** aOutFile) {
699   if (!dmd::IsRunning()) {
700     *aOutFile = nullptr;
701     return NS_OK;
702   }
703 
704   // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used
705   // if DMD is enabled.
706   nsCString dmdFilename;
707   MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
708 
709   // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
710   // and dump DMD output to it.  This must occur after the memory reporters
711   // have been run (above), but before the memory-reports file has been
712   // renamed (so scripts can detect the DMD file, if present).
713 
714   nsresult rv;
715   nsCOMPtr<nsIFile> dmdFile;
716   rv = nsDumpUtils::OpenTempFile(dmdFilename, getter_AddRefs(dmdFile),
717                                  NS_LITERAL_CSTRING("memory-reports"));
718   if (NS_WARN_IF(NS_FAILED(rv))) {
719     return rv;
720   }
721   rv = dmdFile->OpenANSIFileDesc("wb", aOutFile);
722   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed");
723 
724   // Print the path, because on some platforms (e.g. Mac) it's not obvious.
725   dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().get());
726 
727   return rv;
728 }
729 
DumpDMDToFile(FILE * aFile)730 nsresult nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) {
731   RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
732   nsresult rv = gzWriter->InitANSIFileDesc(aFile);
733   if (NS_WARN_IF(NS_FAILED(rv))) {
734     return rv;
735   }
736 
737   // Dump DMD's memory reports analysis to the file.
738   dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter));
739 
740   rv = gzWriter->Finish();
741   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed");
742   return rv;
743 }
744 #endif  // MOZ_DMD
745