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