Bug 973090 - IPC remoting for child GC/CC logging. r=mccr8, r=bent

This has a few semi-interdependent pieces:

* Factoring out the file opening/closing/renaming from the GC/CC logging.

* Using IPC to have the child log to files that the parent opened.

* Changing nsIMemoryInfoDumper.dumpGCAndCCLogsToFile to report completion
  of child process logging (which was impossible before this, and which is
  needed to have a meaningful test case).

* Changing about:memory to dump logs for child processes, matching the
  behavior of the "Measure" button, because it can tell the user where
  they are now.

* Add a test for multiprocess GC/CC log dumping (only of the XPCOM
  interface, not by clicking buttons and scraping the about:memory page,
  but done as a chrome mochitest to start remote browsers); based on
  test_memoryReporters2.xul in the same directory.
This commit is contained in:
Jed Davis 2014-05-13 13:13:00 -04:00
parent 1170b7a391
commit e4ced412bb
17 changed files with 928 additions and 236 deletions

View File

@ -25,6 +25,7 @@
#include "mozilla/dom/DOMStorageIPC.h"
#include "mozilla/hal_sandbox/PHalChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "mozilla/ipc/GeckoChildProcessHost.h"
#include "mozilla/ipc/TestShellChild.h"
#include "mozilla/layers/CompositorChild.h"
@ -46,6 +47,7 @@
#include "mozilla/unused.h"
#include "nsIConsoleListener.h"
#include "nsICycleCollectorListener.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIMemoryReporter.h"
@ -78,6 +80,7 @@
#include "nsIGeolocationProvider.h"
#include "mozilla/dom/PMemoryReportRequestChild.h"
#include "mozilla/dom/PCycleCollectWithLogsChild.h"
#ifdef MOZ_PERMISSIONS
#include "nsIScriptSecurityManager.h"
@ -203,6 +206,108 @@ MemoryReportRequestChild::~MemoryReportRequestChild()
MOZ_COUNT_DTOR(MemoryReportRequestChild);
}
// IPC sender for remote GC/CC logging.
class CycleCollectWithLogsChild MOZ_FINAL
: public PCycleCollectWithLogsChild
, public nsICycleCollectorLogSink
{
public:
NS_DECL_ISUPPORTS
CycleCollectWithLogsChild(const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog)
{
mGCLog = FileDescriptorToFILE(aGCLog, "w");
mCCLog = FileDescriptorToFILE(aCCLog, "w");
}
NS_IMETHOD Open(FILE** aGCLog, FILE** aCCLog) MOZ_OVERRIDE
{
if (NS_WARN_IF(!mGCLog) || NS_WARN_IF(!mCCLog)) {
return NS_ERROR_FAILURE;
}
*aGCLog = mGCLog;
*aCCLog = mCCLog;
return NS_OK;
}
NS_IMETHOD CloseGCLog() MOZ_OVERRIDE
{
MOZ_ASSERT(mGCLog);
fclose(mGCLog);
mGCLog = nullptr;
SendCloseGCLog();
return NS_OK;
}
NS_IMETHOD CloseCCLog() MOZ_OVERRIDE
{
MOZ_ASSERT(mCCLog);
fclose(mCCLog);
mCCLog = nullptr;
SendCloseCCLog();
return NS_OK;
}
NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) MOZ_OVERRIDE
{
return UnimplementedProperty();
}
NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) MOZ_OVERRIDE
{
return UnimplementedProperty();
}
NS_IMETHOD GetProcessIdentifier(int32_t *aIdentifier) MOZ_OVERRIDE
{
return UnimplementedProperty();
}
NS_IMETHOD SetProcessIdentifier(int32_t aIdentifier) MOZ_OVERRIDE
{
return UnimplementedProperty();
}
NS_IMETHOD GetGcLog(nsIFile** aPath) MOZ_OVERRIDE
{
return UnimplementedProperty();
}
NS_IMETHOD GetCcLog(nsIFile** aPath) MOZ_OVERRIDE
{
return UnimplementedProperty();
}
private:
~CycleCollectWithLogsChild()
{
if (mGCLog) {
fclose(mGCLog);
mGCLog = nullptr;
}
if (mCCLog) {
fclose(mCCLog);
mCCLog = nullptr;
}
// The XPCOM refcount drives the IPC lifecycle; see also
// DeallocPCycleCollectWithLogsChild.
unused << Send__delete__(this);
}
nsresult UnimplementedProperty()
{
MOZ_ASSERT(false, "This object is a remote GC/CC logger;"
" this property isn't meaningful.");
return NS_ERROR_UNEXPECTED;
}
FILE* mGCLog;
FILE* mCCLog;
};
NS_IMPL_ISUPPORTS(CycleCollectWithLogsChild, nsICycleCollectorLogSink);
class AlertObserver
{
public:
@ -669,16 +774,39 @@ ContentChild::DeallocPMemoryReportRequestChild(PMemoryReportRequestChild* actor)
return true;
}
bool
ContentChild::RecvDumpGCAndCCLogsToFile(const nsString& aIdentifier,
const bool& aDumpAllTraces,
const bool& aDumpChildProcesses)
PCycleCollectWithLogsChild*
ContentChild::AllocPCycleCollectWithLogsChild(const bool& aDumpAllTraces,
const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog)
{
CycleCollectWithLogsChild* actor = new CycleCollectWithLogsChild(aGCLog, aCCLog);
// Return actor with refcount 0, which is safe because it has a non-XPCOM type.
return actor;
}
bool
ContentChild::RecvPCycleCollectWithLogsConstructor(PCycleCollectWithLogsChild* aActor,
const bool& aDumpAllTraces,
const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog)
{
// Take a reference here, where the XPCOM type is regained.
nsRefPtr<CycleCollectWithLogsChild> sink = static_cast<CycleCollectWithLogsChild*>(aActor);
nsCOMPtr<nsIMemoryInfoDumper> dumper = do_GetService("@mozilla.org/memory-info-dumper;1");
nsString gcLogPath, ccLogPath;
dumper->DumpGCAndCCLogsToFile(aIdentifier, aDumpAllTraces,
aDumpChildProcesses, gcLogPath, ccLogPath);
dumper->DumpGCAndCCLogsToSink(aDumpAllTraces, sink);
// The actor's destructor is called when the last reference goes away...
return true;
}
bool
ContentChild::DeallocPCycleCollectWithLogsChild(PCycleCollectWithLogsChild* /* aActor */)
{
// ...so when we get here, there's nothing for us to do.
//
// Also, we're already in ~CycleCollectWithLogsChild (q.v.) at
// this point, so we shouldn't touch the actor in any case.
return true;
}

View File

@ -142,13 +142,20 @@ public:
const bool &minimizeMemoryUsage,
const nsString &aDMDDumpIdent) MOZ_OVERRIDE;
virtual PCycleCollectWithLogsChild*
AllocPCycleCollectWithLogsChild(const bool& aDumpAllTraces,
const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog) MOZ_OVERRIDE;
virtual bool
RecvAudioChannelNotify() MOZ_OVERRIDE;
DeallocPCycleCollectWithLogsChild(PCycleCollectWithLogsChild* aActor) MOZ_OVERRIDE;
virtual bool
RecvPCycleCollectWithLogsConstructor(PCycleCollectWithLogsChild* aChild,
const bool& aDumpAllTraces,
const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog) MOZ_OVERRIDE;
virtual bool
RecvDumpGCAndCCLogsToFile(const nsString& aIdentifier,
const bool& aDumpAllTraces,
const bool& aDumpChildProcesses) MOZ_OVERRIDE;
RecvAudioChannelNotify() MOZ_OVERRIDE;
virtual PTestShellChild* AllocPTestShellChild() MOZ_OVERRIDE;
virtual bool DeallocPTestShellChild(PTestShellChild*) MOZ_OVERRIDE;

View File

@ -35,6 +35,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ExternalHelperAppParent.h"
#include "mozilla/dom/PFileDescriptorSetParent.h"
#include "mozilla/dom/PCycleCollectWithLogsParent.h"
#include "mozilla/dom/PMemoryReportRequestParent.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/DOMStorageIPC.h"
@ -50,6 +51,7 @@
#include "mozilla/hal_sandbox/PHalParent.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "mozilla/ipc/TestShellParent.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/layers/CompositorParent.h"
@ -75,12 +77,14 @@
#include "nsIAlertsService.h"
#include "nsIAppsService.h"
#include "nsIClipboard.h"
#include "nsICycleCollectorListener.h"
#include "nsIDOMGeoGeolocation.h"
#include "mozilla/dom/WakeLock.h"
#include "nsIDOMWindow.h"
#include "nsIExternalProtocolService.h"
#include "nsIGfxInfo.h"
#include "nsIIdleService.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIMozBrowserFrame.h"
#include "nsIMutable.h"
@ -353,6 +357,82 @@ MemoryReportRequestParent::~MemoryReportRequestParent()
MOZ_COUNT_DTOR(MemoryReportRequestParent);
}
// IPC receiver for remote GC/CC logging.
class CycleCollectWithLogsParent MOZ_FINAL : public PCycleCollectWithLogsParent
{
public:
~CycleCollectWithLogsParent()
{
MOZ_COUNT_DTOR(CycleCollectWithLogsParent);
}
static bool AllocAndSendConstructor(ContentParent* aManager,
bool aDumpAllTraces,
nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback)
{
CycleCollectWithLogsParent *actor;
FILE* gcLog;
FILE* ccLog;
nsresult rv;
actor = new CycleCollectWithLogsParent(aSink, aCallback);
rv = actor->mSink->Open(&gcLog, &ccLog);
if (NS_WARN_IF(NS_FAILED(rv))) {
delete actor;
return false;
}
return aManager->
SendPCycleCollectWithLogsConstructor(actor,
aDumpAllTraces,
FILEToFileDescriptor(gcLog),
FILEToFileDescriptor(ccLog));
}
private:
virtual bool RecvCloseGCLog() MOZ_OVERRIDE
{
unused << mSink->CloseGCLog();
return true;
}
virtual bool RecvCloseCCLog() MOZ_OVERRIDE
{
unused << mSink->CloseCCLog();
return true;
}
virtual bool Recv__delete__() MOZ_OVERRIDE
{
// Report completion to mCallback only on successful
// completion of the protocol.
nsCOMPtr<nsIFile> gcLog, ccLog;
mSink->GetGcLog(getter_AddRefs(gcLog));
mSink->GetCcLog(getter_AddRefs(ccLog));
unused << mCallback->OnDump(gcLog, ccLog, /* parent = */ false);
return true;
}
virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE
{
// If the actor is unexpectedly destroyed, we deliberately
// don't call Close[GC]CLog on the sink, because the logs may
// be incomplete. See also the nsCycleCollectorLogSinkToFile
// implementaiton of those methods, and its destructor.
}
CycleCollectWithLogsParent(nsICycleCollectorLogSink *aSink,
nsIDumpGCAndCCLogsCallback *aCallback)
: mSink(aSink), mCallback(aCallback)
{
MOZ_COUNT_CTOR(CycleCollectWithLogsParent);
}
nsCOMPtr<nsICycleCollectorLogSink> mSink;
nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
};
// A memory reporter for ContentParent objects themselves.
class ContentParentsMemoryReporter MOZ_FINAL : public nsIMemoryReporter
{
@ -2590,6 +2670,32 @@ ContentParent::DeallocPMemoryReportRequestParent(PMemoryReportRequestParent* act
return true;
}
PCycleCollectWithLogsParent*
ContentParent::AllocPCycleCollectWithLogsParent(const bool& aDumpAllTraces,
const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog)
{
MOZ_CRASH("Don't call this; use ContentParent::CycleCollectWithLogs");
}
bool
ContentParent::DeallocPCycleCollectWithLogsParent(PCycleCollectWithLogsParent* aActor)
{
delete aActor;
return true;
}
bool
ContentParent::CycleCollectWithLogs(bool aDumpAllTraces,
nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback)
{
return CycleCollectWithLogsParent::AllocAndSendConstructor(this,
aDumpAllTraces,
aSink,
aCallback);
}
PTestShellParent*
ContentParent::AllocPTestShellParent()
{

View File

@ -28,7 +28,9 @@
class mozIApplication;
class nsConsoleService;
class nsICycleCollectorLogSink;
class nsIDOMBlob;
class nsIDumpGCAndCCLogsCallback;
class nsIMemoryReporter;
class ParentIdleListener;
@ -224,6 +226,11 @@ public:
const nsString& aPageURL,
const bool& aIsAudio,
const bool& aIsVideo) MOZ_OVERRIDE;
bool CycleCollectWithLogs(bool aDumpAllTraces,
nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback);
protected:
void OnChannelConnected(int32_t pid) MOZ_OVERRIDE;
virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
@ -374,6 +381,13 @@ private:
const nsString &aDMDDumpIdent) MOZ_OVERRIDE;
virtual bool DeallocPMemoryReportRequestParent(PMemoryReportRequestParent* actor) MOZ_OVERRIDE;
virtual PCycleCollectWithLogsParent*
AllocPCycleCollectWithLogsParent(const bool& aDumpAllTraces,
const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog) MOZ_OVERRIDE;
virtual bool
DeallocPCycleCollectWithLogsParent(PCycleCollectWithLogsParent* aActor) MOZ_OVERRIDE;
virtual PTestShellParent* AllocPTestShellParent() MOZ_OVERRIDE;
virtual bool DeallocPTestShellParent(PTestShellParent* shell) MOZ_OVERRIDE;

View File

@ -10,6 +10,7 @@ include protocol PBlob;
include protocol PBluetooth;
include protocol PBrowser;
include protocol PCompositor;
include protocol PCycleCollectWithLogs;
include protocol PCrashReporter;
include protocol PExternalHelperApp;
include protocol PDeviceStorageRequest;
@ -273,6 +274,7 @@ intr protocol PContent
manages PBluetooth;
manages PBrowser;
manages PCrashReporter;
manages PCycleCollectWithLogs;
manages PDeviceStorageRequest;
manages PFileSystemRequest;
manages PExternalHelperApp;
@ -337,14 +339,14 @@ child:
async SpeakerManagerNotify();
/**
* Dump this process's GC and CC logs.
* Dump this process's GC and CC logs to the provided files.
*
* For documentation on the args, see dumpGCAndCCLogsToFile in
* For documentation on the other args, see dumpGCAndCCLogsToFile in
* nsIMemoryInfoDumper.idl
*/
async DumpGCAndCCLogsToFile(nsString identifier,
bool dumpAllTraces,
bool dumpChildProcesses);
PCycleCollectWithLogs(bool dumpAllTraces,
FileDescriptor gcLog,
FileDescriptor ccLog);
PTestShell();

View File

@ -0,0 +1,22 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
include protocol PContent;
namespace mozilla {
namespace dom {
protocol PCycleCollectWithLogs {
manager PContent;
parent:
CloseGCLog();
CloseCCLog();
__delete__();
};
}
}

View File

@ -78,6 +78,7 @@ IPDL_SOURCES += [
'PContentPermission.ipdlh',
'PContentPermissionRequest.ipdl',
'PCrashReporter.ipdl',
'PCycleCollectWithLogs.ipdl',
'PDocumentRenderer.ipdl',
'PFileDescriptorSet.ipdl',
'PFilePicker.ipdl',

View File

@ -14,6 +14,13 @@
#include "prio.h"
#include "private/pprio.h"
#include <errno.h>
#ifdef XP_WIN
#include <io.h>
#else
#include <unistd.h>
#endif
using mozilla::ipc::CloseFileRunnable;
#ifdef DEBUG
@ -76,3 +83,54 @@ CloseFileRunnable::Run()
CloseFile();
return NS_OK;
}
namespace mozilla {
namespace ipc {
FILE*
FileDescriptorToFILE(const FileDescriptor& aDesc,
const char* aOpenMode)
{
if (!aDesc.IsValid()) {
errno = EBADF;
return nullptr;
}
FileDescriptor::PlatformHandleType handle = aDesc.PlatformHandle();
#ifdef XP_WIN
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), 0);
if (fd == -1) {
CloseHandle(handle);
return nullptr;
}
#else
int fd = handle;
#endif
FILE* file = fdopen(fd, aOpenMode);
if (!file) {
int saved_errno = errno;
close(fd);
errno = saved_errno;
}
return file;
}
FileDescriptor
FILEToFileDescriptor(FILE* aStream)
{
if (!aStream) {
errno = EBADF;
return FileDescriptor();
}
#ifdef XP_WIN
int fd = _fileno(aStream);
if (fd == -1) {
return FileDescriptor();
}
return FileDescriptor(reinterpret_cast<HANDLE>(_get_osfhandle(fd)));
#else
return FileDescriptor(fileno(aStream));
#endif
}
} // namespace ipc
} // namespace mozilla

View File

@ -9,6 +9,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "nsIRunnable.h"
#include <stdio.h>
namespace mozilla {
namespace ipc {
@ -42,6 +43,17 @@ private:
void CloseFile();
};
// On failure, FileDescriptorToFILE closes the given descriptor; on
// success, fclose()ing the returned FILE* will close the handle.
// This is meant for use with FileDescriptors received over IPC.
FILE* FileDescriptorToFILE(const FileDescriptor& aDesc,
const char* aOpenMode);
// FILEToFileDescriptor does not consume the given FILE*; it must be
// fclose()d as normal, and this does not invalidate the returned
// FileDescriptor.
FileDescriptor FILEToFileDescriptor(FILE* aStream);
} // namespace ipc
} // namespace mozilla

View File

@ -147,12 +147,13 @@ function updateMainAndFooter(aMsg, aFooterAction, aClassName)
gMain.classList.add(gVerbose.checked ? 'verbose' : 'non-verbose');
}
let msgElement;
if (aMsg) {
let className = "section"
if (aClassName) {
className = className + " " + aClassName;
}
appendElementWithText(gMain, 'div', className, aMsg);
msgElement = appendElementWithText(gMain, 'div', className, aMsg);
}
switch (aFooterAction) {
@ -160,6 +161,7 @@ function updateMainAndFooter(aMsg, aFooterAction, aClassName)
case SHOW_FOOTER: gFooter.classList.remove('hidden'); break;
default: assertInput(false, "bad footer action in updateMainAndFooter");
}
return msgElement;
}
function appendTextNode(aP, aText)
@ -413,25 +415,27 @@ function saveGCLogAndVerboseCCLog()
function dumpGCLogAndCCLog(aVerbose)
{
let gcLogPath = {};
let ccLogPath = {};
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
.getService(Ci.nsIMemoryInfoDumper);
updateMainAndFooter("Saving logs...", HIDE_FOOTER);
dumper.dumpGCAndCCLogsToFile("", aVerbose, /* dumpChildProcesses = */ false,
gcLogPath, ccLogPath);
updateMainAndFooter("", HIDE_FOOTER);
let inProgress = updateMainAndFooter("Saving logs...", HIDE_FOOTER);
let section = appendElement(gMain, 'div', "section");
appendElementWithText(section, 'div', "",
"Saved GC log to " + gcLogPath.value);
let ccLogType = aVerbose ? "verbose" : "concise";
appendElementWithText(section, 'div', "",
"Saved " + ccLogType + " CC log to " + ccLogPath.value);
function displayInfo(gcLog, ccLog, isParent) {
appendElementWithText(section, 'div', "",
"Saved GC log to " + gcLog.path);
let ccLogType = aVerbose ? "verbose" : "concise";
appendElementWithText(section, 'div', "",
"Saved " + ccLogType + " CC log to " + ccLog.path);
}
dumper.dumpGCAndCCLogsToFile("", aVerbose, /* dumpChildProcesses = */ true,
{ onDump: displayInfo,
onFinish: function() {
inProgress.remove();
}
});
}
/**

View File

@ -15,3 +15,4 @@ support-files =
[test_memoryReporters.xul]
[test_memoryReporters2.xul]
[test_sqliteMultiReporter.xul]
[test_dumpGCAndCCLogsToFile.xul]

View File

@ -0,0 +1,98 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="GC/CC logging with child processes"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
SimpleTest.waitForExplicitFinish();
let numRemotes = 3;
let numReady = 0;
// Create some remote processes, and set up message-passing so that
// we know when each child is fully initialized.
let remotes = [];
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", numRemotes]]},
function() {
for (let i = 0; i < numRemotes; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome");
w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler);
let remoteBrowser = w.document.getElementById("remote");
let mm = remoteBrowser.messageManager;
mm.addMessageListener("test:ready", function readyHandler() {
mm.removeMessageListener("test:ready", readyHandler);
numReady++;
if (numReady == numRemotes) {
// All the remote processes are ready. Run test.
runTest();
}
});
mm.loadFrameScript("data:," + encodeURI("sendAsyncMessage('test:ready');"), true);
});
}
});
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
function runTest()
{
let numParents = 0;
let numChildren = 0;
dumper.dumpGCAndCCLogsToFile(
/* identifier: */ "test." + Date.now(),
/* allTraces: */ false,
/* childProcesses: */ true,
{
onDump: function(gcLog, ccLog, isParent) {
if (isParent) {
numParents++;
} else {
numChildren++;
}
checkAndRemoveLog(gcLog);
checkAndRemoveLog(ccLog);
},
onFinish: function() {
is(numParents, 1,
"GC/CC logs for the parent process");
is(numChildren, numRemotes,
"GC/CC logs for each child process");
cleanUpAndFinish();
}
});
}
function cleanUpAndFinish() {
// Close the remote processes.
for (let i = 0; i < numRemotes; i++) {
remotes[i].close();
}
SimpleTest.finish();
}
function checkAndRemoveLog(logFile) {
let name = logFile.path;
ok(logFile.exists(), "log file "+name+" exists");
ok(logFile.isFile(), "log file "+name+" is a regular file");
ok(logFile.fileSize > 0, "log file "+name+" is not empty");
logFile.remove(/* recursive: */ false);
}
]]></script>
</window>

View File

@ -1465,22 +1465,241 @@ struct CCGraphDescriber : public LinkedListElement<CCGraphDescriber>
Type mType;
};
class nsCycleCollectorLogSinkToFile MOZ_FINAL : public nsICycleCollectorLogSink
{
public:
NS_DECL_ISUPPORTS
nsCycleCollectorLogSinkToFile() :
mProcessIdentifier(base::GetCurrentProcId()),
mGCLog("gc-edges"), mCCLog("cc-edges")
{
}
NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) MOZ_OVERRIDE
{
aIdentifier = mFilenameIdentifier;
return NS_OK;
}
NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) MOZ_OVERRIDE
{
mFilenameIdentifier = aIdentifier;
return NS_OK;
}
NS_IMETHOD GetProcessIdentifier(int32_t* aIdentifier) MOZ_OVERRIDE
{
*aIdentifier = mProcessIdentifier;
return NS_OK;
}
NS_IMETHOD SetProcessIdentifier(int32_t aIdentifier) MOZ_OVERRIDE
{
mProcessIdentifier = aIdentifier;
return NS_OK;
}
NS_IMETHOD GetGcLog(nsIFile** aPath) MOZ_OVERRIDE
{
NS_IF_ADDREF(*aPath = mGCLog.mFile);
return NS_OK;
}
NS_IMETHOD GetCcLog(nsIFile** aPath) MOZ_OVERRIDE
{
NS_IF_ADDREF(*aPath = mCCLog.mFile);
return NS_OK;
}
NS_IMETHOD Open(FILE** aGCLog, FILE** aCCLog) MOZ_OVERRIDE
{
nsresult rv;
if (mGCLog.mStream || mCCLog.mStream) {
return NS_ERROR_UNEXPECTED;
}
rv = OpenLog(&mGCLog);
NS_ENSURE_SUCCESS(rv, rv);
*aGCLog = mGCLog.mStream;
rv = OpenLog(&mCCLog);
NS_ENSURE_SUCCESS(rv, rv);
*aCCLog = mCCLog.mStream;
return NS_OK;
}
NS_IMETHOD CloseGCLog() MOZ_OVERRIDE
{
if (!mGCLog.mStream) {
return NS_ERROR_UNEXPECTED;
}
CloseLog(&mGCLog, NS_LITERAL_STRING("Garbage"));
return NS_OK;
}
NS_IMETHOD CloseCCLog() MOZ_OVERRIDE
{
if (!mCCLog.mStream) {
return NS_ERROR_UNEXPECTED;
}
CloseLog(&mCCLog, NS_LITERAL_STRING("Cycle"));
return NS_OK;
}
private:
~nsCycleCollectorLogSinkToFile()
{
if (mGCLog.mStream) {
MozillaUnRegisterDebugFILE(mGCLog.mStream);
fclose(mGCLog.mStream);
}
if (mCCLog.mStream) {
MozillaUnRegisterDebugFILE(mCCLog.mStream);
fclose(mCCLog.mStream);
}
}
struct FileInfo {
const char* const mPrefix;
nsCOMPtr<nsIFile> mFile;
FILE* mStream;
FileInfo(const char* aPrefix) : mPrefix(aPrefix), mStream(nullptr) { }
};
/**
* Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in
* $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing
* file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll
* try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so
* on.
*/
already_AddRefed<nsIFile> CreateTempFile(const char* aPrefix)
{
nsPrintfCString filename("%s.%d%s%s.log",
aPrefix,
mProcessIdentifier,
mFilenameIdentifier.IsEmpty() ? "" : ".",
NS_ConvertUTF16toUTF8(mFilenameIdentifier).get());
// Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from
// the fallback directories in OpenTempFile. We don't use an nsCOMPtr
// here because OpenTempFile uses an in/out param and getter_AddRefs
// wouldn't work.
nsIFile* logFile = nullptr;
if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) {
NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true,
&logFile);
}
// On Android or B2G, this function will open a file named
// aFilename under a memory-reporting-specific folder
// (/data/local/tmp/memory-reports). Otherwise, it will open a
// file named aFilename under "NS_OS_TEMP_DIR".
nsresult rv = nsDumpUtils::OpenTempFile(
filename,
&logFile,
NS_LITERAL_CSTRING("memory-reports"));
if (NS_FAILED(rv)) {
NS_IF_RELEASE(logFile);
return nullptr;
}
return dont_AddRef(logFile);
}
nsresult OpenLog(FileInfo* aLog)
{
// Initially create the log in a file starting with "incomplete-".
// We'll move the file and strip off the "incomplete-" once the dump
// completes. (We do this because we don't want scripts which poll
// the filesystem looking for GC/CC dumps to grab a file before we're
// finished writing to it.)
nsAutoCString incomplete;
incomplete += "incomplete-";
incomplete += aLog->mPrefix;
MOZ_ASSERT(!aLog->mFile);
aLog->mFile = CreateTempFile(incomplete.get());
if (NS_WARN_IF(!aLog->mFile))
return NS_ERROR_UNEXPECTED;
MOZ_ASSERT(!aLog->mStream);
aLog->mFile->OpenANSIFileDesc("w", &aLog->mStream);
if (NS_WARN_IF(!aLog->mStream))
return NS_ERROR_UNEXPECTED;
MozillaRegisterDebugFILE(aLog->mStream);
return NS_OK;
}
nsresult CloseLog(FileInfo* aLog, const nsAString& aCollectorKind)
{
MOZ_ASSERT(aLog->mStream);
MOZ_ASSERT(aLog->mFile);
MozillaUnRegisterDebugFILE(aLog->mStream);
fclose(aLog->mStream);
aLog->mStream = nullptr;
// Strip off "incomplete-".
nsCOMPtr<nsIFile> logFileFinalDestination =
CreateTempFile(aLog->mPrefix);
if (NS_WARN_IF(!logFileFinalDestination))
return NS_ERROR_UNEXPECTED;
nsAutoString logFileFinalDestinationName;
logFileFinalDestination->GetLeafName(logFileFinalDestinationName);
if (NS_WARN_IF(logFileFinalDestinationName.IsEmpty()))
return NS_ERROR_UNEXPECTED;
aLog->mFile->MoveTo(/* directory */ nullptr, logFileFinalDestinationName);
// Save the file path.
aLog->mFile = logFileFinalDestination;
// Log to the error console.
nsCOMPtr<nsIConsoleService> cs =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
if (cs) {
// Copy out the path.
nsAutoString logPath;
logFileFinalDestination->GetPath(logPath);
nsString msg = aCollectorKind
+ NS_LITERAL_STRING(" Collector log dumped to ") + logPath;
cs->LogStringMessage(msg.get());
}
return NS_OK;
}
int32_t mProcessIdentifier;
nsString mFilenameIdentifier;
FileInfo mGCLog;
FileInfo mCCLog;
};
NS_IMPL_ISUPPORTS(nsCycleCollectorLogSinkToFile, nsICycleCollectorLogSink)
class nsCycleCollectorLogger MOZ_FINAL : public nsICycleCollectorListener
{
public:
nsCycleCollectorLogger() :
mStream(nullptr), mWantAllTraces(false),
mDisableLog(false), mWantAfterProcessing(false)
nsCycleCollectorLogger()
: mLogSink(nsCycleCollector_createLogSink())
, mWantAllTraces(false)
, mDisableLog(false)
, mWantAfterProcessing(false)
, mCCLog(nullptr)
{
}
~nsCycleCollectorLogger()
{
ClearDescribers();
if (mStream) {
MozillaUnRegisterDebugFILE(mStream);
fclose(mStream);
}
}
NS_DECL_ISUPPORTS
void SetAllTraces()
@ -1525,113 +1744,50 @@ public:
return NS_OK;
}
NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier)
NS_IMETHOD GetLogSink(nsICycleCollectorLogSink** aLogSink)
{
aIdentifier = mFilenameIdentifier;
NS_ADDREF(*aLogSink = mLogSink);
return NS_OK;
}
NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier)
NS_IMETHOD SetLogSink(nsICycleCollectorLogSink* aLogSink)
{
mFilenameIdentifier = aIdentifier;
return NS_OK;
}
NS_IMETHOD GetGcLogPath(nsAString& aPath)
{
aPath = mGCLogPath;
return NS_OK;
}
NS_IMETHOD GetCcLogPath(nsAString& aPath)
{
aPath = mCCLogPath;
if (!aLogSink) {
return NS_ERROR_INVALID_ARG;
}
mLogSink = aLogSink;
return NS_OK;
}
NS_IMETHOD Begin()
{
nsresult rv;
mCurrentAddress.AssignLiteral("0x");
ClearDescribers();
if (mDisableLog) {
return NS_OK;
}
// Initially create the log in a file starting with
// "incomplete-gc-edges". We'll move the file and strip off the
// "incomplete-" once the dump completes. (We do this because we don't
// want scripts which poll the filesystem looking for gc/cc dumps to
// grab a file before we're finished writing to it.)
nsCOMPtr<nsIFile> gcLogFile = CreateTempFile("incomplete-gc-edges");
if (NS_WARN_IF(!gcLogFile)) {
return NS_ERROR_UNEXPECTED;
}
FILE* gcLog;
rv = mLogSink->Open(&gcLog, &mCCLog);
NS_ENSURE_SUCCESS(rv, rv);
// Dump the JS heap.
FILE* gcLogANSIFile = nullptr;
gcLogFile->OpenANSIFileDesc("w", &gcLogANSIFile);
if (NS_WARN_IF(!gcLogANSIFile)) {
return NS_ERROR_UNEXPECTED;
}
MozillaRegisterDebugFILE(gcLogANSIFile);
CollectorData* data = sCollectorData.get();
if (data && data->mRuntime) {
data->mRuntime->DumpJSHeap(gcLogANSIFile);
data->mRuntime->DumpJSHeap(gcLog);
}
MozillaUnRegisterDebugFILE(gcLogANSIFile);
fclose(gcLogANSIFile);
// Strip off "incomplete-".
nsCOMPtr<nsIFile> gcLogFileFinalDestination =
CreateTempFile("gc-edges");
if (NS_WARN_IF(!gcLogFileFinalDestination)) {
return NS_ERROR_UNEXPECTED;
}
nsAutoString gcLogFileFinalDestinationName;
gcLogFileFinalDestination->GetLeafName(gcLogFileFinalDestinationName);
if (NS_WARN_IF(gcLogFileFinalDestinationName.IsEmpty())) {
return NS_ERROR_UNEXPECTED;
}
gcLogFile->MoveTo(/* directory */ nullptr, gcLogFileFinalDestinationName);
// Log to the error console.
nsCOMPtr<nsIConsoleService> cs =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
if (cs) {
nsAutoString gcLogPath;
gcLogFileFinalDestination->GetPath(gcLogPath);
nsString msg = NS_LITERAL_STRING("Garbage Collector log dumped to ") +
gcLogPath;
cs->LogStringMessage(msg.get());
mGCLogPath = gcLogPath;
}
// Open a file for dumping the CC graph. We again prefix with
// "incomplete-".
mOutFile = CreateTempFile("incomplete-cc-edges");
if (NS_WARN_IF(!mOutFile)) {
return NS_ERROR_UNEXPECTED;
}
MOZ_ASSERT(!mStream);
mOutFile->OpenANSIFileDesc("w", &mStream);
if (NS_WARN_IF(!mStream)) {
return NS_ERROR_UNEXPECTED;
}
MozillaRegisterDebugFILE(mStream);
fprintf(mStream, "# WantAllTraces=%s\n", mWantAllTraces ? "true" : "false");
rv = mLogSink->CloseGCLog();
NS_ENSURE_SUCCESS(rv, rv);
fprintf(mCCLog, "# WantAllTraces=%s\n", mWantAllTraces ? "true" : "false");
return NS_OK;
}
NS_IMETHOD NoteRefCountedObject(uint64_t aAddress, uint32_t aRefCount,
const char* aObjectDescription)
{
if (!mDisableLog) {
fprintf(mStream, "%p [rc=%u] %s\n", (void*)aAddress, aRefCount,
fprintf(mCCLog, "%p [rc=%u] %s\n", (void*)aAddress, aRefCount,
aObjectDescription);
}
if (mWantAfterProcessing) {
@ -1651,7 +1807,7 @@ public:
uint64_t aCompartmentAddress)
{
if (!mDisableLog) {
fprintf(mStream, "%p [gc%s] %s\n", (void*)aAddress,
fprintf(mCCLog, "%p [gc%s] %s\n", (void*)aAddress,
aMarked ? ".marked" : "", aObjectDescription);
}
if (mWantAfterProcessing) {
@ -1660,7 +1816,7 @@ public:
mCurrentAddress.AssignLiteral("0x");
mCurrentAddress.AppendInt(aAddress, 16);
d->mType = aMarked ? CCGraphDescriber::eGCMarkedObject :
CCGraphDescriber::eGCedObject;
CCGraphDescriber::eGCedObject;
d->mAddress = mCurrentAddress;
d->mName.Append(aObjectDescription);
if (aCompartmentAddress) {
@ -1675,7 +1831,7 @@ public:
NS_IMETHOD NoteEdge(uint64_t aToAddress, const char* aEdgeName)
{
if (!mDisableLog) {
fprintf(mStream, "> %p %s\n", (void*)aToAddress, aEdgeName);
fprintf(mCCLog, "> %p %s\n", (void*)aToAddress, aEdgeName);
}
if (mWantAfterProcessing) {
CCGraphDescriber* d = new CCGraphDescriber();
@ -1692,7 +1848,7 @@ public:
uint64_t aKeyDelegate, uint64_t aValue)
{
if (!mDisableLog) {
fprintf(mStream, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n",
fprintf(mCCLog, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n",
(void*)aMap, (void*)aKey, (void*)aKeyDelegate, (void*)aValue);
}
// We don't support after-processing for weak map entries.
@ -1701,7 +1857,7 @@ public:
NS_IMETHOD NoteIncrementalRoot(uint64_t aAddress)
{
if (!mDisableLog) {
fprintf(mStream, "IncrementalRoot %p\n", (void*)aAddress);
fprintf(mCCLog, "IncrementalRoot %p\n", (void*)aAddress);
}
// We don't support after-processing for incremental roots.
return NS_OK;
@ -1709,14 +1865,14 @@ public:
NS_IMETHOD BeginResults()
{
if (!mDisableLog) {
fputs("==========\n", mStream);
fputs("==========\n", mCCLog);
}
return NS_OK;
}
NS_IMETHOD DescribeRoot(uint64_t aAddress, uint32_t aKnownEdges)
{
if (!mDisableLog) {
fprintf(mStream, "%p [known=%u]\n", (void*)aAddress, aKnownEdges);
fprintf(mCCLog, "%p [known=%u]\n", (void*)aAddress, aKnownEdges);
}
if (mWantAfterProcessing) {
CCGraphDescriber* d = new CCGraphDescriber();
@ -1730,7 +1886,7 @@ public:
NS_IMETHOD DescribeGarbage(uint64_t aAddress)
{
if (!mDisableLog) {
fprintf(mStream, "%p [garbage]\n", (void*)aAddress);
fprintf(mCCLog, "%p [garbage]\n", (void*)aAddress);
}
if (mWantAfterProcessing) {
CCGraphDescriber* d = new CCGraphDescriber();
@ -1743,43 +1899,9 @@ public:
NS_IMETHOD End()
{
if (!mDisableLog) {
MOZ_ASSERT(mStream);
MOZ_ASSERT(mOutFile);
MozillaUnRegisterDebugFILE(mStream);
fclose(mStream);
mStream = nullptr;
// Strip off "incomplete-" from the log file's name.
nsCOMPtr<nsIFile> logFileFinalDestination =
CreateTempFile("cc-edges");
if (NS_WARN_IF(!logFileFinalDestination)) {
return NS_ERROR_UNEXPECTED;
}
nsAutoString logFileFinalDestinationName;
logFileFinalDestination->GetLeafName(logFileFinalDestinationName);
if (NS_WARN_IF(logFileFinalDestinationName.IsEmpty())) {
return NS_ERROR_UNEXPECTED;
}
mOutFile->MoveTo(/* directory = */ nullptr,
logFileFinalDestinationName);
mOutFile = nullptr;
// Log to the error console.
nsCOMPtr<nsIConsoleService> cs =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
if (cs) {
nsAutoString ccLogPath;
logFileFinalDestination->GetPath(ccLogPath);
nsString msg = NS_LITERAL_STRING("Cycle Collector log dumped to ") +
ccLogPath;
cs->LogStringMessage(msg.get());
mCCLogPath = ccLogPath;
}
mCCLog = nullptr;
nsresult rv = mLogSink->CloseCCLog();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
@ -1829,47 +1951,6 @@ public:
return NS_OK;
}
private:
/**
* Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in
* $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing
* file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll
* try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so
* on.
*/
already_AddRefed<nsIFile>
CreateTempFile(const char* aPrefix)
{
nsPrintfCString filename("%s.%d%s%s.log",
aPrefix,
base::GetCurrentProcId(),
mFilenameIdentifier.IsEmpty() ? "" : ".",
NS_ConvertUTF16toUTF8(mFilenameIdentifier).get());
// Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from
// the fallback directories in OpenTempFile. We don't use an nsCOMPtr
// here because OpenTempFile uses an in/out param and getter_AddRefs
// wouldn't work.
nsIFile* logFile = nullptr;
if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) {
NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true,
&logFile);
}
// 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".
nsresult rv = nsDumpUtils::OpenTempFile(
filename,
&logFile,
NS_LITERAL_CSTRING("memory-reports"));
if (NS_FAILED(rv)) {
NS_IF_RELEASE(logFile);
return nullptr;
}
return dont_AddRef(logFile);
}
void ClearDescribers()
{
CCGraphDescriber* d;
@ -1878,16 +1959,13 @@ private:
}
}
FILE* mStream;
nsCOMPtr<nsIFile> mOutFile;
nsCOMPtr<nsICycleCollectorLogSink> mLogSink;
bool mWantAllTraces;
bool mDisableLog;
bool mWantAfterProcessing;
nsString mFilenameIdentifier;
nsString mGCLogPath;
nsString mCCLogPath;
nsCString mCurrentAddress;
mozilla::LinkedList<CCGraphDescriber> mDescribers;
FILE* mCCLog;
};
NS_IMPL_ISUPPORTS(nsCycleCollectorLogger, nsICycleCollectorListener)
@ -3999,6 +4077,13 @@ nsCycleCollector_doDeferredDeletion()
return data->mCollector->FreeSnowWhite(false);
}
already_AddRefed<nsICycleCollectorLogSink>
nsCycleCollector_createLogSink()
{
nsCOMPtr<nsICycleCollectorLogSink> sink = new nsCycleCollectorLogSinkToFile();
return sink.forget();
}
void
nsCycleCollector_collect(nsICycleCollectorListener* aManualListener)
{

View File

@ -7,7 +7,9 @@
#define nsCycleCollector_h__
class nsICycleCollectorListener;
class nsICycleCollectorLogSink;
class nsISupports;
template<class T> struct already_AddRefed;
#include "nsError.h"
#include "nsID.h"
@ -43,6 +45,8 @@ void nsCycleCollector_finishAnyCurrentCollection();
void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation = false);
bool nsCycleCollector_doDeferredDeletion();
already_AddRefed<nsICycleCollectorLogSink> nsCycleCollector_createLogSink();
void nsCycleCollector_collect(nsICycleCollectorListener* aManualListener);
// If aSliceTime is negative, the CC will run to completion. Otherwise,

View File

@ -4,6 +4,13 @@
#include "nsISupports.idl"
%{C++
#include <stdio.h>
%}
[ptr] native FILE(FILE);
interface nsIFile;
/**
* Interfaces for observing the cycle collector's work, both from C++ and
* from JavaScript.
@ -49,6 +56,34 @@ interface nsICycleCollectorHandler : nsISupports
void describeGarbage(in ACString aAddress);
};
/**
* This interface allows replacing the log-writing backend for an
* nsICycleCollectorListener. As this interface is also called while
* the cycle collector is running, it cannot be implemented in JS.
*/
[scriptable, builtinclass, uuid(3ad9875f-d0e4-4ac2-87e3-f127f6c02ce1)]
interface nsICycleCollectorLogSink : nsISupports
{
[noscript] void open(out FILE aGCLog, out FILE aCCLog);
void closeGCLog();
void closeCCLog();
// This string will appear somewhere in the log's filename.
attribute AString filenameIdentifier;
// This is the process ID; it can be changed if logging is on behalf
// of another process.
attribute int32_t processIdentifier;
// The GC log file, if logging to files.
readonly attribute nsIFile gcLog;
// The CC log file, if logging to files.
readonly attribute nsIFile ccLog;
};
/**
* Given an instance of this interface, the cycle collector calls the instance's
* methods to report the objects it visits, the edges between them, and its
@ -87,7 +122,7 @@ interface nsICycleCollectorHandler : nsISupports
*
*
* This interface cannot be implemented by JavaScript code, as it is called
* while the cycle collector works. To analyze cycle collection data in JS:
* while the cycle collector is running. To analyze cycle collection data in JS:
*
* - Create an instance of @mozilla.org/cycle-collector-logger;1, which
* implements this interface.
@ -109,7 +144,7 @@ interface nsICycleCollectorHandler : nsISupports
* on objects however it pleases: the cycle collector has finished its
* work, and the JS code is simply consuming recorded data.
*/
[scriptable, builtinclass, uuid(c46e6947-9076-4a0e-bb27-d4aa3706c54d)]
[scriptable, builtinclass, uuid(c7d55656-e0d8-4986-88bb-cb28cb55b993)]
interface nsICycleCollectorListener : nsISupports
{
// Return a listener that directs the cycle collector to traverse
@ -134,19 +169,13 @@ interface nsICycleCollectorListener : nsISupports
// Initially false.
attribute boolean disableLog;
// This string will appear somewhere in the log's filename.
attribute AString filenameIdentifier;
// If |disableLog| is false, this object will be sent the log text.
attribute nsICycleCollectorLogSink logSink;
// If true, record all method calls in memory, to be retrieved later
// using |processNext|. Initially false.
attribute boolean wantAfterProcessing;
// This string will indicate the full path of the GC log if enabled.
readonly attribute AString gcLogPath;
// This string will indicate the full path of the CC log if enabled.
readonly attribute AString ccLogPath;
void begin();
void noteRefCountedObject (in unsigned long long aAddress,
in unsigned long aRefCount,

View File

@ -5,13 +5,46 @@
#include "nsISupports.idl"
interface nsIFile;
interface nsICycleCollectorLogSink;
[scriptable, function, uuid(2dea18fc-fbfa-4bf7-ad45-0efaf5495f5e)]
interface nsIFinishDumpingCallback : nsISupports
{
void callback(in nsISupports data);
};
[scriptable, builtinclass, uuid(815bf31b-f5bd-425d-85c3-4657a7a91dad)]
/**
* Callback interface for |dumpGCAndCCLogsToFile|, below. Note that
* these method calls can occur before |dumpGCAndCCLogsToFile|
* returns.
*/
[scriptable, uuid(dc1b2b24-65bd-441b-b6bd-cb5825a7ed14)]
interface nsIDumpGCAndCCLogsCallback : nsISupports
{
/**
* Called whenever a process has successfully finished dumping its GC/CC logs.
* Incomplete dumps (e.g., if the child crashes or is killed due to memory
* exhaustion) are not reported.
*
* @param aGCLog The file that the GC log was written to.
*
* @param aCCLog The file that the CC log was written to.
*
* @param aIsParent indicates whether this log file pair is from the
* parent process.
*/
void onDump(in nsIFile aGCLog,
in nsIFile aCCLog,
in bool aIsParent);
/**
* Called when GC/CC logging has finished, after all calls to |onDump|.
*/
void onFinish();
};
[scriptable, builtinclass, uuid(a156fda4-aea0-4da2-9daa-9858430fade9)]
interface nsIMemoryInfoDumper : nsISupports
{
/**
@ -146,13 +179,17 @@ interface nsIMemoryInfoDumper : nsISupports
* DumpGCAndCCLogsToFile in our child processes. If so, the child processes
* will dump their children, and so on.
*
* @param aGCLogPath The full path of the file that the GC log was written to.
*
* @param aCCLogPath The full path of the file that the CC log was written to.
*/
void dumpGCAndCCLogsToFile(in AString aIdentifier,
in bool aDumpAllTraces,
in bool aDumpChildProcesses,
out AString aGCLogPath,
out AString aCCLogPath);
in nsIDumpGCAndCCLogsCallback aCallback);
/**
* Like |dumpGCAndCCLogsToFile|, but sends the logs to the given log
* sink object instead of accessing the filesystem directly, and
* dumps the current process only.
*/
void dumpGCAndCCLogsToSink(in bool aDumpAllTraces,
in nsICycleCollectorLogSink aSink);
};

View File

@ -11,6 +11,7 @@
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentChild.h"
#include "nsIConsoleService.h"
#include "nsCycleCollector.h"
#include "nsICycleCollectorListener.h"
#include "nsIMemoryReporter.h"
#include "nsDirectoryServiceDefs.h"
@ -74,9 +75,13 @@ private:
const bool mMinimizeMemoryUsage;
};
class GCAndCCLogDumpRunnable : public nsRunnable
class GCAndCCLogDumpRunnable MOZ_FINAL
: public nsRunnable
, public nsIDumpGCAndCCLogsCallback
{
public:
NS_DECL_ISUPPORTS_INHERITED
GCAndCCLogDumpRunnable(const nsAString& aIdentifier,
bool aDumpAllTraces,
bool aDumpChildProcesses)
@ -86,14 +91,23 @@ public:
{
}
NS_IMETHOD Run()
NS_IMETHOD Run() MOZ_OVERRIDE
{
nsCOMPtr<nsIMemoryInfoDumper> dumper =
do_GetService("@mozilla.org/memory-info-dumper;1");
nsString ccLogPath, gcLogPath;
dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces,
mDumpChildProcesses, gcLogPath, ccLogPath);
mDumpChildProcesses, this);
return NS_OK;
}
NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) MOZ_OVERRIDE
{
return NS_OK;
}
NS_IMETHOD OnFinish() MOZ_OVERRIDE
{
return NS_OK;
}
@ -103,6 +117,8 @@ private:
const bool mDumpChildProcesses;
};
NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, nsRunnable, nsIDumpGCAndCCLogsCallback)
} // anonymous namespace
#if defined(MOZ_SUPPORTS_RT_SIGNALS) // {
@ -286,28 +302,69 @@ EnsureNonEmptyIdentifier(nsAString& aIdentifier)
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 MOZ_FINAL : public nsIDumpGCAndCCLogsCallback
{
public:
NS_DECL_ISUPPORTS
nsDumpGCAndCCLogsCallbackHolder(nsIDumpGCAndCCLogsCallback* aCallback)
: mCallback(aCallback)
{
}
NS_IMETHODIMP OnFinish()
{
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent)
{
return mCallback->OnDump(aGCLog, aCCLog, aIsParent);
}
private:
~nsDumpGCAndCCLogsCallbackHolder()
{
unused << mCallback->OnFinish();
}
nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
};
NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback)
NS_IMETHODIMP
nsMemoryInfoDumper::DumpGCAndCCLogsToFile(const nsAString& aIdentifier,
bool aDumpAllTraces,
bool aDumpChildProcesses,
nsAString& aGCLogPath,
nsAString& aCCLogPath)
nsIDumpGCAndCCLogsCallback* aCallback)
{
nsString identifier(aIdentifier);
EnsureNonEmptyIdentifier(identifier);
nsCOMPtr<nsIDumpGCAndCCLogsCallback> callbackHolder =
new nsDumpGCAndCCLogsCallbackHolder(aCallback);
if (aDumpChildProcesses) {
nsTArray<ContentParent*> children;
ContentParent::GetAll(children);
for (uint32_t i = 0; i < children.Length(); i++) {
unused << children[i]->SendDumpGCAndCCLogsToFile(
identifier, aDumpAllTraces, aDumpChildProcesses);
ContentParent* cp = children[i];
nsCOMPtr<nsICycleCollectorLogSink> logSink =
nsCycleCollector_createLogSink();
logSink->SetFilenameIdentifier(identifier);
logSink->SetProcessIdentifier(cp->Pid());
unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink,
callbackHolder);
}
}
nsCOMPtr<nsICycleCollectorListener> logger =
do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
logger->SetFilenameIdentifier(identifier);
if (aDumpAllTraces) {
nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
@ -315,10 +372,37 @@ nsMemoryInfoDumper::DumpGCAndCCLogsToFile(const nsAString& aIdentifier,
logger = allTracesLogger;
}
nsCOMPtr<nsICycleCollectorLogSink> logSink;
logger->GetLogSink(getter_AddRefs(logSink));
logSink->SetFilenameIdentifier(identifier);
nsJSContext::CycleCollectNow(logger);
logger->GetGcLogPath(aGCLogPath);
logger->GetCcLogPath(aCCLogPath);
nsCOMPtr<nsIFile> gcLog, ccLog;
logSink->GetGcLog(getter_AddRefs(gcLog));
logSink->GetCcLog(getter_AddRefs(ccLog));
callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true);
return NS_OK;
}
NS_IMETHODIMP
nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
nsICycleCollectorLogSink* aSink)
{
nsCOMPtr<nsICycleCollectorListener> logger =
do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
if (aDumpAllTraces) {
nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
logger->AllTraces(getter_AddRefs(allTracesLogger));
logger = allTracesLogger;
}
logger->SetLogSink(aSink);
nsJSContext::CycleCollectNow(logger);
return NS_OK;
}