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