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