Bug 738624 - Add ghost windows to about:compartments. r=njn

--HG--
extra : rebase_source : a486e644351af72a4276f26de2e4588ee851426f
This commit is contained in:
Justin Lebar 2012-04-02 10:37:04 -04:00
parent a00240770f
commit 39bee4009c
6 changed files with 326 additions and 61 deletions

View File

@ -58,16 +58,23 @@ NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryMultiReporter, nsIObserver,
void
nsWindowMemoryReporter::Init()
{
// The memory reporter manager is going to own this object.
nsWindowMemoryReporter *reporter = new nsWindowMemoryReporter();
NS_RegisterMemoryMultiReporter(reporter);
// The memory reporter manager will own this object.
nsWindowMemoryReporter *windowReporter = new nsWindowMemoryReporter();
NS_RegisterMemoryMultiReporter(windowReporter);
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
// DOM_WINDOW_DESTROYED_TOPIC announces what we call window "detachment",
// when a window's docshell is set to NULL.
os->AddObserver(reporter, DOM_WINDOW_DESTROYED_TOPIC, /* weakRef = */ true);
os->AddObserver(windowReporter, DOM_WINDOW_DESTROYED_TOPIC,
/* weakRef = */ true);
os->AddObserver(windowReporter, "after-minimize-memory-usage",
/* weakRef = */ true);
}
nsGhostWindowMemoryReporter *ghostReporter =
new nsGhostWindowMemoryReporter(windowReporter);
NS_RegisterMemoryMultiReporter(ghostReporter);
}
static already_AddRefed<nsIURI>
@ -291,17 +298,29 @@ nsWindowMemoryReporter::GetExplicitNonHeap(PRInt64* aAmount)
return NS_OK;
}
PRUint32
nsWindowMemoryReporter::GetGhostTimeout()
{
return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
}
NS_IMETHODIMP
nsWindowMemoryReporter::Observe(nsISupports *aSubject, const char *aTopic,
const PRUnichar *aData)
{
// A window was detached. Insert it into mDetachedWindows and run
// CheckForGhostWindows sometime soon.
if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) {
ObserveDOMWindowDetached(aSubject);
} else if (!strcmp(aTopic, "after-minimize-memory-usage")) {
ObserveAfterMinimizeMemoryUsage();
} else {
MOZ_ASSERT(false);
}
MOZ_ASSERT(!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC));
return NS_OK;
}
void
nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports *aWindow)
nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports* aWindow)
{
nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
if (!weakWindow) {
@ -318,8 +337,35 @@ nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports *aWindow)
NS_DispatchToCurrentThread(runnable);
mCheckForGhostWindowsCallbackPending = true;
}
}
return NS_OK;
static PLDHashOperator
BackdateTimeStampsEnumerator(nsISupports *aKey, TimeStamp &aTimeStamp,
void* aClosure)
{
TimeStamp *minTimeStamp = static_cast<TimeStamp*>(aClosure);
if (!aTimeStamp.IsNull() && aTimeStamp > *minTimeStamp) {
aTimeStamp = *minTimeStamp;
}
return PL_DHASH_NEXT;
}
void
nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage()
{
// Someone claims they've done enough GC/CCs so that all eligible windows
// have been free'd. So we deem that any windows which satisfy ghost
// criteria (1) and (2) now satisfy criterion (3) as well.
//
// To effect this change, we'll backdate some of our timestamps.
TimeStamp minTimeStamp = TimeStamp::Now() -
TimeDuration::FromSeconds(GetGhostTimeout());
mDetachedWindows.Enumerate(BackdateTimeStampsEnumerator,
&minTimeStamp);
}
void
@ -467,14 +513,103 @@ nsWindowMemoryReporter::CheckForGhostWindows(
windowsById->EnumerateRead(GetNonDetachedWindowDomainsEnumerator,
&nonDetachedEnumData);
PRUint32 ghostTimeout =
Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
// Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
// if it's not null.
CheckForGhostWindowsEnumeratorData ghostEnumData =
{ &nonDetachedWindowDomains, aOutGhostIDs, tldService,
ghostTimeout, TimeStamp::Now() };
GetGhostTimeout(), TimeStamp::Now() };
mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator,
&ghostEnumData);
}
NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::nsGhostWindowMemoryReporter,
nsIMemoryMultiReporter)
nsWindowMemoryReporter::
nsGhostWindowMemoryReporter::nsGhostWindowMemoryReporter(
nsWindowMemoryReporter* aWindowReporter)
: mWindowReporter(aWindowReporter)
{
}
NS_IMETHODIMP
nsWindowMemoryReporter::
nsGhostWindowMemoryReporter::GetName(nsACString& aName)
{
aName.AssignLiteral("ghost-windows");
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
nsGhostWindowMemoryReporter::GetExplicitNonHeap(PRInt64* aOut)
{
*aOut = 0;
return NS_OK;
}
struct ReportGhostWindowsEnumeratorData
{
nsIMemoryMultiReporterCallback* callback;
nsISupports* closure;
nsresult rv;
};
static PLDHashOperator
ReportGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void* aClosure)
{
ReportGhostWindowsEnumeratorData *data =
static_cast<ReportGhostWindowsEnumeratorData*>(aClosure);
nsGlobalWindow::WindowByIdTable* windowsById =
nsGlobalWindow::GetWindowsTable();
if (!windowsById) {
NS_WARNING("Couldn't get window-by-id hashtable?");
return PL_DHASH_NEXT;
}
nsGlobalWindow* window = windowsById->Get(aIDHashKey->GetKey());
if (!window) {
NS_WARNING("Could not look up window?");
return PL_DHASH_NEXT;
}
nsCAutoString path;
path.AppendLiteral("ghost-windows/");
AppendWindowURI(window, path);
nsresult rv = data->callback->Callback(
/* process = */ EmptyCString(),
path,
nsIMemoryReporter::KIND_SUMMARY,
nsIMemoryReporter::UNITS_COUNT,
/* amount = */ 1,
/* desc = */ EmptyCString(),
data->closure);
if (NS_FAILED(rv) && NS_SUCCEEDED(data->rv)) {
data->rv = rv;
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
nsGhostWindowMemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb,
nsISupports* aClosure)
{
// Get the IDs of all the ghost windows in existance.
nsTHashtable<nsUint64HashKey> ghostWindows;
ghostWindows.Init();
mWindowReporter->CheckForGhostWindows(&ghostWindows);
ReportGhostWindowsEnumeratorData reportGhostWindowsEnumData =
{ aCb, aClosure, NS_OK };
// Call aCb->Callback() for each ghost window.
ghostWindows.EnumerateEntries(ReportGhostWindowsEnumerator,
&reportGhostWindowsEnumData);
return reportGhostWindowsEnumData.rv;
}

View File

@ -42,6 +42,7 @@
#include "nsIObserver.h"
#include "nsDataHashtable.h"
#include "nsWeakReference.h"
#include "nsAutoPtr.h"
#include "mozilla/TimeStamp.h"
// This should be used for any nsINode sub-class that has fields of its own
@ -139,6 +140,23 @@ public:
static void Init();
private:
/**
* nsGhostWindowMemoryReporter generates the "ghost-windows" memory report.
* If you're only interested in the list of ghost windows, running this
* report is faster than running nsWindowMemoryReporter.
*/
class nsGhostWindowMemoryReporter: public nsIMemoryMultiReporter
{
public:
nsGhostWindowMemoryReporter(nsWindowMemoryReporter* aWindowReporter);
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYMULTIREPORTER
private:
nsRefPtr<nsWindowMemoryReporter> mWindowReporter;
};
// Protect ctor, use Init() instead.
nsWindowMemoryReporter();

View File

@ -1703,7 +1703,7 @@ class JSCompartmentsMultiReporter : public nsIMemoryMultiReporter
for (size_t i = 0; i < paths.length(); i++)
// These ones don't need a description, hence the "".
REPORT(nsCString(paths[i]),
nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::KIND_SUMMARY,
nsIMemoryReporter::UNITS_COUNT,
1, "");

View File

@ -51,6 +51,7 @@ const Cu = Components.utils;
const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const KIND_OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const KIND_SUMMARY = Ci.nsIMemoryReporter.KIND_SUMMARY;
const UNITS_BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
@ -163,10 +164,12 @@ function minimizeMemoryUsage3x(fAfter)
.getService(Ci.nsIObserverService);
os.notifyObservers(null, "memory-pressure", "heap-minimize");
if (++i < 3)
if (++i < 3) {
runSoon(sendHeapMinNotificationsInner);
else
} else {
os.notifyObservers(null, "after-minimize-memory-usage", "about:memory");
runSoon(fAfter);
}
}
sendHeapMinNotificationsInner();
@ -275,11 +278,10 @@ function checkReport(aUnsafePath, aKind, aUnits, aAmount, aDescription)
assert(aUnits === UNITS_BYTES, "bad smaps units");
assert(aDescription !== "", "empty smaps description");
} else if (aUnsafePath.startsWith("compartments/")) {
assert(aKind === KIND_OTHER, "bad compartments kind");
assert(aUnits === UNITS_COUNT, "bad compartments units");
assert(aAmount === 1, "bad amount");
assert(aDescription === "", "bad description");
} else if (aKind === KIND_SUMMARY) {
assert(!aUnsafePath.startsWith("explicit/") &&
!aUnsafePath.startsWith("smaps/"),
"bad SUMMARY path");
} else {
assert(aUnsafePath.indexOf("/") === -1, "'other' path contains '/'");
@ -537,20 +539,22 @@ Report.prototype = {
function getReportsByProcess(aMgr)
{
// Ignore the "smaps" multi-reporter in non-verbose mode, and the
// "compartments" multi-reporter all the time. (Note that reports from these
// multi-reporters can reach here as single reports if they were in the child
// process.)
// "compartments" and "ghost-windows" multi-reporters all the time. (Note
// that reports from these multi-reporters can reach here as single reports
// if they were in the child process.)
function ignoreSingle(aPath)
{
return (aPath.startsWith("smaps/") && !gVerbose) ||
(aPath.startsWith("compartments/"))
aPath.startsWith("compartments/") ||
aPath.startsWith("ghost-windows/");
}
function ignoreMulti(aName)
{
return ((aName === "smaps" && !gVerbose) ||
(aName === "compartments"));
return (aName === "smaps" && !gVerbose) ||
aName === "compartments" ||
aName === "ghost-windows";
}
let reportsByProcess = {};
@ -1552,15 +1556,21 @@ function updateAboutCompartments()
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
let compartmentsByProcess = getCompartmentsByProcess(mgr);
let ghostWindowsByProcess = getGhostWindowsByProcess(mgr);
function handleProcess(aProcess) {
appendProcessAboutCompartmentsElements(body, aProcess,
compartmentsByProcess[aProcess],
ghostWindowsByProcess[aProcess]);
}
// Generate output for one process at a time. Always start with the
// Main process.
let compartmentsByProcess = getCompartmentsByProcess(mgr);
appendProcessCompartmentsElements(body, "Main",
compartmentsByProcess["Main"]);
handleProcess('Main');
for (let process in compartmentsByProcess) {
if (process !== "Main") {
appendProcessCompartmentsElements(body, process,
compartmentsByProcess[process]);
handleProcess(process);
}
}
@ -1656,30 +1666,89 @@ function getCompartmentsByProcess(aMgr)
return compartmentsByProcess;
}
//---------------------------------------------------------------------------
function appendProcessCompartmentsElementsHelper(aP, aCompartments, aKindString)
function GhostWindow(aUnsafeURL)
{
appendElementWithText(aP, "h2", "", aKindString + " Compartments\n");
// Call it _unsafeName rather than _unsafeURL for symmetry with the
// Compartment object.
this._unsafeName = aUnsafeURL;
let compartmentTextArray = [];
let uPre = appendElement(aP, "pre", "entries");
for (let name in aCompartments) {
let c = aCompartments[name];
let isSystemKind = aKindString === "System";
if (c._isSystemCompartment === isSystemKind) {
let text = flipBackslashes(c._unsafeName);
if (c._nMerged) {
text += " [" + c._nMerged + "]";
}
text += "\n";
compartmentTextArray.push(text);
// this._nMerged is only defined if > 1
}
GhostWindow.prototype = {
merge: function(r) {
this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
}
};
function getGhostWindowsByProcess(aMgr)
{
function ignoreSingle(aPath)
{
return !aPath.startsWith('ghost-windows/')
}
function ignoreMulti(aName)
{
return aName !== "ghost-windows";
}
let ghostWindowsByProcess = {};
function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
aDescription)
{
let unsafeSplit = aUnsafePath.split('/');
assert(unsafeSplit[0] == 'ghost-windows/',
'Unexpected path in getGhostWindowsByProcess: ' + aUnsafePath);
let unsafeURL = unsafeSplit[1];
let ghostWindow = new GhostWindow(unsafeURL);
let process = aProcess === "" ? "Main" : aProcess;
if (!ghostWindowsByProcess[process]) {
ghostWindowsByProcess[process] = {};
}
if (ghostWindowsByProcess[process][unsafeURL]) {
ghostWindowsByProcess[process][unsafeURL].merge(ghostWindow);
}
else {
ghostWindowsByProcess[process][unsafeURL] = ghostWindow;
}
}
compartmentTextArray.sort();
for (let i = 0; i < compartmentTextArray.length; i++) {
appendElementWithText(uPre, "span", "", compartmentTextArray[i]);
processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
return ghostWindowsByProcess;
}
//---------------------------------------------------------------------------
function appendProcessAboutCompartmentsElementsHelper(aP, aEntries, aKindString)
{
// aEntries might be null or undefined, e.g. if there are no ghost windows
// for this process.
aEntries = aEntries ? aEntries : {};
appendElementWithText(aP, "h2", "", aKindString + "\n");
let uPre = appendElement(aP, "pre", "entries");
let lines = [];
for (let name in aEntries) {
let e = aEntries[name];
let line = flipBackslashes(e._unsafeName);
if (e._nMerged) {
line += ' [' + e._nMerged + ']';
}
line += '\n';
lines.push(line);
}
lines.sort();
for (let i = 0; i < lines.length; i++) {
appendElementWithText(uPre, "span", "", lines[i]);
}
appendTextNode(aP, "\n"); // gives nice spacing when we cut and paste
@ -1694,14 +1763,30 @@ function appendProcessCompartmentsElementsHelper(aP, aCompartments, aKindString)
* The name of the process.
* @param aCompartments
* Table of Compartments for this process, indexed by _unsafeName.
* @param aGhostWindows
* Array of window URLs of ghost windows.
*
* @return The generated text.
*/
function appendProcessCompartmentsElements(aP, aProcess, aCompartments)
function appendProcessAboutCompartmentsElements(aP, aProcess, aCompartments, aGhostWindows)
{
appendElementWithText(aP, "h1", "", aProcess + " Process");
appendTextNode(aP, "\n\n"); // gives nice spacing when we cut and paste
appendProcessCompartmentsElementsHelper(aP, aCompartments, "User");
appendProcessCompartmentsElementsHelper(aP, aCompartments, "System");
let userCompartments = {};
let systemCompartments = {};
for (let name in aCompartments) {
let c = aCompartments[name];
if (c._isSystemCompartment) {
systemCompartments[name] = c;
}
else {
userCompartments[name] = c;
}
}
appendProcessAboutCompartmentsElementsHelper(aP, userCompartments, "User Compartments");
appendProcessAboutCompartmentsElementsHelper(aP, systemCompartments, "System Compartments");
appendProcessAboutCompartmentsElementsHelper(aP, aGhostWindows, "Ghost Windows");
}

View File

@ -47,6 +47,7 @@
const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const SUMMARY = Ci.nsIMemoryReporter.KIND_SUMMARY;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
@ -71,8 +72,8 @@
f("", "other2", OTHER, COUNT, 888),
f("2nd", "explicit/c", HEAP, BYTES, 333 * MB),
f("2nd", "compartments/user/child-user-compartment", OTHER, COUNT, 1),
f("2nd", "compartments/system/child-system-compartment", OTHER, COUNT, 1)
f("2nd", "compartments/user/child-user-compartment", SUMMARY, COUNT, 1),
f("2nd", "compartments/system/child-system-compartment", SUMMARY, COUNT, 1)
];
var fakeMultiReporters = [
@ -90,7 +91,7 @@
{ name: "compartments",
collectReports: function(aCbObj, aClosure) {
function f(aP) {
aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
aCbObj.callback("", aP, SUMMARY, COUNT, 1, "", aClosure);
}
f("compartments/user/http:\\\\foo.com\\");
f("compartments/user/https:\\\\bar.com\\bar?baz");
@ -106,6 +107,16 @@
},
explicitNonHeap: 0
},
{ name: "ghost-windows",
collectReports: function(aCbObj, aClosure) {
function f(aP) {
aCbObj.callback("", aP, SUMMARY, COUNT, 1, "", aClosure);
}
f("ghost-windows/https:\\\\very-long-url.com\\very-long\\oh-so-long\\really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789");
f("ghost-windows/http:\\\\foobar.com\\foo?bar#baz");
},
explicitNonHeap: 0
},
// These shouldn't show up.
{ name: "smaps",
collectReports: function(aCbObj, aClosure) {
@ -149,6 +160,10 @@ System Compartments\n\
atoms\n\
moz-nullprincipal:{7ddefdaf-34f1-473f-9b03-50a4568ccb06}\n\
\n\
Ghost Windows\n\
http://foobar.com/foo?bar#baz\n\
https://very-long-url.com/very-long/oh-so-long/really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789\n\
\n\
2nd Process\n\
\n\
User Compartments\n\
@ -157,6 +172,8 @@ child-user-compartment\n\
System Compartments\n\
child-system-compartment\n\
\n\
Ghost Windows\n\
\n\
";
// Verbose mode output is the same when you cut and paste.

View File

@ -117,10 +117,8 @@ interface nsIMemoryReporter : nsISupports
* Reporters in this category must have kind NONHEAP, units BYTES, and
* a non-empty description.
*
* - Paths starting with "compartments/" represent the names of JS
* compartments. Reporters in this category must paths of the form
* "compartments/user/<name>" or "compartments/system/<name>", amount 1,
* kind OTHER, units COUNT, and an empty description.
* - Reporters with kind SUMMARY may have any path which doesn't start with
* "explicit/" or "smaps/".
*
* - All other paths represent cross-cutting values and may overlap with any
* other reporter. Reporters in this category must have paths that do not
@ -146,10 +144,22 @@ interface nsIMemoryReporter : nsISupports
* - OTHER: reporters which don't fit into either of these categories. Such
* reporters must have a path that does not start with "explicit/" or
* "smaps/" and may have any units.
*
* - SUMMARY: reporters which report data that's available in a more
* detailed form via other reporters. These reporters are sometimes
* useful for efficiency purposes -- for example, a KIND_SUMMARY reporter
* might list all the JS compartments without the overhead of the full JS
* memory reporter, which walks the JS heap.
*
* Unlike other reporters, SUMMARY reporters may have empty descriptions.
*
* SUMMARY reporters must not have a path starting with "explicit/" or
* "smaps/".
*/
const PRInt32 KIND_NONHEAP = 0;
const PRInt32 KIND_HEAP = 1;
const PRInt32 KIND_OTHER = 2;
const PRInt32 KIND_SUMMARY = 3;
/*
* KIND_MAPPED is a deprecated synonym for KIND_NONHEAP. We keep it around