/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 | | |