1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/base/nsMemoryInfoDumper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,767 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/nsMemoryInfoDumper.h" 1.11 +#include "nsDumpUtils.h" 1.12 + 1.13 +#include "mozilla/unused.h" 1.14 +#include "mozilla/dom/ContentParent.h" 1.15 +#include "mozilla/dom/ContentChild.h" 1.16 +#include "nsIConsoleService.h" 1.17 +#include "nsICycleCollectorListener.h" 1.18 +#include "nsIMemoryReporter.h" 1.19 +#include "nsDirectoryServiceDefs.h" 1.20 +#include "nsGZFileWriter.h" 1.21 +#include "nsJSEnvironment.h" 1.22 +#include "nsPrintfCString.h" 1.23 +#include "nsISimpleEnumerator.h" 1.24 +#include "nsServiceManagerUtils.h" 1.25 +#include "nsIFile.h" 1.26 + 1.27 +#ifdef XP_WIN 1.28 +#include <process.h> 1.29 +#define getpid _getpid 1.30 +#else 1.31 +#include <unistd.h> 1.32 +#endif 1.33 + 1.34 +#ifdef XP_UNIX 1.35 +#define MOZ_SUPPORTS_FIFO 1 1.36 +#endif 1.37 + 1.38 +#if defined(XP_LINUX) || defined(__FreeBSD__) 1.39 +#define MOZ_SUPPORTS_RT_SIGNALS 1 1.40 +#endif 1.41 + 1.42 +#if defined(MOZ_SUPPORTS_RT_SIGNALS) 1.43 +#include <fcntl.h> 1.44 +#include <sys/types.h> 1.45 +#include <sys/stat.h> 1.46 +#endif 1.47 + 1.48 +#if defined(MOZ_SUPPORTS_FIFO) 1.49 +#include "mozilla/Preferences.h" 1.50 +#endif 1.51 + 1.52 +using namespace mozilla; 1.53 +using namespace mozilla::dom; 1.54 + 1.55 +namespace { 1.56 + 1.57 +class DumpMemoryInfoToTempDirRunnable : public nsRunnable 1.58 +{ 1.59 +public: 1.60 + DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, 1.61 + bool aMinimizeMemoryUsage) 1.62 + : mIdentifier(aIdentifier) 1.63 + , mMinimizeMemoryUsage(aMinimizeMemoryUsage) 1.64 + {} 1.65 + 1.66 + NS_IMETHOD Run() 1.67 + { 1.68 + nsCOMPtr<nsIMemoryInfoDumper> dumper = do_GetService("@mozilla.org/memory-info-dumper;1"); 1.69 + dumper->DumpMemoryInfoToTempDir(mIdentifier, mMinimizeMemoryUsage); 1.70 + return NS_OK; 1.71 + } 1.72 + 1.73 +private: 1.74 + const nsString mIdentifier; 1.75 + const bool mMinimizeMemoryUsage; 1.76 +}; 1.77 + 1.78 +class GCAndCCLogDumpRunnable : public nsRunnable 1.79 +{ 1.80 +public: 1.81 + GCAndCCLogDumpRunnable(const nsAString& aIdentifier, 1.82 + bool aDumpAllTraces, 1.83 + bool aDumpChildProcesses) 1.84 + : mIdentifier(aIdentifier) 1.85 + , mDumpAllTraces(aDumpAllTraces) 1.86 + , mDumpChildProcesses(aDumpChildProcesses) 1.87 + {} 1.88 + 1.89 + NS_IMETHOD Run() 1.90 + { 1.91 + nsCOMPtr<nsIMemoryInfoDumper> dumper = 1.92 + do_GetService("@mozilla.org/memory-info-dumper;1"); 1.93 + 1.94 + nsString ccLogPath, gcLogPath; 1.95 + dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces, 1.96 + mDumpChildProcesses, gcLogPath, ccLogPath); 1.97 + return NS_OK; 1.98 + } 1.99 + 1.100 +private: 1.101 + const nsString mIdentifier; 1.102 + const bool mDumpAllTraces; 1.103 + const bool mDumpChildProcesses; 1.104 +}; 1.105 + 1.106 +} // anonymous namespace 1.107 + 1.108 +#if defined(MOZ_SUPPORTS_RT_SIGNALS) // { 1.109 +namespace { 1.110 + 1.111 +/* 1.112 + * The following code supports dumping about:memory upon receiving a signal. 1.113 + * 1.114 + * We listen for the following signals: 1.115 + * 1.116 + * - SIGRTMIN: Dump our memory reporters (and those of our child 1.117 + * processes), 1.118 + * - SIGRTMIN + 1: Dump our memory reporters (and those of our child 1.119 + * processes) after minimizing memory usage, and 1.120 + * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes. 1.121 + * 1.122 + * When we receive one of these signals, we write the signal number to a pipe. 1.123 + * The IO thread then notices that the pipe has been written to, and kicks off 1.124 + * the appropriate task on the main thread. 1.125 + * 1.126 + * This scheme is similar to using signalfd(), except it's portable and it 1.127 + * doesn't require the use of sigprocmask, which is problematic because it 1.128 + * masks signals received by child processes. 1.129 + * 1.130 + * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. 1.131 + * But that uses libevent, which does not handle the realtime signals (bug 1.132 + * 794074). 1.133 + */ 1.134 + 1.135 +// It turns out that at least on some systems, SIGRTMIN is not a compile-time 1.136 +// constant, so these have to be set at runtime. 1.137 +static uint8_t sDumpAboutMemorySignum; // SIGRTMIN 1.138 +static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1 1.139 +static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2 1.140 + 1.141 +void doMemoryReport(const uint8_t recvSig) 1.142 +{ 1.143 + // Dump our memory reports (but run this on the main thread!). 1.144 + bool doMMUFirst = recvSig == sDumpAboutMemoryAfterMMUSignum; 1.145 + LOG("SignalWatcher(sig %d) dispatching memory report runnable.", recvSig); 1.146 + nsRefPtr<DumpMemoryInfoToTempDirRunnable> runnable = 1.147 + new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(), 1.148 + doMMUFirst); 1.149 + NS_DispatchToMainThread(runnable); 1.150 +} 1.151 + 1.152 +void doGCCCDump(const uint8_t recvSig) 1.153 +{ 1.154 + LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", recvSig); 1.155 + // Dump GC and CC logs (from the main thread). 1.156 + nsRefPtr<GCAndCCLogDumpRunnable> runnable = 1.157 + new GCAndCCLogDumpRunnable( 1.158 + /* identifier = */ EmptyString(), 1.159 + /* allTraces = */ true, 1.160 + /* dumpChildProcesses = */ true); 1.161 + NS_DispatchToMainThread(runnable); 1.162 +} 1.163 + 1.164 +} // anonymous namespace 1.165 +#endif // MOZ_SUPPORTS_RT_SIGNALS } 1.166 + 1.167 +#if defined(MOZ_SUPPORTS_FIFO) // { 1.168 +namespace { 1.169 + 1.170 +void doMemoryReport(const nsCString& inputStr) 1.171 +{ 1.172 + bool doMMUMemoryReport = inputStr == NS_LITERAL_CSTRING("minimize memory report"); 1.173 + LOG("FifoWatcher(command:%s) dispatching memory report runnable.", inputStr.get()); 1.174 + nsRefPtr<DumpMemoryInfoToTempDirRunnable> runnable = 1.175 + new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(), 1.176 + doMMUMemoryReport); 1.177 + NS_DispatchToMainThread(runnable); 1.178 +} 1.179 + 1.180 +void doGCCCDump(const nsCString& inputStr) 1.181 +{ 1.182 + bool doAllTracesGCCCDump = inputStr == NS_LITERAL_CSTRING("gc log"); 1.183 + LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.", inputStr.get()); 1.184 + nsRefPtr<GCAndCCLogDumpRunnable> runnable = 1.185 + new GCAndCCLogDumpRunnable(/* identifier = */ EmptyString(), 1.186 + doAllTracesGCCCDump, 1.187 + /* dumpChildProcesses = */ true); 1.188 + NS_DispatchToMainThread(runnable); 1.189 +} 1.190 + 1.191 +bool SetupFifo() 1.192 +{ 1.193 + static bool fifoCallbacksRegistered = false; 1.194 + 1.195 + if (!FifoWatcher::MaybeCreate()) { 1.196 + return false; 1.197 + } 1.198 + 1.199 + MOZ_ASSERT(!fifoCallbacksRegistered, 1.200 + "FifoWatcher callbacks should be registered only once"); 1.201 + 1.202 + FifoWatcher* fw = FifoWatcher::GetSingleton(); 1.203 + // Dump our memory reports (but run this on the main thread!). 1.204 + fw->RegisterCallback(NS_LITERAL_CSTRING("memory report"), 1.205 + doMemoryReport); 1.206 + fw->RegisterCallback(NS_LITERAL_CSTRING("minimize memory report"), 1.207 + doMemoryReport); 1.208 + // Dump GC and CC logs (from the main thread). 1.209 + fw->RegisterCallback(NS_LITERAL_CSTRING("gc log"), 1.210 + doGCCCDump); 1.211 + fw->RegisterCallback(NS_LITERAL_CSTRING("abbreviated gc log"), 1.212 + doGCCCDump); 1.213 + 1.214 + fifoCallbacksRegistered = true; 1.215 + return true; 1.216 +} 1.217 + 1.218 +void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) 1.219 +{ 1.220 + LOG("%s changed", FifoWatcher::kPrefName); 1.221 + if (SetupFifo()) { 1.222 + Preferences::UnregisterCallback(OnFifoEnabledChange, 1.223 + FifoWatcher::kPrefName, 1.224 + nullptr); 1.225 + } 1.226 +} 1.227 + 1.228 +} // anonymous namespace 1.229 +#endif // MOZ_SUPPORTS_FIFO } 1.230 + 1.231 +NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper) 1.232 + 1.233 +nsMemoryInfoDumper::nsMemoryInfoDumper() 1.234 +{ 1.235 +} 1.236 + 1.237 +nsMemoryInfoDumper::~nsMemoryInfoDumper() 1.238 +{ 1.239 +} 1.240 + 1.241 +/* static */ void 1.242 +nsMemoryInfoDumper::Initialize() 1.243 +{ 1.244 +#if defined(MOZ_SUPPORTS_RT_SIGNALS) 1.245 + SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton(); 1.246 + 1.247 + // Dump memory reporters (and those of our child processes) 1.248 + sDumpAboutMemorySignum = SIGRTMIN; 1.249 + sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport); 1.250 + // Dump our memory reporters after minimizing memory usage 1.251 + sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1; 1.252 + sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport); 1.253 + // Dump the GC and CC logs in this and our child processes. 1.254 + sGCAndCCDumpSignum = SIGRTMIN + 2; 1.255 + sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump); 1.256 +#endif 1.257 + 1.258 +#if defined(MOZ_SUPPORTS_FIFO) 1.259 + if (!SetupFifo()) { 1.260 + // NB: This gets loaded early enough that it's possible there is a user pref 1.261 + // set to enable the fifo watcher that has not been loaded yet. Register 1.262 + // to attempt to initialize if the fifo watcher becomes enabled by 1.263 + // a user pref. 1.264 + Preferences::RegisterCallback(OnFifoEnabledChange, 1.265 + FifoWatcher::kPrefName, 1.266 + nullptr); 1.267 + } 1.268 +#endif 1.269 +} 1.270 + 1.271 +static void 1.272 +EnsureNonEmptyIdentifier(nsAString& aIdentifier) 1.273 +{ 1.274 + if (!aIdentifier.IsEmpty()) { 1.275 + return; 1.276 + } 1.277 + 1.278 + // If the identifier is empty, set it to the number of whole seconds since the 1.279 + // epoch. This identifier will appear in the files that this process 1.280 + // generates and also the files generated by this process's children, allowing 1.281 + // us to identify which files are from the same memory report request. 1.282 + aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000); 1.283 +} 1.284 + 1.285 +NS_IMETHODIMP 1.286 +nsMemoryInfoDumper::DumpGCAndCCLogsToFile(const nsAString& aIdentifier, 1.287 + bool aDumpAllTraces, 1.288 + bool aDumpChildProcesses, 1.289 + nsAString& aGCLogPath, 1.290 + nsAString& aCCLogPath) 1.291 +{ 1.292 + nsString identifier(aIdentifier); 1.293 + EnsureNonEmptyIdentifier(identifier); 1.294 + 1.295 + if (aDumpChildProcesses) { 1.296 + nsTArray<ContentParent*> children; 1.297 + ContentParent::GetAll(children); 1.298 + for (uint32_t i = 0; i < children.Length(); i++) { 1.299 + unused << children[i]->SendDumpGCAndCCLogsToFile( 1.300 + identifier, aDumpAllTraces, aDumpChildProcesses); 1.301 + } 1.302 + } 1.303 + 1.304 + nsCOMPtr<nsICycleCollectorListener> logger = 1.305 + do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); 1.306 + logger->SetFilenameIdentifier(identifier); 1.307 + 1.308 + if (aDumpAllTraces) { 1.309 + nsCOMPtr<nsICycleCollectorListener> allTracesLogger; 1.310 + logger->AllTraces(getter_AddRefs(allTracesLogger)); 1.311 + logger = allTracesLogger; 1.312 + } 1.313 + 1.314 + nsJSContext::CycleCollectNow(logger); 1.315 + 1.316 + logger->GetGcLogPath(aGCLogPath); 1.317 + logger->GetCcLogPath(aCCLogPath); 1.318 + 1.319 + return NS_OK; 1.320 +} 1.321 + 1.322 +namespace mozilla { 1.323 + 1.324 +#define DUMP(o, s) \ 1.325 + do { \ 1.326 + nsresult rv = (o)->Write(s); \ 1.327 + if (NS_WARN_IF(NS_FAILED(rv))) \ 1.328 + return rv; \ 1.329 + } while (0) 1.330 + 1.331 +class DumpReportCallback MOZ_FINAL : public nsIHandleReportCallback 1.332 +{ 1.333 +public: 1.334 + NS_DECL_ISUPPORTS 1.335 + 1.336 + DumpReportCallback(nsGZFileWriter* aWriter) 1.337 + : mIsFirst(true) 1.338 + , mWriter(aWriter) 1.339 + {} 1.340 + 1.341 + NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath, 1.342 + int32_t aKind, int32_t aUnits, int64_t aAmount, 1.343 + const nsACString &aDescription, 1.344 + nsISupports *aData) 1.345 + { 1.346 + if (mIsFirst) { 1.347 + DUMP(mWriter, "["); 1.348 + mIsFirst = false; 1.349 + } else { 1.350 + DUMP(mWriter, ","); 1.351 + } 1.352 + 1.353 + nsAutoCString process; 1.354 + if (aProcess.IsEmpty()) { 1.355 + // If the process is empty, the report originated with the process doing 1.356 + // the dumping. In that case, generate the process identifier, which is of 1.357 + // the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we don't 1.358 + // have a process name. If we're the main process, we let $PROCESS_NAME be 1.359 + // "Main Process". 1.360 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.361 + // We're the main process. 1.362 + process.AssignLiteral("Main Process"); 1.363 + } else if (ContentChild *cc = ContentChild::GetSingleton()) { 1.364 + // Try to get the process name from ContentChild. 1.365 + cc->GetProcessName(process); 1.366 + } 1.367 + ContentChild::AppendProcessId(process); 1.368 + 1.369 + } else { 1.370 + // Otherwise, the report originated with another process and already has a 1.371 + // process name. Just use that. 1.372 + process = aProcess; 1.373 + } 1.374 + 1.375 + DUMP(mWriter, "\n {\"process\": \""); 1.376 + DUMP(mWriter, process); 1.377 + 1.378 + DUMP(mWriter, "\", \"path\": \""); 1.379 + nsCString path(aPath); 1.380 + path.ReplaceSubstring("\\", "\\\\"); /* <backslash> --> \\ */ 1.381 + path.ReplaceSubstring("\"", "\\\""); // " --> \" 1.382 + DUMP(mWriter, path); 1.383 + 1.384 + DUMP(mWriter, "\", \"kind\": "); 1.385 + DUMP(mWriter, nsPrintfCString("%d", aKind)); 1.386 + 1.387 + DUMP(mWriter, ", \"units\": "); 1.388 + DUMP(mWriter, nsPrintfCString("%d", aUnits)); 1.389 + 1.390 + DUMP(mWriter, ", \"amount\": "); 1.391 + DUMP(mWriter, nsPrintfCString("%lld", aAmount)); 1.392 + 1.393 + nsCString description(aDescription); 1.394 + description.ReplaceSubstring("\\", "\\\\"); /* <backslash> --> \\ */ 1.395 + description.ReplaceSubstring("\"", "\\\""); // " --> \" 1.396 + description.ReplaceSubstring("\n", "\\n"); // <newline> --> \n 1.397 + DUMP(mWriter, ", \"description\": \""); 1.398 + DUMP(mWriter, description); 1.399 + DUMP(mWriter, "\"}"); 1.400 + 1.401 + return NS_OK; 1.402 + } 1.403 + 1.404 +private: 1.405 + bool mIsFirst; 1.406 + nsRefPtr<nsGZFileWriter> mWriter; 1.407 +}; 1.408 + 1.409 +NS_IMPL_ISUPPORTS(DumpReportCallback, nsIHandleReportCallback) 1.410 + 1.411 +} // namespace mozilla 1.412 + 1.413 +static void 1.414 +MakeFilename(const char *aPrefix, const nsAString &aIdentifier, 1.415 + const char *aSuffix, nsACString &aResult) 1.416 +{ 1.417 + aResult = nsPrintfCString("%s-%s-%d.%s", 1.418 + aPrefix, 1.419 + NS_ConvertUTF16toUTF8(aIdentifier).get(), 1.420 + getpid(), aSuffix); 1.421 +} 1.422 + 1.423 +#ifdef MOZ_DMD 1.424 +struct DMDWriteState 1.425 +{ 1.426 + static const size_t kBufSize = 4096; 1.427 + char mBuf[kBufSize]; 1.428 + nsRefPtr<nsGZFileWriter> mGZWriter; 1.429 + 1.430 + DMDWriteState(nsGZFileWriter *aGZWriter) 1.431 + : mGZWriter(aGZWriter) 1.432 + {} 1.433 +}; 1.434 + 1.435 +static void 1.436 +DMDWrite(void* aState, const char* aFmt, va_list ap) 1.437 +{ 1.438 + DMDWriteState *state = (DMDWriteState*)aState; 1.439 + vsnprintf(state->mBuf, state->kBufSize, aFmt, ap); 1.440 + unused << state->mGZWriter->Write(state->mBuf); 1.441 +} 1.442 +#endif 1.443 + 1.444 +static nsresult 1.445 +DumpHeader(nsIGZFileWriter* aWriter) 1.446 +{ 1.447 + // Increment this number if the format changes. 1.448 + // 1.449 + // This is the first write to the file, and it causes |aWriter| to allocate 1.450 + // over 200 KiB of memory. 1.451 + // 1.452 + DUMP(aWriter, "{\n \"version\": 1,\n"); 1.453 + 1.454 + DUMP(aWriter, " \"hasMozMallocUsableSize\": "); 1.455 + 1.456 + nsCOMPtr<nsIMemoryReporterManager> mgr = 1.457 + do_GetService("@mozilla.org/memory-reporter-manager;1"); 1.458 + if (NS_WARN_IF(!mgr)) 1.459 + return NS_ERROR_UNEXPECTED; 1.460 + 1.461 + DUMP(aWriter, mgr->GetHasMozMallocUsableSize() ? "true" : "false"); 1.462 + DUMP(aWriter, ",\n"); 1.463 + DUMP(aWriter, " \"reports\": "); 1.464 + 1.465 + return NS_OK; 1.466 +} 1.467 + 1.468 +static nsresult 1.469 +DumpFooter(nsIGZFileWriter* aWriter) 1.470 +{ 1.471 + DUMP(aWriter, "\n ]\n}\n"); 1.472 + 1.473 + return NS_OK; 1.474 +} 1.475 + 1.476 +class TempDirMemoryFinishCallback MOZ_FINAL : public nsIFinishReportingCallback 1.477 +{ 1.478 +public: 1.479 + NS_DECL_ISUPPORTS 1.480 + 1.481 + TempDirMemoryFinishCallback(nsGZFileWriter *aWriter, 1.482 + nsIFile *aTmpFile, 1.483 + const nsCString &aFilename, 1.484 + const nsString &aIdentifier) 1.485 + : mrWriter(aWriter) 1.486 + , mrTmpFile(aTmpFile) 1.487 + , mrFilename(aFilename) 1.488 + , mIdentifier(aIdentifier) 1.489 + {} 1.490 + 1.491 + NS_IMETHOD Callback(nsISupports *aData); 1.492 + 1.493 +private: 1.494 + nsRefPtr<nsGZFileWriter> mrWriter; 1.495 + nsCOMPtr<nsIFile> mrTmpFile; 1.496 + nsCString mrFilename; 1.497 + nsString mIdentifier; 1.498 +}; 1.499 + 1.500 +NS_IMPL_ISUPPORTS(TempDirMemoryFinishCallback, nsIFinishReportingCallback) 1.501 + 1.502 +NS_IMETHODIMP 1.503 +nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier, 1.504 + bool aMinimizeMemoryUsage) 1.505 +{ 1.506 + nsString identifier(aIdentifier); 1.507 + EnsureNonEmptyIdentifier(identifier); 1.508 + 1.509 +#ifdef MOZ_DMD 1.510 + // Clear DMD's reportedness state before running the memory reporters, to 1.511 + // avoid spurious twice-reported warnings. 1.512 + dmd::ClearReports(); 1.513 +#endif 1.514 + 1.515 + // Open a new file named something like 1.516 + // 1.517 + // incomplete-memory-report-<identifier>-<pid>.json.gz 1.518 + // 1.519 + // in NS_OS_TEMP_DIR for writing. When we're finished writing the report, 1.520 + // we'll rename this file and get rid of the "incomplete-" prefix. 1.521 + // 1.522 + // We do this because we don't want scripts which poll the filesystem 1.523 + // looking for memory report dumps to grab a file before we're finished 1.524 + // writing to it. 1.525 + 1.526 + // Note that |mrFilename| is missing the "incomplete-" prefix; we'll tack 1.527 + // that on in a moment. 1.528 + nsCString mrFilename; 1.529 + // The "unified" indicates that we merge the memory reports from all 1.530 + // processes and write out one file, rather than a separate file for 1.531 + // each process as was the case before bug 946407. This is so that 1.532 + // the get_about_memory.py script in the B2G repository can 1.533 + // determine when it's done waiting for files to appear. 1.534 + MakeFilename("unified-memory-report", identifier, "json.gz", mrFilename); 1.535 + 1.536 + nsCOMPtr<nsIFile> mrTmpFile; 1.537 + nsresult rv; 1.538 + // In Android case, this function will open a file named aFilename under 1.539 + // specific folder (/data/local/tmp/memory-reports). Otherwise, it will 1.540 + // open a file named aFilename under "NS_OS_TEMP_DIR". 1.541 + rv = nsDumpUtils::OpenTempFile(NS_LITERAL_CSTRING("incomplete-") + 1.542 + mrFilename, 1.543 + getter_AddRefs(mrTmpFile), 1.544 + NS_LITERAL_CSTRING("memory-reports")); 1.545 + if (NS_WARN_IF(NS_FAILED(rv))) 1.546 + return rv; 1.547 + 1.548 + nsRefPtr<nsGZFileWriter> mrWriter = new nsGZFileWriter(); 1.549 + rv = mrWriter->Init(mrTmpFile); 1.550 + if (NS_WARN_IF(NS_FAILED(rv))) 1.551 + return rv; 1.552 + 1.553 + // Dump the memory reports to the file. 1.554 + rv = DumpHeader(mrWriter); 1.555 + if (NS_WARN_IF(NS_FAILED(rv))) 1.556 + return rv; 1.557 + 1.558 + // Process reporters. 1.559 + nsCOMPtr<nsIMemoryReporterManager> mgr = 1.560 + do_GetService("@mozilla.org/memory-reporter-manager;1"); 1.561 + nsRefPtr<DumpReportCallback> dumpReport = new DumpReportCallback(mrWriter); 1.562 + nsRefPtr<nsIFinishReportingCallback> finishReport = 1.563 + new TempDirMemoryFinishCallback(mrWriter, mrTmpFile, mrFilename, identifier); 1.564 + rv = mgr->GetReportsExtended(dumpReport, nullptr, 1.565 + finishReport, nullptr, 1.566 + aMinimizeMemoryUsage, 1.567 + identifier); 1.568 + return rv; 1.569 +} 1.570 + 1.571 +#ifdef MOZ_DMD 1.572 +nsresult 1.573 +nsMemoryInfoDumper::DumpDMD(const nsAString &aIdentifier) 1.574 +{ 1.575 + if (!dmd::IsEnabled()) { 1.576 + return NS_OK; 1.577 + } 1.578 + 1.579 + nsresult rv; 1.580 + 1.581 + // Create a filename like dmd-<identifier>-<pid>.txt.gz, which will be used 1.582 + // if DMD is enabled. 1.583 + nsCString dmdFilename; 1.584 + MakeFilename("dmd", aIdentifier, "txt.gz", dmdFilename); 1.585 + 1.586 + // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing, 1.587 + // and dump DMD output to it. This must occur after the memory reporters 1.588 + // have been run (above), but before the memory-reports file has been 1.589 + // renamed (so scripts can detect the DMD file, if present). 1.590 + 1.591 + nsCOMPtr<nsIFile> dmdFile; 1.592 + rv = nsDumpUtils::OpenTempFile(dmdFilename, 1.593 + getter_AddRefs(dmdFile), 1.594 + NS_LITERAL_CSTRING("memory-reports")); 1.595 + if (NS_WARN_IF(NS_FAILED(rv))) 1.596 + return rv; 1.597 + 1.598 + nsRefPtr<nsGZFileWriter> dmdWriter = new nsGZFileWriter(); 1.599 + rv = dmdWriter->Init(dmdFile); 1.600 + if (NS_WARN_IF(NS_FAILED(rv))) 1.601 + return rv; 1.602 + 1.603 + // Dump DMD output to the file. 1.604 + 1.605 + DMDWriteState state(dmdWriter); 1.606 + dmd::Writer w(DMDWrite, &state); 1.607 + dmd::Dump(w); 1.608 + 1.609 + rv = dmdWriter->Finish(); 1.610 + NS_WARN_IF(NS_FAILED(rv)); 1.611 + return rv; 1.612 +} 1.613 +#endif // MOZ_DMD 1.614 + 1.615 +NS_IMETHODIMP 1.616 +TempDirMemoryFinishCallback::Callback(nsISupports *aData) 1.617 +{ 1.618 + nsresult rv; 1.619 + 1.620 + rv = DumpFooter(mrWriter); 1.621 + if (NS_WARN_IF(NS_FAILED(rv))) 1.622 + return rv; 1.623 + 1.624 + // The call to Finish() deallocates the memory allocated by mrWriter's first 1.625 + // DUMP() call (within DumpProcessMemoryReportsToGZFileWriter()). Because 1.626 + // that memory was live while the memory reporters ran and thus measured by 1.627 + // them -- by "heap-allocated" if nothing else -- we want DMD to see it as 1.628 + // well. So we deliberately don't call Finish() until after DMD finishes. 1.629 + rv = mrWriter->Finish(); 1.630 + if (NS_WARN_IF(NS_FAILED(rv))) 1.631 + return rv; 1.632 + 1.633 + // Rename the memory reports file, now that we're done writing all the files. 1.634 + // Its final name is "memory-report<-identifier>-<pid>.json.gz". 1.635 + 1.636 + nsCOMPtr<nsIFile> mrFinalFile; 1.637 + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mrFinalFile)); 1.638 + if (NS_WARN_IF(NS_FAILED(rv))) 1.639 + return rv; 1.640 + 1.641 +#ifdef ANDROID 1.642 + rv = mrFinalFile->AppendNative(NS_LITERAL_CSTRING("memory-reports")); 1.643 + if (NS_WARN_IF(NS_FAILED(rv))) 1.644 + return rv; 1.645 +#endif 1.646 + 1.647 + rv = mrFinalFile->AppendNative(mrFilename); 1.648 + if (NS_WARN_IF(NS_FAILED(rv))) 1.649 + return rv; 1.650 + 1.651 + rv = mrFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); 1.652 + if (NS_WARN_IF(NS_FAILED(rv))) 1.653 + return rv; 1.654 + 1.655 + nsAutoString mrActualFinalFilename; 1.656 + rv = mrFinalFile->GetLeafName(mrActualFinalFilename); 1.657 + if (NS_WARN_IF(NS_FAILED(rv))) 1.658 + return rv; 1.659 + 1.660 + rv = mrTmpFile->MoveTo(/* directory */ nullptr, mrActualFinalFilename); 1.661 + if (NS_WARN_IF(NS_FAILED(rv))) 1.662 + return rv; 1.663 + 1.664 + // Write a message to the console. 1.665 + 1.666 + nsCOMPtr<nsIConsoleService> cs = 1.667 + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); 1.668 + if (NS_WARN_IF(NS_FAILED(rv))) 1.669 + return rv; 1.670 + 1.671 + nsString path; 1.672 + mrTmpFile->GetPath(path); 1.673 + if (NS_WARN_IF(NS_FAILED(rv))) 1.674 + return rv; 1.675 + 1.676 + nsString msg = NS_LITERAL_STRING( 1.677 + "nsIMemoryInfoDumper dumped reports to "); 1.678 + msg.Append(path); 1.679 + return cs->LogStringMessage(msg.get()); 1.680 +} 1.681 + 1.682 +// This dumps the JSON footer and closes the file, and then calls the given 1.683 +// nsIFinishDumpingCallback. 1.684 +class FinishReportingCallback MOZ_FINAL : public nsIFinishReportingCallback 1.685 +{ 1.686 +public: 1.687 + NS_DECL_ISUPPORTS 1.688 + 1.689 + FinishReportingCallback(nsIFinishDumpingCallback* aFinishDumping, 1.690 + nsISupports* aFinishDumpingData) 1.691 + : mFinishDumping(aFinishDumping) 1.692 + , mFinishDumpingData(aFinishDumpingData) 1.693 + {} 1.694 + 1.695 + NS_IMETHOD Callback(nsISupports* aData) 1.696 + { 1.697 + nsCOMPtr<nsIGZFileWriter> writer = do_QueryInterface(aData); 1.698 + NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE); 1.699 + 1.700 + nsresult rv = DumpFooter(writer); 1.701 + NS_ENSURE_SUCCESS(rv, rv); 1.702 + 1.703 + rv = writer->Finish(); 1.704 + NS_ENSURE_SUCCESS(rv, rv); 1.705 + 1.706 + if (!mFinishDumping) { 1.707 + return NS_OK; 1.708 + } 1.709 + 1.710 + return mFinishDumping->Callback(mFinishDumpingData); 1.711 + } 1.712 + 1.713 +private: 1.714 + nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping; 1.715 + nsCOMPtr<nsISupports> mFinishDumpingData; 1.716 +}; 1.717 + 1.718 +NS_IMPL_ISUPPORTS(FinishReportingCallback, nsIFinishReportingCallback) 1.719 + 1.720 +NS_IMETHODIMP 1.721 +nsMemoryInfoDumper::DumpMemoryReportsToNamedFile( 1.722 + const nsAString& aFilename, 1.723 + nsIFinishDumpingCallback* aFinishDumping, 1.724 + nsISupports* aFinishDumpingData) 1.725 +{ 1.726 + MOZ_ASSERT(!aFilename.IsEmpty()); 1.727 + 1.728 + // Create the file. 1.729 + 1.730 + nsCOMPtr<nsIFile> mrFile; 1.731 + nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(mrFile)); 1.732 + if (NS_WARN_IF(NS_FAILED(rv))) 1.733 + return rv; 1.734 + 1.735 + mrFile->InitWithPath(aFilename); 1.736 + if (NS_WARN_IF(NS_FAILED(rv))) 1.737 + return rv; 1.738 + 1.739 + bool exists; 1.740 + rv = mrFile->Exists(&exists); 1.741 + if (NS_WARN_IF(NS_FAILED(rv))) 1.742 + return rv; 1.743 + 1.744 + if (!exists) { 1.745 + rv = mrFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); 1.746 + if (NS_WARN_IF(NS_FAILED(rv))) 1.747 + return rv; 1.748 + } 1.749 + 1.750 + // Write the memory reports to the file. 1.751 + 1.752 + nsRefPtr<nsGZFileWriter> mrWriter = new nsGZFileWriter(); 1.753 + rv = mrWriter->Init(mrFile); 1.754 + if (NS_WARN_IF(NS_FAILED(rv))) 1.755 + return rv; 1.756 + 1.757 + rv = DumpHeader(mrWriter); 1.758 + if (NS_WARN_IF(NS_FAILED(rv))) 1.759 + return rv; 1.760 + 1.761 + // Process reports and finish up. 1.762 + nsRefPtr<DumpReportCallback> dumpReport = new DumpReportCallback(mrWriter); 1.763 + nsRefPtr<FinishReportingCallback> finishReporting = 1.764 + new FinishReportingCallback(aFinishDumping, aFinishDumpingData); 1.765 + nsCOMPtr<nsIMemoryReporterManager> mgr = 1.766 + do_GetService("@mozilla.org/memory-reporter-manager;1"); 1.767 + return mgr->GetReports(dumpReport, nullptr, finishReporting, mrWriter); 1.768 +} 1.769 + 1.770 +#undef DUMP