mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-31 19:10:36 +00:00
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:
parent
1170b7a391
commit
e4ced412bb
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
22
dom/ipc/PCycleCollectWithLogs.ipdl
Normal file
22
dom/ipc/PCycleCollectWithLogs.ipdl
Normal 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__();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -78,6 +78,7 @@ IPDL_SOURCES += [
|
||||
'PContentPermission.ipdlh',
|
||||
'PContentPermissionRequest.ipdl',
|
||||
'PCrashReporter.ipdl',
|
||||
'PCycleCollectWithLogs.ipdl',
|
||||
'PDocumentRenderer.ipdl',
|
||||
'PFileDescriptorSet.ipdl',
|
||||
'PFilePicker.ipdl',
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,3 +15,4 @@ support-files =
|
||||
[test_memoryReporters.xul]
|
||||
[test_memoryReporters2.xul]
|
||||
[test_sqliteMultiReporter.xul]
|
||||
[test_dumpGCAndCCLogsToFile.xul]
|
||||
|
@ -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>
|
@ -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)
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user