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