/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Some Android devices seem to send RT signals to Firefox so we want to avoid // consuming those as they're not user triggered. #if !defined(ANDROID) && (defined(XP_LINUX) || defined(__FreeBSD__)) # define MOZ_SUPPORTS_RT_SIGNALS 1 #endif
#ifdefined(MOZ_SUPPORTS_RT_SIGNALS) # include <fcntl.h> # include <sys/types.h> # include <sys/stat.h> #endif
#ifdefined(MOZ_SUPPORTS_FIFO) # include "mozilla/Preferences.h" #endif
/* * The following code supports dumping about:memory upon receiving a signal. * * We listen for the following signals: * * - SIGRTMIN: Dump our memory reporters (and those of our child * processes), * - SIGRTMIN + 1: Dump our memory reporters (and those of our child * processes) after minimizing memory usage, and * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes. * * When we receive one of these signals, we write the signal number to a pipe. * The IO thread then notices that the pipe has been written to, and kicks off * the appropriate task on the main thread. * * This scheme is similar to using signalfd(), except it's portable and it * doesn't require the use of sigprocmask, which is problematic because it * masks signals received by child processes.
*/
// It turns out that at least on some systems, SIGRTMIN is not a compile-time // constant, so these have to be set at runtime. static uint8_t sDumpAboutMemorySignum; // SIGRTMIN static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1 static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2
void doMemoryReport(const uint8_t aRecvSig) { // Dump our memory reports (but run this on the main thread!). bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum;
LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig);
RefPtr<DumpMemoryInfoToTempDirRunnable> runnable = new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns, /* anonymize = */ false, minimize);
NS_DispatchToMainThread(runnable);
}
void doGCCCDump(const uint8_t aRecvSig) {
LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig); // Dump GC and CC logs (from the main thread).
RefPtr<GCAndCCLogDumpRunnable> runnable = new GCAndCCLogDumpRunnable(/* identifier = */ u""_ns, /* allTraces = */ true, /* dumpChildProcesses = */ true);
NS_DispatchToMainThread(runnable);
}
MOZ_ASSERT(!fifoCallbacksRegistered, "FifoWatcher callbacks should be registered only once");
FifoWatcher* fw = FifoWatcher::GetSingleton(); // Dump our memory reports (but run this on the main thread!).
fw->RegisterCallback("memory report"_ns, doMemoryReport);
fw->RegisterCallback("minimize memory report"_ns, doMemoryReport); // Dump GC and CC logs (from the main thread).
fw->RegisterCallback("gc log"_ns, doGCCCDump);
fw->RegisterCallback("abbreviated gc log"_ns, doGCCCDump);
// Dump memory reporters (and those of our child processes)
sDumpAboutMemorySignum = SIGRTMIN;
sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport); // Dump our memory reporters after minimizing memory usage
sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport); // Dump the GC and CC logs in this and our child processes.
sGCAndCCDumpSignum = SIGRTMIN + 2;
sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump); #endif
#ifdefined(MOZ_SUPPORTS_FIFO) if (!SetupFifo()) { // NB: This gets loaded early enough that it's possible there is a user pref // set to enable the fifo watcher that has not been loaded yet. Register // to attempt to initialize if the fifo watcher becomes enabled by // a user pref.
Preferences::RegisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName);
} #endif
}
staticvoid EnsureNonEmptyIdentifier(nsAString& aIdentifier) { if (!aIdentifier.IsEmpty()) { return;
}
// If the identifier is empty, set it to the number of whole seconds since the // epoch. This identifier will appear in the files that this process // generates and also the files generated by this process's children, allowing // us to identify which files are from the same memory report request.
aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
}
// Use XPCOM refcounting to fire |onFinish| when all reference-holders // (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself) // have gone away. class nsDumpGCAndCCLogsCallbackHolder final
: public nsIDumpGCAndCCLogsCallback { public:
NS_DECL_ISUPPORTS
// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming // the following two problems: // - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write(). // - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted. class GZWriterWrapper final : public JSONWriteFunc { public: explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {}
void Write(const Span<constchar>& aStr) final { // Ignore any failure because JSONWriteFunc doesn't have a mechanism for // handling errors.
Unused << mGZWriter->Write(aStr.data(), aStr.size());
}
nsresult Finish() { return mGZWriter->Finish(); }
private:
RefPtr<nsGZFileWriter> mGZWriter;
};
// We need two callbacks: one that handles reports, and one that is called at // the end of reporting. Both the callbacks need access to the same JSONWriter, // so we implement both of them in this one class. class HandleReportAndFinishReportingCallbacks final
: public nsIHandleReportCallback, public nsIFinishReportingCallback { public:
NS_DECL_ISUPPORTS
// This is the callback for nsIHandleReportCallback.
NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
int32_t aKind, int32_t aUnits, int64_t aAmount, const nsACString& aDescription,
nsISupports* aData) override {
nsAutoCString process; if (aProcess.IsEmpty()) { // If the process is empty, the report originated with the process doing // the dumping. In that case, generate the process identifier, which is // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we // don't have a process name. If we're the main process, we let // $PROCESS_NAME be "Main Process". // // `appendAboutMemoryMain()` in aboutMemory.js does much the same thing // for live memory reports. if (XRE_IsParentProcess()) { // We're the main process.
process.AssignLiteral("Main Process");
} elseif (ContentChild* cc = ContentChild::GetSingleton()) { // Try to get the process name from ContentChild.
cc->GetProcessName(process);
}
ContentChild::AppendProcessId(process);
} else { // Otherwise, the report originated with another process and already has a // process name. Just use that.
process = aProcess;
}
// This is the callback for nsIFinishReportingCallback.
NS_IMETHOD Callback(nsISupports* aData) override {
mWriter->EndArray(); // end of "reports" array
mWriter->End();
// The call to Finish() deallocates the memory allocated by the first Write // call. Because that memory was live while the memory reporters ran and // was measured by them -- by "heap-allocated" if nothing else -- we want // DMD to see it as well. So we deliberately don't call Finish() until // after DMD finishes.
nsresult rv = static_cast<GZWriterWrapper&>(mWriter->WriteFunc()).Finish();
NS_ENSURE_SUCCESS(rv, rv);
NS_IMETHOD Callback(nsISupports* aData) override { // Rename the memory reports file, now that we're done writing all the // files. Its final name is "memory-report<-identifier>-<pid>.json.gz".
// This is the first write to the file, and it causes |aWriter| to allocate // over 200 KiB of memory.
jsonWriter->Start();
{ // Increment this number if the format changes.
jsonWriter->IntProperty("version", 1);
jsonWriter->BoolProperty("hasMozMallocUsableSize",
mgr->GetHasMozMallocUsableSize());
jsonWriter->StartArrayProperty("reports");
}
// Open a new file named something like // // incomplete-memory-report-<identifier>-<pid>.json.gz // // in NS_OS_TEMP_DIR for writing. When we're finished writing the report, // we'll rename this file and get rid of the "incomplete-" prefix. // // We do this because we don't want scripts which poll the filesystem // looking for memory report dumps to grab a file before we're finished // writing to it.
// The "unified" indicates that we merge the memory reports from all // processes and write out one file, rather than a separate file for // each process as was the case before bug 946407. This is so that // the get_about_memory.py script in the B2G repository can // determine when it's done waiting for files to appear.
nsCString reportsFinalFilename;
MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
reportsFinalFilename);
nsCOMPtr<nsIFile> reportsTmpFile;
nsresult rv; // In Android case, this function will open a file named aFilename under // specific folder (/data/local/tmp/memory-reports). Otherwise, it will // open a file named aFilename under "NS_OS_TEMP_DIR".
rv = nsDumpUtils::OpenTempFile("incomplete-"_ns + reportsFinalFilename,
getter_AddRefs(reportsTmpFile), "memory-reports"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
RefPtr<TempDirFinishCallback> finishDumping = new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
FILE** aOutFile) { if (!dmd::IsRunning()) {
*aOutFile = nullptr; return NS_OK;
}
// Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used // if DMD is enabled.
nsCString dmdFilename;
MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
// Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing, // and dump DMD output to it. This must occur after the memory reporters // have been run (above), but before the memory-reports file has been // renamed (so scripts can detect the DMD file, if present).
// Print the path, because on some platforms (e.g. Mac) it's not obvious.
dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().get());
return rv;
}
nsresult nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) {
RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
nsresult rv = gzWriter->InitANSIFileDesc(aFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
// Dump DMD's memory reports analysis to the file.
dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter));