Coverage Report

Created: 2018-09-25 14:53

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