Bug 929797 - Implement proper memory reporting for child processes. r=khuey.

--HG--
extra : rebase_source : f0788757afc2097830295170120e1e2094993cc7
This commit is contained in:
Nicholas Nethercote 2013-10-22 22:26:24 -07:00
parent fbfefcc32a
commit a277702631
16 changed files with 802 additions and 288 deletions

View File

@ -433,7 +433,7 @@ ContentChild::InitXPCOM()
}
PMemoryReportRequestChild*
ContentChild::AllocPMemoryReportRequestChild()
ContentChild::AllocPMemoryReportRequestChild(const uint32_t& generation)
{
return new MemoryReportRequestChild();
}
@ -480,7 +480,9 @@ NS_IMPL_ISUPPORTS1(
)
bool
ContentChild::RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child)
ContentChild::RecvPMemoryReportRequestConstructor(
PMemoryReportRequestChild* child,
const uint32_t& generation)
{
nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
@ -504,7 +506,7 @@ ContentChild::RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* chi
r->CollectReports(cb, wrappedReports);
}
child->Send__delete__(child, reports);
child->Send__delete__(child, generation, reports);
return true;
}

View File

@ -111,13 +111,14 @@ public:
virtual bool DeallocPIndexedDBChild(PIndexedDBChild* aActor);
virtual PMemoryReportRequestChild*
AllocPMemoryReportRequestChild();
AllocPMemoryReportRequestChild(const uint32_t& generation);
virtual bool
DeallocPMemoryReportRequestChild(PMemoryReportRequestChild* actor);
virtual bool
RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child);
RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child,
const uint32_t& generation);
virtual bool
RecvAudioChannelNotify();

View File

@ -78,6 +78,7 @@
#include "nsISupportsPrimitives.h"
#include "nsIURIFixup.h"
#include "nsIWindowWatcher.h"
#include "nsMemoryReporterManager.h"
#include "nsServiceManagerUtils.h"
#include "nsStyleSheetService.h"
#include "nsThreadUtils.h"
@ -160,61 +161,13 @@ namespace dom {
#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
// This represents all the memory reports provided by a child process.
class ChildReporter MOZ_FINAL : public nsIMemoryReporter
{
public:
ChildReporter(const InfallibleTArray<MemoryReport>& childReports)
{
for (uint32_t i = 0; i < childReports.Length(); i++) {
MemoryReport r(childReports[i].process(),
childReports[i].path(),
childReports[i].kind(),
childReports[i].units(),
childReports[i].amount(),
childReports[i].desc());
// Child reports have a non-empty process.
MOZ_ASSERT(!r.process().IsEmpty());
mChildReports.AppendElement(r);
}
}
NS_DECL_ISUPPORTS
NS_IMETHOD GetName(nsACString& name)
{
name.AssignLiteral("content-child");
return NS_OK;
}
NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCb,
nsISupports* aClosure)
{
for (uint32_t i = 0; i < mChildReports.Length(); i++) {
nsresult rv;
MemoryReport r = mChildReports[i];
rv = aCb->Callback(r.process(), r.path(), r.kind(), r.units(),
r.amount(), r.desc(), aClosure);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
private:
InfallibleTArray<MemoryReport> mChildReports;
};
NS_IMPL_ISUPPORTS1(ChildReporter, nsIMemoryReporter)
class MemoryReportRequestParent : public PMemoryReportRequestParent
{
public:
MemoryReportRequestParent();
virtual ~MemoryReportRequestParent();
virtual bool Recv__delete__(const InfallibleTArray<MemoryReport>& report);
virtual bool Recv__delete__(const uint32_t& generation, const InfallibleTArray<MemoryReport>& report);
private:
ContentParent* Owner()
{
@ -228,9 +181,13 @@ MemoryReportRequestParent::MemoryReportRequestParent()
}
bool
MemoryReportRequestParent::Recv__delete__(const InfallibleTArray<MemoryReport>& childReports)
MemoryReportRequestParent::Recv__delete__(const uint32_t& generation, const InfallibleTArray<MemoryReport>& childReports)
{
Owner()->SetChildMemoryReports(childReports);
nsRefPtr<nsMemoryReporterManager> mgr =
nsMemoryReporterManager::GetOrCreate();
if (mgr) {
mgr->HandleChildReports(generation, childReports);
}
return true;
}
@ -1043,7 +1000,6 @@ ContentParent::ShutDownProcess(bool aCloseWithError)
// shut down the cycle collector. But by then it's too late to release any
// CC'ed objects, so we need to null them out here, while we still can. See
// bug 899761.
mChildReporter = nullptr;
if (mMessageManager) {
mMessageManager->Disconnect();
mMessageManager = nullptr;
@ -1218,8 +1174,12 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
ppm->Disconnect();
}
// unregister the child memory reporter
UnregisterChildMemoryReporter();
// Tell the memory reporter manager that this ContentParent is going away.
nsRefPtr<nsMemoryReporterManager> mgr =
nsMemoryReporterManager::GetOrCreate();
if (mgr) {
mgr->DecrementNumChildProcesses();
}
// remove the global remote preferences observers
Preferences::RemoveObserver(this, "");
@ -1426,6 +1386,13 @@ ContentParent::ContentParent(mozIApplication* aApp,
IToplevelProtocol::SetTransport(mSubprocess->GetChannel());
// Tell the memory reporter manager that this ContentParent exists.
nsRefPtr<nsMemoryReporterManager> mgr =
nsMemoryReporterManager::GetOrCreate();
if (mgr) {
mgr->IncrementNumChildProcesses();
}
std::vector<std::string> extraArgs;
if (aIsNuwaProcess) {
extraArgs.push_back("-nuwa");
@ -2037,7 +2004,7 @@ ContentParent::Observe(nsISupports* aSubject,
return NS_ERROR_NOT_AVAILABLE;
}
else if (!strcmp(aTopic, "child-memory-reporter-request")) {
unused << SendPMemoryReportRequestConstructor();
unused << SendPMemoryReportRequestConstructor((uint32_t)(uintptr_t)aData);
}
else if (!strcmp(aTopic, "child-gc-request")){
unused << SendGarbageCollect();
@ -2481,7 +2448,7 @@ ContentParent::RecvPIndexedDBConstructor(PIndexedDBParent* aActor)
}
PMemoryReportRequestParent*
ContentParent::AllocPMemoryReportRequestParent()
ContentParent::AllocPMemoryReportRequestParent(const uint32_t& generation)
{
MemoryReportRequestParent* parent = new MemoryReportRequestParent();
return parent;
@ -2494,32 +2461,6 @@ ContentParent::DeallocPMemoryReportRequestParent(PMemoryReportRequestParent* act
return true;
}
void
ContentParent::SetChildMemoryReports(const InfallibleTArray<MemoryReport>& childReports)
{
nsCOMPtr<nsIMemoryReporterManager> mgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
if (mChildReporter)
mgr->UnregisterReporter(mChildReporter);
mChildReporter = new ChildReporter(childReports);
mgr->RegisterReporter(mChildReporter);
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
if (obs)
obs->NotifyObservers(nullptr, "child-memory-reporter-update", nullptr);
}
void
ContentParent::UnregisterChildMemoryReporter()
{
nsCOMPtr<nsIMemoryReporterManager> mgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
mgr->UnregisterReporter(mChildReporter);
}
PTestShellParent*
ContentParent::AllocPTestShellParent()
{

View File

@ -140,10 +140,6 @@ public:
bool IsAlive();
bool IsForApp();
void SetChildMemoryReports(const InfallibleTArray<MemoryReport>&
childReports);
void UnregisterChildMemoryReporter();
GeckoChildProcessHost* Process() {
return mSubprocess;
}
@ -340,7 +336,7 @@ private:
virtual bool DeallocPIndexedDBParent(PIndexedDBParent* aActor);
virtual PMemoryReportRequestParent* AllocPMemoryReportRequestParent();
virtual PMemoryReportRequestParent* AllocPMemoryReportRequestParent(const uint32_t& generation);
virtual bool DeallocPMemoryReportRequestParent(PMemoryReportRequestParent* actor);
virtual PTestShellParent* AllocPTestShellParent();
@ -500,14 +496,6 @@ private:
uint64_t mChildID;
int32_t mGeolocationWatchID;
// This is a reporter holding the reports from the child's last
// "child-memory-reporter-update" notification. To update this, one can
// broadcast the topic "child-memory-reporter-request" using the
// nsIObserverService.
//
// Note that this assumes there is at most one child process at a time!
nsCOMPtr<nsIMemoryReporter> mChildReporter;
nsString mAppManifestURL;
/**

View File

@ -231,7 +231,7 @@ child:
*/
async SetProcessPrivileges(ChildPrivileges privs);
PMemoryReportRequest();
PMemoryReportRequest(uint32_t generation);
/**
* Notify the AudioChannelService in the child processes.

View File

@ -21,7 +21,7 @@ protocol PMemoryReportRequest {
manager PContent;
parent:
__delete__(MemoryReport[] report);
__delete__(uint32_t generation, MemoryReport[] report);
};
}

View File

@ -48,11 +48,6 @@ XPCOMUtils.defineLazyGetter(this, "nsGzipConverter",
let gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
.getService(Ci.nsIMemoryReporterManager);
// We need to know about "child-memory-reporter-update" events from child
// processes.
Services.obs.addObserver(updateAboutMemoryFromReporters,
"child-memory-reporter-update", false);
let gUnnamedProcessStr = "Main Process";
let gIsDiff = false;
@ -120,8 +115,6 @@ function debug(x)
function onUnload()
{
Services.obs.removeObserver(updateAboutMemoryFromReporters,
"child-memory-reporter-update");
}
//---------------------------------------------------------------------------
@ -388,11 +381,6 @@ function doMMU()
function doMeasure()
{
// Notify any children that they should measure memory consumption, then
// update the page. If any reports come back from children,
// updateAboutMemoryFromReporters() will be called again and the page will
// regenerate.
Services.obs.notifyObservers(null, "child-memory-reporter-request", null);
updateAboutMemoryFromReporters();
}
@ -402,10 +390,7 @@ function doMeasure()
*/
function updateAboutMemoryFromReporters()
{
// First, clear the contents of main. Necessary because
// updateAboutMemoryFromReporters() might be called more than once due to the
// "child-memory-reporter-update" observer.
updateMainAndFooter("", SHOW_FOOTER);
updateMainAndFooter("Measuring...", HIDE_FOOTER);
try {
let processLiveMemoryReports =
@ -416,12 +401,13 @@ function updateAboutMemoryFromReporters()
aDescription, /* presence = */ undefined);
}
let e = gMgr.enumerateReporters();
while (e.hasMoreElements()) {
let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
mr.collectReports(handleReport, null);
let displayReportsAndFooter = function() {
updateMainAndFooter("", SHOW_FOOTER);
aDisplayReports();
}
aDisplayReports();
gMgr.getReports(handleReport, null,
displayReportsAndFooter, null);
}
// Process the reports from the live memory reporters.
@ -1841,9 +1827,11 @@ function saveReportsToFile()
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
.getService(Ci.nsIMemoryInfoDumper);
dumper.dumpMemoryReportsToNamedFile(fp.file.path);
let finishDumping = () => {
updateMainAndFooter("Saved reports to " + fp.file.path, HIDE_FOOTER);
}
updateMainAndFooter("Saved reports to " + fp.file.path, HIDE_FOOTER);
dumper.dumpMemoryReportsToNamedFile(fp.file.path, finishDumping, null);
}
};
fp.open(fpCallback);

View File

@ -10,6 +10,7 @@ support-files =
[test_aboutmemory2.xul]
[test_aboutmemory3.xul]
[test_aboutmemory4.xul]
[test_aboutmemory5.xul]
[test_memoryReporters.xul]
[test_memoryReporters2.xul]
[test_sqliteMultiReporter.xul]

View File

@ -108,21 +108,60 @@
let e = document.createEvent('Event');
e.initEvent('change', true, true);
if (!aFilename2) {
if (aDumpFirst) {
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
function check() {
// Initialize the clipboard contents.
SpecialPowers.clipboardCopyString("initial clipboard value");
dumper.dumpMemoryReportsToNamedFile(filePath,
/* minimizeMemoryUsage = */ false,
/* dumpChildProcesses = */ false);
let numFailures = 0, maxFailures = 30;
// Because the file load is async, we don't know when it will finish and
// the output will show up. So we poll.
function copyPasteAndCheck() {
// Copy and paste frame contents, and filter out non-deterministic
// differences.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
actual = actual.replace(/\(pid \d+\)/g, "(pid NNN)");
if (actual === aExpected) {
SimpleTest.ok(true, "Clipboard has the expected contents");
aNext();
} else {
numFailures++;
if (numFailures === maxFailures) {
ok(false, "pasted text doesn't match");
dump("******EXPECTED******\n");
dump(aExpected);
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
finish();
} else {
setTimeout(copyPasteAndCheck, 100);
}
}
}
copyPasteAndCheck();
}
if (!aFilename2) {
function loadAndCheck() {
let fileInput1 =
frame.contentWindow.document.getElementById("fileInput1");
fileInput1.value = filePath; // this works because it's a chrome test
fileInput1.dispatchEvent(e);
check();
}
let fileInput1 =
frame.contentWindow.document.getElementById("fileInput1");
fileInput1.value = filePath; // this works because it's a chrome test
fileInput1.dispatchEvent(e);
if (aDumpFirst) {
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
dumper.dumpMemoryReportsToNamedFile(filePath, loadAndCheck, null);
} else {
loadAndCheck();
}
} else {
let fileInput2 =
@ -144,42 +183,9 @@
let e2 = document.createEvent('Event');
e2.initEvent('change', true, true);
fileInput2.dispatchEvent(e);
check();
}
// Initialize the clipboard contents.
SpecialPowers.clipboardCopyString("initial clipboard value");
let numFailures = 0, maxFailures = 30;
// Because the file load is async, we don't know when it will finish and
// the output will show up. So we poll.
function copyPasteAndCheck() {
// Copy and paste frame contents, and filter out non-deterministic
// differences.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
actual = actual.replace(/\(pid \d+\)/g, "(pid NNN)");
if (actual === aExpected) {
SimpleTest.ok(true, "Clipboard has the expected contents");
aNext();
} else {
numFailures++;
if (numFailures === maxFailures) {
ok(false, "pasted text doesn't match");
dump("******EXPECTED******\n");
dump(aExpected);
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
finish();
} else {
setTimeout(copyPasteAndCheck, 100);
}
}
}
copyPasteAndCheck();
}
// Returns a function that chains together multiple test() calls.

View File

@ -0,0 +1,142 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the saving and loading of memory reports to/from file in
about:memory in the presence of child processes. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<iframe id="amFrame" height="400" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
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", 3]]}, 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.
SimpleTest.waitForFocus(onFocus);
}
});
mm.loadFrameScript("data:," + encodeURI("sendAsyncMessage('test:ready');"), true);
});
}
});
// Load the given file into the frame, then copy+paste the entire frame and
// check that the cut text matches what we expect.
function onFocus() {
let frame = document.getElementById("amFrame");
frame.focus();
function getFilePath(aFilename) {
let file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsIFile);
file.append("chrome");
file.append("toolkit");
file.append("components");
file.append("aboutmemory");
file.append("tests");
file.append(aFilename);
return file.path;
}
let filePath = getFilePath("memory-reports-dumped.json.gz");
let e = document.createEvent('Event');
e.initEvent('change', true, true);
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
dumper.dumpMemoryReportsToNamedFile(filePath, loadAndCheck, null);
function loadAndCheck() {
// Load the file.
let fileInput1 =
frame.contentWindow.document.getElementById("fileInput1");
fileInput1.value = filePath; // this works because it's a chrome test
fileInput1.dispatchEvent(e);
// Initialize the clipboard contents.
SpecialPowers.clipboardCopyString("initial clipboard value");
let numFailures = 0, maxFailures = 30;
copyPasteAndCheck();
// Because the file load is async, we don't know when it will finish and
// the output will show up. So we poll.
function copyPasteAndCheck() {
// Copy and paste frame contents, and filter out non-deterministic
// differences.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
// If we have more than 1000 chars, we've probably successfully
// copy+pasted.
if (actual.length > 1000) {
let vsizes = actual.match(/vsize/g);
let endOfBrowsers = actual.match(/End of Browser/g);
if (vsizes.length == 4 && endOfBrowsers.length == 3) {
ok(true, "three child processes present in loaded data");
} else {
ok(false, "pasted text lacks four 'vsize' and three 'End of Browser' strings");
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
}
// Close the remote processes.
for (let i = 0; i < numRemotes; i++) {
remotes[i].close();
}
SimpleTest.finish();
} else {
numFailures++;
if (numFailures === maxFailures) {
ok(false, "not enough chars in pasted output");
SimpleTest.finish();
} else {
setTimeout(copyPasteAndCheck, 100);
}
}
}
}
}
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View File

@ -19,15 +19,11 @@
SimpleTest.waitForExplicitFinish();
// We want to know when a remote process sends us an update.
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.addObserver(childMemoryReporterUpdate, "child-memory-reporter-update", false);
let numRemotes = 3;
let numReady = 0;
// Create some remote processes, and set up message-passing so that go() is
// called once per process, once it is fully initialized.
// 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", 3]]}, function() {
for (let i = 0; i < numRemotes; i++) {
@ -39,47 +35,40 @@
let mm = remoteBrowser.messageManager;
mm.addMessageListener("test:ready", function readyHandler() {
mm.removeMessageListener("test:ready", readyHandler);
remoteReady();
numReady++;
if (numReady == numRemotes) {
// All the remote processes are ready. Do memory reporting.
doReports();
}
});
mm.loadFrameScript("data:," + encodeURI("sendAsyncMessage('test:ready');"), true);
});
}
});
let numReady = 0;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
function remoteReady()
function doReports()
{
numReady++;
if (numReady == numRemotes) {
// All the remote processes are ready. Ask them for memory reports.
os.notifyObservers(null, "child-memory-reporter-request", null);
}
}
let residents = {};
let numUpdates = 0;
function childMemoryReporterUpdate()
{
numUpdates++;
if (numUpdates == numRemotes) {
// All the remote processes have reported back. Check reports.
let residents = {};
// Get all the reports.
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
let e = mgr.enumerateReporters();
while (e.hasMoreElements()) {
let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
r.collectReports(function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) {
if (aPath === "resident") {
ok(100 * 1000 <= aAmount && aAmount <= 10 * 1000 * 1000 * 1000,
"resident is reasonable");
residents[aProcess] = aAmount;
}
}, null);
let handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) {
if (aPath === "resident") {
ok(100 * 1000 <= aAmount && aAmount <= 10 * 1000 * 1000 * 1000,
"resident is reasonable");
residents[aProcess] = aAmount;
}
}
let processReports = function() {
// First, test a failure case: calling getReports() before the previous
// getReports() has finished should silently abort. (And the arguments
// won't be used.)
mgr.getReports(
() => ok(false, "handleReport called for nested getReports() call"),
null, null, null
);
// Close the remote processes.
for (let i = 0; i < numRemotes; i++) {
@ -107,6 +96,9 @@
SimpleTest.finish();
}
mgr.getReports(handleReport, null, processReports, null);
}
]]></script>
</window>

View File

@ -5,13 +5,18 @@
#include "nsISupports.idl"
[scriptable, builtinclass, uuid(3FFA5113-2A10-43BB-923B-6A2FAF67BE97)]
[scriptable, function, uuid(2dea18fc-fbfa-4bf7-ad45-0efaf5495f5e)]
interface nsIFinishDumpingCallback : nsISupports
{
void callback(in nsISupports data);
};
[scriptable, builtinclass, uuid(fd5de30a-e3d6-4965-8244-86709e1ed23b)]
interface nsIMemoryInfoDumper : nsISupports
{
/**
* This dumps gzipped memory reports for this process. If a file of the
* given name exists, it will be overwritten. Nothing is done for any child
* processes (and their children, recursively).
* This dumps gzipped memory reports for this process and its child
* processes. If a file of the given name exists, it will be overwritten.
*
* @param aFilename The output file.
*
@ -83,17 +88,19 @@ interface nsIMemoryInfoDumper : nsISupports
* }
* }
*/
void dumpMemoryReportsToNamedFile(in AString aFilename);
void dumpMemoryReportsToNamedFile(in AString aFilename,
in nsIFinishDumpingCallback aFinishDumping,
in nsISupports aFinishDumpingData);
/**
* Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory
* reports for this process and possibly our child processes (and their
* reports for this process and possibly its child processes (and their
* children, recursively) to a file in the tmp directory called
* memory-reports-<identifier>-<pid>.json.gz (or something similar, such as
* memory-reports-<identifier>-<pid>-1.json.gz; no existing file will be
* overwritten).
*
* If DMD is enabled, this method also dump gzipped DMD output to a file in
* If DMD is enabled, this method also dumps gzipped DMD output to a file in
* the tmp directory called dmd-<identifier>-<pid>.txt.gz (or something
* similar; again, no existing file will be overwritten).
*

View File

@ -179,13 +179,19 @@ interface nsIMemoryReporter : nsISupports
const int32_t UNITS_PERCENTAGE = 3;
};
[scriptable, builtinclass, uuid(4db7040a-16f9-4249-879b-fe72729c7ef5)]
[scriptable, function, uuid(548b3909-c04d-4ca6-8466-b8bee3837457)]
interface nsIFinishReportingCallback : nsISupports
{
void callback(in nsISupports data);
};
[scriptable, builtinclass, uuid(a1292276-726b-4ef5-a017-5a455d6664dd)]
interface nsIMemoryReporterManager : nsISupports
{
/*
* Return an enumerator of nsIMemoryReporters that are currently registered.
* Initialize.
*/
nsISimpleEnumerator enumerateReporters();
void init();
/*
* Register the given nsIMemoryReporter. After a reporter is registered,
@ -207,9 +213,30 @@ interface nsIMemoryReporterManager : nsISupports
void registerReporterEvenIfBlocked(in nsIMemoryReporter aReporter);
/*
* Initialize.
* Return an enumerator of nsIMemoryReporters that are currently registered
* in the current process. WARNING: this does not do anything with child
* processes. Use getReports() if you want measurements from child
* processes.
*/
void init();
nsISimpleEnumerator enumerateReporters();
/*
* Get memory reports for the current process and all child processes.
* |handleReport| is called for each report, and |finishReporting| is called
* once all reports have been handled.
*
* |finishReporting| is called even if, for example, some child processes
* fail to report back. However, calls to this method will silently and
* immediately abort -- and |finishReporting| will not be called -- if a
* previous getReports() call is still in flight, i.e. if it has not yet
* finished invoking |finishReporting|. The silent abort is because the
* in-flight request will finish soon, and the caller would very likely just
* catch and ignore any error anyway.
*/
void getReports(in nsIMemoryReporterCallback handleReport,
in nsISupports handleReportData,
in nsIFinishReportingCallback finishReporting,
in nsISupports finishReportingData);
/*
* The memory reporter manager, for the most part, treats reporters
@ -332,9 +359,14 @@ interface nsIMemoryReporterManager : nsISupports
#include "js/TypeDecls.h"
#include "nsStringGlue.h"
#include "nsTArray.h"
class nsPIDOMWindow;
// nsIHandleReportCallback is a better name, but keep nsIMemoryReporterCallback
// around for backwards compatibility.
typedef nsIMemoryReporterCallback nsIHandleReportCallback;
// Note that the memory reporters are held in an nsCOMArray, which means
// that individual reporters should be referenced with |nsIMemoryReporter *|
// instead of nsCOMPtr<nsIMemoryReporter>.
@ -396,8 +428,9 @@ nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn);
#if defined(MOZ_DMD)
namespace mozilla {
namespace dmd {
// This runs all the memory reporters but does nothing with the results; i.e.
// it does the minimal amount of work possible for DMD to do its thing.
// This runs all the memory reporters in the current process but does nothing
// with the results; i.e. it does the minimal amount of work possible for DMD
// to do its thing. It does nothing with child processes.
void RunReporters();
}
}

View File

@ -610,32 +610,33 @@ namespace mozilla {
} while (0)
static nsresult
DumpReport(nsIGZFileWriter *aWriter, bool *aIsFirstPtr,
DumpReport(nsIGZFileWriter *aWriter, bool aIsFirst,
const nsACString &aProcess, const nsACString &aPath, int32_t aKind,
int32_t aUnits, int64_t aAmount, const nsACString &aDescription)
{
// We only want to dump reports for this process. If |aProcess| is
// non-nullptr that means we've received it from another process in response
// to a "child-memory-reporter-request" event; ignore such reports.
if (!aProcess.IsEmpty()) {
return NS_OK;
}
DUMP(aWriter, aIsFirst ? "[" : ",");
DUMP(aWriter, *aIsFirstPtr ? "[" : ",");
*aIsFirstPtr = false;
// Generate the process identifier, which is of the form "$PROCESS_NAME
// (pid $PID)", or just "(pid $PID)" if we don't have a process name. If
// we're the main process, we let $PROCESS_NAME be "Main Process".
nsAutoCString process;
if (XRE_GetProcessType() == GeckoProcessType_Default) {
// We're the main process.
process.AssignLiteral("Main Process ");
} else if (ContentChild *cc = ContentChild::GetSingleton()) {
// Try to get the process name from ContentChild.
cc->GetProcessName(process);
if (aProcess.IsEmpty()) {
// If the process is empty, the report originated with the process doing
// the dumping. In that case, generate the process identifier, which is of
// the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we don't
// have a process name. If we're the main process, we let $PROCESS_NAME be
// "Main Process".
if (XRE_GetProcessType() == GeckoProcessType_Default) {
// We're the main process.
process.AssignLiteral("Main Process");
} else if (ContentChild *cc = ContentChild::GetSingleton()) {
// Try to get the process name from ContentChild.
cc->GetProcessName(process);
}
ContentChild::AppendProcessId(process);
} else {
// Otherwise, the report originated with another process and already has a
// process name. Just use that.
process = aProcess;
}
ContentChild::AppendProcessId(process);
DUMP(aWriter, "\n {\"process\": \"");
DUMP(aWriter, process);
@ -666,12 +667,12 @@ DumpReport(nsIGZFileWriter *aWriter, bool *aIsFirstPtr,
return NS_OK;
}
class DumpReporterCallback MOZ_FINAL : public nsIMemoryReporterCallback
class DumpReportCallback MOZ_FINAL : public nsIHandleReportCallback
{
public:
NS_DECL_ISUPPORTS
DumpReporterCallback() : mIsFirst(true) {}
DumpReportCallback() : mIsFirst(true) {}
NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
int32_t aKind, int32_t aUnits, int64_t aAmount,
@ -681,15 +682,17 @@ public:
nsCOMPtr<nsIGZFileWriter> writer = do_QueryInterface(aData);
NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE);
return DumpReport(writer, &mIsFirst, aProcess, aPath, aKind, aUnits,
aAmount, aDescription);
nsresult rv = DumpReport(writer, mIsFirst, aProcess, aPath, aKind, aUnits,
aAmount, aDescription);
mIsFirst = false;
return rv;
}
private:
bool mIsFirst;
};
NS_IMPL_ISUPPORTS1(DumpReporterCallback, nsIMemoryReporterCallback)
NS_IMPL_ISUPPORTS1(DumpReportCallback, nsIHandleReportCallback)
} // namespace mozilla
@ -785,7 +788,7 @@ DMDWrite(void* aState, const char* aFmt, va_list ap)
#endif
static nsresult
DumpProcessMemoryReportsToGZFileWriter(nsIGZFileWriter *aWriter)
DumpHeader(nsIGZFileWriter* aWriter)
{
// Increment this number if the format changes.
//
@ -804,22 +807,39 @@ DumpProcessMemoryReportsToGZFileWriter(nsIGZFileWriter *aWriter)
DUMP(aWriter, ",\n");
DUMP(aWriter, " \"reports\": ");
// Process reporters.
bool more;
nsCOMPtr<nsISimpleEnumerator> e;
mgr->EnumerateReporters(getter_AddRefs(e));
nsRefPtr<DumpReporterCallback> cb = new DumpReporterCallback();
while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryReporter> r;
e->GetNext(getter_AddRefs(r));
r->CollectReports(cb, aWriter);
}
return NS_OK;
}
static nsresult
DumpFooter(nsIGZFileWriter* aWriter)
{
DUMP(aWriter, "\n ]\n}\n");
return NS_OK;
}
static nsresult
DumpProcessMemoryReportsToGZFileWriter(nsIGZFileWriter* aWriter)
{
nsresult rv = DumpHeader(aWriter);
NS_ENSURE_SUCCESS(rv, rv);
// Process reporters.
bool more;
nsCOMPtr<nsISimpleEnumerator> e;
nsCOMPtr<nsIMemoryReporterManager> mgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
mgr->EnumerateReporters(getter_AddRefs(e));
nsRefPtr<DumpReportCallback> dumpReport = new DumpReportCallback();
while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryReporter> r;
e->GetNext(getter_AddRefs(r));
r->CollectReports(dumpReport, aWriter);
}
return DumpFooter(aWriter);
}
nsresult
DumpProcessMemoryInfoToTempDir(const nsAString& aIdentifier)
{
@ -977,8 +997,45 @@ nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
return DumpProcessMemoryInfoToTempDir(identifier);
}
// This dumps the JSON footer and closes the file, and then calls the given
// nsIFinishDumpingCallback.
class FinishReportingCallback MOZ_FINAL : public nsIFinishReportingCallback
{
public:
NS_DECL_ISUPPORTS
FinishReportingCallback(nsIFinishDumpingCallback* aFinishDumping,
nsISupports* aFinishDumpingData)
: mFinishDumping(aFinishDumping)
, mFinishDumpingData(aFinishDumpingData)
{}
NS_IMETHOD Callback(nsISupports* aData)
{
nsCOMPtr<nsIGZFileWriter> writer = do_QueryInterface(aData);
NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE);
nsresult rv = DumpFooter(writer);
NS_ENSURE_SUCCESS(rv, rv);
rv = writer->Finish();
NS_ENSURE_SUCCESS(rv, rv);
return mFinishDumping->Callback(mFinishDumpingData);
}
private:
nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
nsCOMPtr<nsISupports> mFinishDumpingData;
};
NS_IMPL_ISUPPORTS1(FinishReportingCallback, nsIFinishReportingCallback)
NS_IMETHODIMP
nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(const nsAString& aFilename)
nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
const nsAString& aFilename,
nsIFinishDumpingCallback* aFinishDumping,
nsISupports* aFinishDumpingData)
{
MOZ_ASSERT(!aFilename.IsEmpty());
@ -1006,12 +1063,16 @@ nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(const nsAString& aFilename)
rv = mrWriter->Init(mrFile);
NS_ENSURE_SUCCESS(rv, rv);
DumpProcessMemoryReportsToGZFileWriter(mrWriter);
rv = mrWriter->Finish();
rv = DumpHeader(mrWriter);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
// Process reports and finish up.
nsRefPtr<DumpReportCallback> dumpReport = new DumpReportCallback();
nsRefPtr<FinishReportingCallback> finishReporting =
new FinishReportingCallback(aFinishDumping, aFinishDumpingData);
nsCOMPtr<nsIMemoryReporterManager> mgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
return mgr->GetReports(dumpReport, mrWriter, finishReporting, mrWriter);
}
#undef DUMP

View File

@ -11,6 +11,7 @@
#include "nsServiceManagerUtils.h"
#include "nsMemoryReporterManager.h"
#include "nsISimpleEnumerator.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
#include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
@ -23,6 +24,7 @@
#include "mozilla/PodOperations.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/PMemoryReportRequestParent.h" // for dom::MemoryReport
#ifndef XP_WIN
#include <unistd.h>
@ -679,7 +681,7 @@ public:
return NS_OK;
}
NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCallback,
NS_IMETHOD CollectReports(nsIHandleReport* aHandleReport,
nsISupports* aData)
{
dmd::Sizes sizes;
@ -688,10 +690,10 @@ public:
#define REPORT(_path, _amount, _desc) \
do { \
nsresult rv; \
rv = aCallback->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
nsIMemoryReporter::KIND_HEAP, \
nsIMemoryReporter::UNITS_BYTES, _amount, \
NS_LITERAL_CSTRING(_desc), aData); \
rv = aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
nsIMemoryReporter::KIND_HEAP, \
nsIMemoryReporter::UNITS_BYTES, _amount, \
NS_LITERAL_CSTRING(_desc), aData); \
NS_ENSURE_SUCCESS(rv, rv); \
} while (0)
@ -845,10 +847,11 @@ HashtableEnumerator::GetNext(nsISupports** aNext)
nsMemoryReporterManager::nsMemoryReporterManager()
: mMutex("nsMemoryReporterManager::mMutex"),
mIsRegistrationBlocked(false)
mIsRegistrationBlocked(false),
mNumChildProcesses(0),
mNextGeneration(1),
mGetReportsState(nullptr)
{
PodZero(&mAmountFns);
PodZero(&mSizeOfTabFns);
}
nsMemoryReporterManager::~nsMemoryReporterManager()
@ -858,8 +861,8 @@ nsMemoryReporterManager::~nsMemoryReporterManager()
NS_IMETHODIMP
nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator** aResult)
{
// Memory reporters are not necessarily threadsafe, so EnumerateReporters()
// must be called from the main thread.
// Memory reporters are not necessarily threadsafe, so this function must
// be called from the main thread.
if (!NS_IsMainThread()) {
MOZ_CRASH();
}
@ -872,6 +875,220 @@ nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator** aResult)
return NS_OK;
}
//#define DEBUG_CHILD_PROCESS_MEMORY_REPORTING 1
#ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING
#define MEMORY_REPORTING_LOG(format, ...) \
fprintf(stderr, "++++ MEMORY REPORTING: " format, ##__VA_ARGS__);
#else
#define MEMORY_REPORTING_LOG(...)
#endif
void
nsMemoryReporterManager::IncrementNumChildProcesses()
{
if (!NS_IsMainThread()) {
MOZ_CRASH();
}
mNumChildProcesses++;
MEMORY_REPORTING_LOG("IncrementNumChildProcesses --> %d\n",
mNumChildProcesses);
}
void
nsMemoryReporterManager::DecrementNumChildProcesses()
{
if (!NS_IsMainThread()) {
MOZ_CRASH();
}
MOZ_ASSERT(mNumChildProcesses > 0);
mNumChildProcesses--;
MEMORY_REPORTING_LOG("DecrementNumChildProcesses --> %d\n",
mNumChildProcesses);
}
NS_IMETHODIMP
nsMemoryReporterManager::GetReports(
nsIHandleReportCallback* aHandleReport,
nsISupports* aHandleReportData,
nsIFinishReportingCallback* aFinishReporting,
nsISupports* aFinishReportingData)
{
// Memory reporters are not necessarily threadsafe, so this function must
// be called from the main thread.
if (!NS_IsMainThread()) {
MOZ_CRASH();
}
uint32_t generation = mNextGeneration++;
if (mGetReportsState) {
// A request is in flight. Don't start another one. And don't report
// an error; just ignore it, and let the in-flight request finish.
MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n",
generation, mGetReportsState->mGeneration);
return NS_OK;
}
MEMORY_REPORTING_LOG("GetReports (gen=%u, %d child(ren) present)\n",
generation, mNumChildProcesses);
if (mNumChildProcesses > 0) {
// Request memory reports from child processes. We do this *before*
// collecting reports for this process so each process can collect
// reports in parallel.
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
NS_ENSURE_STATE(obs);
// Casting the uint32_t generation to |const PRUnichar*| is a hack, but
// simpler than converting the number to an actual string.
obs->NotifyObservers(nullptr, "child-memory-reporter-request",
(const PRUnichar*)(uintptr_t)generation);
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
NS_ENSURE_TRUE(timer, NS_ERROR_FAILURE);
nsresult rv = timer->InitWithFuncCallback(TimeoutCallback,
this, kTimeoutLengthMS,
nsITimer::TYPE_ONE_SHOT);
NS_ENSURE_SUCCESS(rv, rv);
mGetReportsState = new GetReportsState(generation,
timer,
mNumChildProcesses,
aHandleReport,
aHandleReportData,
aFinishReporting,
aFinishReportingData);
}
// Get reports for this process.
nsRefPtr<HashtableEnumerator> e;
{
mozilla::MutexAutoLock autoLock(mMutex);
e = new HashtableEnumerator(mReporters);
}
bool more;
while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryReporter> r;
e->GetNext(getter_AddRefs(r));
r->CollectReports(aHandleReport, aHandleReportData);
}
// If there are no child processes, we can finish up immediately.
return (mNumChildProcesses == 0)
? aFinishReporting->Callback(aFinishReportingData)
: NS_OK;
}
// This function has no return value. If something goes wrong, there's no
// clear place to report the problem to, but that's ok -- we will end up
// hitting the timeout and executing TimeoutCallback().
void
nsMemoryReporterManager::HandleChildReports(
const uint32_t& aGeneration,
const InfallibleTArray<dom::MemoryReport>& aChildReports)
{
// Memory reporting only happens on the main thread.
if (!NS_IsMainThread()) {
MOZ_CRASH();
}
GetReportsState* s = mGetReportsState;
if (!s) {
// If we reach here, either:
//
// - A child process reported back too late, and no subsequent request
// is in flight.
//
// - (Unlikely) A "child-memory-reporter-request" notification was
// triggered from somewhere other than GetReports(), causing child
// processes to report back when the nsMemoryReporterManager wasn't
// expecting it.
//
// Either way, there's nothing to be done. Just ignore it.
MEMORY_REPORTING_LOG(
"HandleChildReports: no request in flight (aGen=%u)\n",
aGeneration);
return;
}
if (aGeneration != s->mGeneration) {
// If we reach here, a child process must have reported back, too late,
// while a subsequent (higher-numbered) request is in flight. Again,
// ignore it.
MOZ_ASSERT(aGeneration < s->mGeneration);
MEMORY_REPORTING_LOG(
"HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n",
aGeneration, s->mGeneration);
return;
}
// Process the reports from the child process.
for (uint32_t i = 0; i < aChildReports.Length(); i++) {
const dom::MemoryReport& r = aChildReports[i];
// Child reports should have a non-empty process.
MOZ_ASSERT(!r.process().IsEmpty());
// If the call fails, ignore and continue.
s->mHandleReport->Callback(r.process(), r.path(), r.kind(),
r.units(), r.amount(), r.desc(),
s->mHandleReportData);
}
// If all the child processes have reported, we can cancel the timer and
// finish up. Otherwise, just return.
s->mNumChildProcessesCompleted++;
MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): completed child %d\n",
aGeneration, s->mNumChildProcessesCompleted);
if (s->mNumChildProcessesCompleted == s->mNumChildProcesses) {
s->mTimer->Cancel();
FinishReporting();
}
}
/* static */ void
nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData)
{
nsMemoryReporterManager* mgr =
static_cast<nsMemoryReporterManager*>(aData);
MOZ_ASSERT(mgr->mGetReportsState);
MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u)\n",
mgr->mGetReportsState->mGeneration);
// We don't bother sending any kind of cancellation message to the child
// processes that haven't reported back.
mgr->FinishReporting();
}
void
nsMemoryReporterManager::FinishReporting()
{
// Memory reporting only happens on the main thread.
if (!NS_IsMainThread()) {
MOZ_CRASH();
}
MOZ_ASSERT(mGetReportsState);
MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u)\n",
mGetReportsState->mGeneration);
// Call this before deleting |mGetReportsState|. That way, if
// |mFinishReportData| calls GetReports(), it will silently abort, as
// required.
(void)mGetReportsState->mFinishReporting->Callback(
mGetReportsState->mFinishReportingData);
delete mGetReportsState;
mGetReportsState = nullptr;
}
static void
DebugAssertRefcountIsNonZero(nsISupports* aObj)
{
@ -975,7 +1192,7 @@ public:
};
NS_IMPL_ISUPPORTS0(Int64Wrapper)
class ExplicitCallback MOZ_FINAL : public nsIMemoryReporterCallback
class ExplicitCallback MOZ_FINAL : public nsIHandleReportCallback
{
public:
NS_DECL_ISUPPORTS
@ -1002,7 +1219,7 @@ public:
return NS_OK;
}
};
NS_IMPL_ISUPPORTS1(ExplicitCallback, nsIMemoryReporterCallback)
NS_IMPL_ISUPPORTS1(ExplicitCallback, nsIHandleReportCallback)
NS_IMETHODIMP
nsMemoryReporterManager::GetExplicit(int64_t* aAmount)
@ -1020,7 +1237,7 @@ nsMemoryReporterManager::GetExplicit(int64_t* aAmount)
// method which did this more efficiently, but it ended up being more
// trouble than it was worth.
nsRefPtr<ExplicitCallback> cb = new ExplicitCallback();
nsRefPtr<ExplicitCallback> handleReport = new ExplicitCallback();
nsRefPtr<Int64Wrapper> wrappedExplicitSize = new Int64Wrapper();
nsCOMPtr<nsISimpleEnumerator> e;
@ -1028,7 +1245,7 @@ nsMemoryReporterManager::GetExplicit(int64_t* aAmount)
while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryReporter> r;
e->GetNext(getter_AddRefs(r));
r->CollectReports(cb, wrappedExplicitSize);
r->CollectReports(handleReport, wrappedExplicitSize);
}
*aAmount = wrappedExplicitSize->mValue;
@ -1349,10 +1566,8 @@ NS_UnregisterMemoryReporter(nsIMemoryReporter* aReporter)
namespace mozilla {
#define GET_MEMORY_REPORTER_MANAGER(mgr) \
nsCOMPtr<nsIMemoryReporterManager> imgr = \
do_GetService("@mozilla.org/memory-reporter-manager;1"); \
nsRefPtr<nsMemoryReporterManager> mgr = \
static_cast<nsMemoryReporterManager*>(imgr.get()); \
nsMemoryReporterManager::GetOrCreate(); \
if (!mgr) { \
return NS_ERROR_FAILURE; \
}
@ -1418,7 +1633,7 @@ DEFINE_REGISTER_SIZE_OF_TAB(NonJS);
namespace mozilla {
namespace dmd {
class NullReporterCallback : public nsIMemoryReporterCallback
class DoNothingCallback : public nsIHandleReportCallback
{
public:
NS_DECL_ISUPPORTS
@ -1433,8 +1648,8 @@ public:
}
};
NS_IMPL_ISUPPORTS1(
NullReporterCallback
, nsIMemoryReporterCallback
DoNothingCallback
, nsIHandleReportCallback
)
void
@ -1443,7 +1658,7 @@ RunReporters()
nsCOMPtr<nsIMemoryReporterManager> mgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
nsRefPtr<NullReporterCallback> cb = new NullReporterCallback();
nsRefPtr<DoNothingCallback> doNothing = new DoNothingCallback();
bool more;
nsCOMPtr<nsISimpleEnumerator> e;
@ -1451,7 +1666,7 @@ RunReporters()
while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryReporter> r;
e->GetNext(getter_AddRefs(r));
r->CollectReports(cb, nullptr);
r->CollectReports(doNothing, nullptr);
}
}

View File

@ -11,6 +11,14 @@
using mozilla::Mutex;
class nsITimer;
namespace mozilla {
namespace dom {
class MemoryReport;
}
}
class nsMemoryReporterManager : public nsIMemoryReporterManager
{
public:
@ -20,6 +28,93 @@ public:
nsMemoryReporterManager();
virtual ~nsMemoryReporterManager();
// Gets the memory reporter manager service.
static nsMemoryReporterManager* GetOrCreate()
{
nsCOMPtr<nsIMemoryReporterManager> imgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
return static_cast<nsMemoryReporterManager*>(imgr.get());
}
void IncrementNumChildProcesses();
void DecrementNumChildProcesses();
// Inter-process memory reporting proceeds as follows.
//
// - GetReports() (declared within NS_DECL_NSIMEMORYREPORTERMANAGER)
// synchronously gets memory reports for the current process, tells all
// child processes to get memory reports, and sets up some state
// (mGetReportsState) for when the child processes report back, including a
// timer. Control then returns to the main event loop.
//
// - HandleChildReports() is called (asynchronously) once per child process
// that reports back. If all child processes report back before time-out,
// the timer is cancelled. (The number of child processes is part of the
// saved request state.)
//
// - TimeoutCallback() is called (asynchronously) if all the child processes
// don't respond within the time threshold.
//
// - FinishReporting() finishes things off. It is *always* called -- either
// from HandleChildReports() (if all child processes have reported back) or
// from TimeoutCallback() (if time-out occurs).
//
// All operations occur on the main thread.
//
// The above sequence of steps is a "request". A partially-completed request
// is described as "in flight".
//
// Each request has a "generation", a unique number that identifies it. This
// is used to ensure that each reports from a child process corresponds to
// the appropriate request from the parent process. (It's easier to
// implement a generation system than to implement a child report request
// cancellation mechanism.)
//
// Failures are mostly ignored, because it's (a) typically the most sensible
// thing to do, and (b) often hard to do anything else. The following are
// the failure cases of note.
//
// - If a request is made while the previous request is in flight, the new
// request is ignored, as per getReports()'s specification. No error is
// reported, because the previous request will complete soon enough.
//
// - If one or more child processes fail to respond within the time limit,
// things will proceed as if they don't exist. No error is reported,
// because partial information is better than nothing.
//
// - If a child process reports after the time-out occurs, it is ignored.
// (Generation checking will ensure it is ignored even if a subsequent
// request is in flight; this is the main use of generations.) No error
// is reported, because there's nothing sensible to be done about it at
// this late stage.
//
// Now, what what happens if a child process is created/destroyed in the
// middle of a request? Well, GetReportsState contains a copy of
// mNumChildProcesses which it uses to determine finished-ness. So...
//
// - If a process is created, it won't have received the request for reports,
// and the GetReportsState's mNumChildProcesses won't account for it. So
// the reported data will reflect how things were when the request began.
//
// - If a process is destroyed before reporting back, we'll just hit the
// time-out, because we'll have received reports (barring other errors)
// from N-1 child process. So the reported data will reflect how things
// are when the request ends.
//
// - If a process is destroyed after reporting back, but before all other
// child processes have reported back, it will be included in the reported
// data. So the reported data will reflect how things were when the
// request began.
//
// The inconsistencies between these three cases are unfortunate but
// difficult to avoid. It's enough of an edge case to not be worth doing
// more.
//
void HandleChildReports(
const uint32_t& generation,
const InfallibleTArray<mozilla::dom::MemoryReport>& aChildReports);
void FinishReporting();
// Functions that (a) implement distinguished amounts, and (b) are outside of
// this module.
struct AmountFns {
@ -36,6 +131,8 @@ public:
mozilla::InfallibleAmountFn mLowMemoryEventsPhysical;
mozilla::InfallibleAmountFn mGhostWindows;
AmountFns() { mozilla::PodZero(this); }
};
AmountFns mAmountFns;
@ -43,15 +140,55 @@ public:
struct SizeOfTabFns {
mozilla::JSSizeOfTabFn mJS;
mozilla::NonJSSizeOfTabFn mNonJS;
SizeOfTabFns() { mozilla::PodZero(this); }
};
SizeOfTabFns mSizeOfTabFns;
private:
nsresult RegisterReporterHelper(nsIMemoryReporter *aReporter, bool aForce);
nsresult RegisterReporterHelper(nsIMemoryReporter* aReporter, bool aForce);
static void TimeoutCallback(nsITimer* aTimer, void* aData);
static const uint32_t kTimeoutLengthMS = 5000;
nsTHashtable<nsISupportsHashKey> mReporters;
Mutex mMutex;
bool mIsRegistrationBlocked;
uint32_t mNumChildProcesses;
uint32_t mNextGeneration;
struct GetReportsState {
uint32_t mGeneration;
nsCOMPtr<nsITimer> mTimer;
uint32_t mNumChildProcesses;
uint32_t mNumChildProcessesCompleted;
nsCOMPtr<nsIHandleReportCallback> mHandleReport;
nsCOMPtr<nsISupports> mHandleReportData;
nsCOMPtr<nsIFinishReportingCallback> mFinishReporting;
nsCOMPtr<nsISupports> mFinishReportingData;
GetReportsState(uint32_t aGeneration, nsITimer* aTimer,
uint32_t aNumChildProcesses,
nsIHandleReportCallback* aHandleReport,
nsISupports* aHandleReportData,
nsIFinishReportingCallback* aFinishReporting,
nsISupports* aFinishReportingData)
: mGeneration(aGeneration),
mTimer(aTimer),
mNumChildProcesses(aNumChildProcesses),
mNumChildProcessesCompleted(0),
mHandleReport(aHandleReport),
mHandleReportData(aHandleReportData),
mFinishReporting(aFinishReporting),
mFinishReportingData(aFinishReportingData)
{}
};
// When this is non-null, a request is in flight. Note: We use manual
// new/delete for this because its lifetime doesn't match block scope or
// anything like that.
GetReportsState* mGetReportsState;
};
#define NS_MEMORY_REPORTER_MANAGER_CID \