Back out bug 738011, bug 738624, and bug 737857 because of WinXP debug reftest log errors

--HG--
extra : rebase_source : 965c755f6416e2ca102b38633fe7a2c7267e956e
This commit is contained in:
Matt Brubeck 2012-04-02 12:44:31 -07:00
parent c7294996b9
commit 6b29960c06
9 changed files with 118 additions and 793 deletions

View File

@ -37,57 +37,26 @@
#include "nsWindowMemoryReporter.h"
#include "nsGlobalWindow.h"
#include "nsIEffectiveTLDService.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
using namespace mozilla;
nsWindowMemoryReporter::nsWindowMemoryReporter()
: mCheckForGhostWindowsCallbackPending(false)
{
mDetachedWindows.Init();
}
NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryMultiReporter, nsIObserver,
nsSupportsWeakReference)
NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter, nsIMemoryMultiReporter)
/* static */
void
nsWindowMemoryReporter::Init()
{
// 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(windowReporter, DOM_WINDOW_DESTROYED_TOPIC,
/* weakRef = */ true);
os->AddObserver(windowReporter, "after-minimize-memory-usage",
/* weakRef = */ true);
}
GhostURLsReporter *ghostMultiReporter =
new GhostURLsReporter(windowReporter);
NS_RegisterMemoryMultiReporter(ghostMultiReporter);
NumGhostsReporter *ghostReporter =
new NumGhostsReporter(windowReporter);
NS_RegisterMemoryReporter(ghostReporter);
// The memory reporter manager is going to own this object.
NS_RegisterMemoryMultiReporter(new nsWindowMemoryReporter());
}
static already_AddRefed<nsIURI>
GetWindowURI(nsIDOMWindow *aWindow)
static void
AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr)
{
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(aWindow);
NS_ENSURE_TRUE(pWindow, NULL);
nsCOMPtr<nsIDocument> doc = do_QueryInterface(pWindow->GetExtantDocument());
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aWindow->GetExtantDocument());
nsCOMPtr<nsIURI> uri;
if (doc) {
@ -95,25 +64,13 @@ GetWindowURI(nsIDOMWindow *aWindow)
}
if (!uri) {
nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal =
do_QueryInterface(aWindow);
NS_ENSURE_TRUE(scriptObjPrincipal, NULL);
nsIPrincipal *principal = scriptObjPrincipal->GetPrincipal();
nsIPrincipal *principal = aWindow->GetPrincipal();
if (principal) {
principal->GetURI(getter_AddRefs(uri));
}
}
return uri.forget();
}
static void
AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr)
{
nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow);
if (uri) {
nsCString spec;
uri->GetSpec(spec);
@ -125,8 +82,6 @@ AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr)
aStr += spec;
} else {
// If we're unable to find a URI, we're dealing with a chrome window with
// no document in it (or somesuch), so we call this a "system window".
aStr += NS_LITERAL_CSTRING("[system]");
}
}
@ -136,31 +91,68 @@ NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(DOMStyleMallocSizeOf, "windows")
static nsresult
CollectWindowReports(nsGlobalWindow *aWindow,
nsWindowSizes *aWindowTotalSizes,
nsTHashtable<nsUint64HashKey> *aGhostWindowIDs,
nsIMemoryMultiReporterCallback *aCb,
nsISupports *aClosure)
{
// DOM window objects fall into one of three categories:
// - "active" windows are currently either displayed in an active
// tab, or a child of such a window.
// - "cached" windows are in the fastback cache.
// - "other" windows are closed (or navigated away from w/o being
// cached) yet held alive by either a website or our code. The
// latter case may be a memory leak, but not necessarily.
//
// For each window we show how much memory the window and its
// document, etc, use, and we report those per URI, where the URI is
// the document URI, if available, or the codebase of the principal in
// the window. In the case where we're unable to find a URI we're
// dealing with a chrome window with no document in it (or somesuch),
// and for that we make the URI be the string "[system]".
//
// Outer windows are lumped in with inner windows, because the amount
// of memory used by outer windows is small.
//
// The path we give to the reporter callback for "active" and "cached"
// windows (both inner and outer) is as follows:
//
// explicit/window-objects/top(<top-outer-uri>, id=<top-outer-id>)/<category>/window(<window-uri>)/...
//
// The path we give for "other" windows is as follows:
//
// explicit/window-objects/top(none)/window(<window-uri>)/...
//
// Where:
// - <category> is "active" or "cached", as described above.
// - <top-outer-id> is the window id (nsPIDOMWindow::WindowID()) of
// the top outer window (i.e. tab, or top level chrome window).
// - <top-inner-uri> is the URI of the top outer window. Excepting
// special windows (such as browser.xul or hiddenWindow.html) it's
// what the address bar shows for the tab.
// - <window-uri> is the URI of aWindow.
//
// Exposing the top-outer-id ensures that each tab gets its own
// sub-tree, even if multiple tabs are showing the same URI.
nsCAutoString windowPath("explicit/window-objects/");
// Our window should have a null top iff it has a null docshell.
MOZ_ASSERT(!!aWindow->GetTop() == !!aWindow->GetDocShell());
nsGlobalWindow *top = aWindow->GetTop();
windowPath += NS_LITERAL_CSTRING("top(");
if (top) {
windowPath += NS_LITERAL_CSTRING("top(");
AppendWindowURI(top, windowPath);
windowPath += NS_LITERAL_CSTRING(", id=");
windowPath.AppendInt(top->WindowID());
windowPath += NS_LITERAL_CSTRING(")/");
} else {
windowPath += NS_LITERAL_CSTRING("none");
}
windowPath += NS_LITERAL_CSTRING(")/");
nsIDocShell *docShell = aWindow->GetDocShell();
if (docShell) {
MOZ_ASSERT(top, "'cached' or 'active' window lacks a top window");
windowPath += aWindow->IsFrozen() ? NS_LITERAL_CSTRING("cached/")
: NS_LITERAL_CSTRING("active/");
} else {
if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
windowPath += NS_LITERAL_CSTRING("top(none)/ghost/");
} else {
windowPath += NS_LITERAL_CSTRING("top(none)/detached/");
}
MOZ_ASSERT(!top, "'other' window has a top window");
}
windowPath += NS_LITERAL_CSTRING("window(");
@ -241,20 +233,12 @@ nsWindowMemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb,
WindowArray windows;
windowsById->Enumerate(GetWindows, &windows);
// Get the IDs of all the "ghost" windows.
nsTHashtable<nsUint64HashKey> ghostWindows;
ghostWindows.Init();
CheckForGhostWindows(&ghostWindows);
nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(
NS_EFFECTIVETLDSERVICE_CONTRACTID);
NS_ENSURE_STATE(tldService);
// Collect window memory usage.
nsRefPtr<nsGlobalWindow> *w = windows.Elements();
nsRefPtr<nsGlobalWindow> *end = w + windows.Length();
nsWindowSizes windowTotalSizes(NULL);
for (PRUint32 i = 0; i < windows.Length(); i++) {
nsresult rv = CollectWindowReports(windows[i], &windowTotalSizes,
&ghostWindows, aCb, aClosure);
for (; w != end; ++w) {
nsresult rv = CollectWindowReports(*w, &windowTotalSizes, aCb, aClosure);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -302,391 +286,4 @@ 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)
{
if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) {
ObserveDOMWindowDetached(aSubject);
} else if (!strcmp(aTopic, "after-minimize-memory-usage")) {
ObserveAfterMinimizeMemoryUsage();
} else {
MOZ_ASSERT(false);
}
return NS_OK;
}
void
nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports* aWindow)
{
nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
if (!weakWindow) {
NS_WARNING("Couldn't take weak reference to a window?");
return;
}
mDetachedWindows.Put(weakWindow, TimeStamp());
if (!mCheckForGhostWindowsCallbackPending) {
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this,
&nsWindowMemoryReporter::CheckForGhostWindowsCallback);
NS_DispatchToCurrentThread(runnable);
mCheckForGhostWindowsCallbackPending = true;
}
}
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
nsWindowMemoryReporter::CheckForGhostWindowsCallback()
{
mCheckForGhostWindowsCallbackPending = false;
CheckForGhostWindows();
}
struct CheckForGhostWindowsEnumeratorData
{
nsTHashtable<nsCStringHashKey> *nonDetachedDomains;
nsTHashtable<nsUint64HashKey> *ghostWindowIDs;
nsIEffectiveTLDService *tldService;
PRUint32 ghostTimeout;
TimeStamp now;
};
static PLDHashOperator
CheckForGhostWindowsEnumerator(nsISupports *aKey, TimeStamp& aTimeStamp,
void* aClosure)
{
CheckForGhostWindowsEnumeratorData *data =
static_cast<CheckForGhostWindowsEnumeratorData*>(aClosure);
nsWeakPtr weakKey = do_QueryInterface(aKey);
nsCOMPtr<nsIDOMWindow> window = do_QueryReferent(weakKey);
if (!window) {
// The window object has been destroyed. Stop tracking its weak ref in our
// hashtable.
return PL_DHASH_REMOVE;
}
nsCOMPtr<nsIDOMWindow> top;
window->GetTop(getter_AddRefs(top));
if (top) {
// The window is no longer detached, so we no longer want to track it.
return PL_DHASH_REMOVE;
}
nsCOMPtr<nsIURI> uri = GetWindowURI(window);
nsCAutoString domain;
if (uri) {
// GetBaseDomain works fine if |uri| is null, but it outputs a warning
// which ends up overrunning the mochitest logs.
data->tldService->GetBaseDomain(uri, 0, domain);
}
if (data->nonDetachedDomains->Contains(domain)) {
// This window shares a domain with a non-detached window, so reset its
// clock.
aTimeStamp = TimeStamp();
} else {
// This window does not share a domain with a non-detached window, so it
// meets ghost criterion (2).
if (aTimeStamp.IsNull()) {
// This may become a ghost window later; start its clock.
aTimeStamp = data->now;
} else if ((data->now - aTimeStamp).ToSeconds() > data->ghostTimeout) {
// This definitely is a ghost window, so add it to ghostWindowIDs, if
// that is not null.
if (data->ghostWindowIDs) {
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(window);
if (pWindow) {
data->ghostWindowIDs->PutEntry(pWindow->WindowID());
}
}
}
}
return PL_DHASH_NEXT;
}
struct GetNonDetachedWindowDomainsEnumeratorData
{
nsTHashtable<nsCStringHashKey> *nonDetachedDomains;
nsIEffectiveTLDService *tldService;
};
static PLDHashOperator
GetNonDetachedWindowDomainsEnumerator(const PRUint64& aId, nsGlobalWindow* aWindow,
void* aClosure)
{
GetNonDetachedWindowDomainsEnumeratorData *data =
static_cast<GetNonDetachedWindowDomainsEnumeratorData*>(aClosure);
if (!aWindow->GetTop()) {
// This window is detached, so we don't care about its domain.
return PL_DHASH_NEXT;
}
nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow);
nsCAutoString domain;
if (uri) {
data->tldService->GetBaseDomain(uri, 0, domain);
}
data->nonDetachedDomains->PutEntry(domain);
return PL_DHASH_NEXT;
}
/**
* Iterate over mDetachedWindows and update it to reflect the current state of
* the world. In particular:
*
* - Remove weak refs to windows which no longer exist.
*
* - Remove references to windows which are no longer detached.
*
* - Reset the timestamp on detached windows which share a domain with a
* non-detached window (they no longer meet ghost criterion (2)).
*
* - If a window now meets ghost criterion (2) but didn't before, set its
* timestamp to now.
*
* Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
* all ghost windows we found.
*/
void
nsWindowMemoryReporter::CheckForGhostWindows(
nsTHashtable<nsUint64HashKey> *aOutGhostIDs /* = NULL */)
{
nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(
NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (!tldService) {
NS_WARNING("Couldn't get TLDService.");
return;
}
nsGlobalWindow::WindowByIdTable *windowsById =
nsGlobalWindow::GetWindowsTable();
if (!windowsById) {
NS_WARNING("GetWindowsTable returned null");
return;
}
nsTHashtable<nsCStringHashKey> nonDetachedWindowDomains;
nonDetachedWindowDomains.Init();
// Populate nonDetachedWindowDomains.
GetNonDetachedWindowDomainsEnumeratorData nonDetachedEnumData =
{ &nonDetachedWindowDomains, tldService };
windowsById->EnumerateRead(GetNonDetachedWindowDomainsEnumerator,
&nonDetachedEnumData);
// Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
// if it's not null.
CheckForGhostWindowsEnumeratorData ghostEnumData =
{ &nonDetachedWindowDomains, aOutGhostIDs, tldService,
GetGhostTimeout(), TimeStamp::Now() };
mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator,
&ghostEnumData);
}
NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::GhostURLsReporter,
nsIMemoryMultiReporter)
nsWindowMemoryReporter::
GhostURLsReporter::GhostURLsReporter(
nsWindowMemoryReporter* aWindowReporter)
: mWindowReporter(aWindowReporter)
{
}
NS_IMETHODIMP
nsWindowMemoryReporter::
GhostURLsReporter::GetName(nsACString& aName)
{
aName.AssignLiteral("ghost-windows");
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
GhostURLsReporter::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::
GhostURLsReporter::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;
}
NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::NumGhostsReporter,
nsIMemoryReporter)
nsWindowMemoryReporter::
NumGhostsReporter::NumGhostsReporter(
nsWindowMemoryReporter *aWindowReporter)
: mWindowReporter(aWindowReporter)
{}
NS_IMETHODIMP
nsWindowMemoryReporter::
NumGhostsReporter::GetProcess(nsACString& aProcess)
{
aProcess.AssignLiteral("");
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
NumGhostsReporter::GetPath(nsACString& aPath)
{
aPath.AssignLiteral("ghost-windows");
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
NumGhostsReporter::GetKind(PRInt32* aKind)
{
*aKind = KIND_OTHER;
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
NumGhostsReporter::GetUnits(PRInt32* aUnits)
{
*aUnits = nsIMemoryReporter::UNITS_COUNT;
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
NumGhostsReporter::GetDescription(nsACString& aDesc)
{
nsPrintfCString str(1024,
"The number of ghost windows present (the number of nodes underneath \
explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost \
window is not shown in any tab, does not share a domain with any non-detached \
windows, and has met these criteria for at least %ds \
(memory.ghost_window_timeout_seconds) or has survived a round of about:memory's \
minimize memory usage button.\n\n\
Ghost windows can happen legitimately, but they are often indicative of leaks \
in the browser or add-ons.",
mWindowReporter->GetGhostTimeout());
aDesc.Assign(str);
return NS_OK;
}
NS_IMETHODIMP
nsWindowMemoryReporter::
NumGhostsReporter::GetAmount(PRInt64* aAmount)
{
nsTHashtable<nsUint64HashKey> ghostWindows;
ghostWindows.Init();
mWindowReporter->CheckForGhostWindows(&ghostWindows);
*aAmount = ghostWindows.Count();
return NS_OK;
}

View File

@ -39,11 +39,6 @@
#define nsWindowMemoryReporter_h__
#include "nsIMemoryReporter.h"
#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
// that it needs to measure; any sub-class that doesn't use it will inherit
@ -54,176 +49,29 @@
class nsWindowSizes {
public:
nsWindowSizes(nsMallocSizeOfFun aMallocSizeOf) {
memset(this, 0, sizeof(nsWindowSizes));
mMallocSizeOf = aMallocSizeOf;
}
nsMallocSizeOfFun mMallocSizeOf;
size_t mDOM;
size_t mStyleSheets;
size_t mLayoutArenas;
size_t mLayoutStyleSets;
size_t mLayoutTextRuns;
nsWindowSizes(nsMallocSizeOfFun aMallocSizeOf) {
memset(this, 0, sizeof(nsWindowSizes));
mMallocSizeOf = aMallocSizeOf;
}
nsMallocSizeOfFun mMallocSizeOf;
size_t mDOM;
size_t mStyleSheets;
size_t mLayoutArenas;
size_t mLayoutStyleSets;
size_t mLayoutTextRuns;
};
/**
* nsWindowMemoryReporter is responsible for the 'explicit/window-objects'
* memory reporter.
*
* We classify DOM window objects into one of three categories:
*
* - "active" windows, which are displayed in a tab (as the top-level window
* or an iframe),
*
* - "cached" windows, which are in the fastback cache (aka the bfcache), and
*
* - "detached" windows, which have a null docshell. A window becomes
* detached when its <iframe> or tab containing the window is destroyed --
* i.e., when the window is no longer active or cached.
*
* Additionally, we classify a subset of detached windows as "ghost" windows.
* Although ghost windows can happen legitimately (a page can hold a reference
* to a cross-domain window and then close its container), the presence of
* ghost windows is often indicative of a memory leak.
*
* A window is a ghost if it meets the following three criteria:
*
* 1) The window is detached.
*
* 2) There exist no non-detached windows with the same base domain as
* the window's principal. (For example, the base domain of
* "wiki.mozilla.co.uk" is "mozilla.co.uk".) This criterion makes us less
* likely to flag a legitimately held-alive detached window as a ghost.
*
* 3) The window has met criteria (1) and (2) above for at least
* memory.ghost_window_timeout_seconds. This criterion is in place so we
* don't immediately declare a window a ghost before the GC/CC has had a
* chance to run.
*
* nsWindowMemoryReporter observes window detachment and uses mDetachedWindows
* to remember when a window first met criteria (1) and (2). When we generate
* a memory report, we use this accounting to determine which windows are
* ghosts.
*
*
* We use the following memory reporter path for active and cached windows:
*
* explicit/window-objects/top(<top-outer-uri>, id=<top-outer-id>)/<category>/window(<window-uri>)/...
*
* For detached and ghost windows, we use
*
* explicit/window-objects/top(none)/<category>/window(<window-uri>)/...
*
* Where
*
* - <category> is "active", "cached", "detached", or "ghost", as described
* above.
*
* - <top-outer-id> is the window id of the top outer window (i.e. the tab, or
* the top level chrome window). Exposing this ensures that each tab gets
* its own sub-tree, even if multiple tabs are showing the same URI.
*
* - <top-uri> is the URI of the top window. Excepting special windows (such
* as browser.xul or hiddenWindow.html) it's what the address bar shows for
* the tab.
*
*/
class nsWindowMemoryReporter: public nsIMemoryMultiReporter,
public nsIObserver,
public nsSupportsWeakReference
class nsWindowMemoryReporter: public nsIMemoryMultiReporter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYMULTIREPORTER
NS_DECL_NSIOBSERVER
static void Init();
private:
/**
* GhostURLsReporter generates the "ghost-windows" multi-report, which
* includes a list of all ghost windows' URLs. If you're only interested in
* this list, running this report is faster than running
* nsWindowMemoryReporter.
*/
class GhostURLsReporter: public nsIMemoryMultiReporter
{
public:
GhostURLsReporter(nsWindowMemoryReporter* aWindowReporter);
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYMULTIREPORTER
private:
nsRefPtr<nsWindowMemoryReporter> mWindowReporter;
};
/**
* nsGhostWindowReporter generates the "ghost-windows" single-report, which
* counts the number of ghost windows present.
*/
class NumGhostsReporter: public nsIMemoryReporter
{
public:
NumGhostsReporter(nsWindowMemoryReporter* aWindowReporter);
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
private:
nsRefPtr<nsWindowMemoryReporter> mWindowReporter;
};
// Protect ctor, use Init() instead.
nsWindowMemoryReporter();
/**
* Get the number of seconds for which a window must satisfy ghost criteria
* (1) and (2) before we deem that it satisfies criterion (3).
*/
PRUint32 GetGhostTimeout();
void ObserveDOMWindowDetached(nsISupports* aWindow);
void ObserveAfterMinimizeMemoryUsage();
/**
* When we observe a DOM window being detached, we enqueue an asynchronous
* event which calls this method. This method then calls
* CheckForGhostWindows.
*/
void CheckForGhostWindowsCallback();
/**
* Iterate over all weak window pointers in mDetachedWindows and update our
* accounting of which windows meet ghost criterion (2).
*
* This method also cleans up mDetachedWindows, removing entries for windows
* which have been destroyed or are no longer detached.
*
* If aOutGhostIDs is non-null, we populate it with the Window IDs of the
* ghost windows.
*
* This is called asynchronously after we observe a DOM window being detached
* from its docshell, and also right before we generate a memory report.
*/
void CheckForGhostWindows(nsTHashtable<nsUint64HashKey> *aOutGhostIDs = NULL);
/**
* Maps a weak reference to a detached window (nsIWeakReference) to the time
* when we observed that the window met ghost criterion (2) above.
*
* If the window has not yet met criterion (2) it maps to the null timestamp.
*
* (Although windows are not added to this table until they're detached, it's
* possible for a detached window to become non-detached, and we won't
* remove it from the table until CheckForGhostWindows runs.)
*/
nsDataHashtable<nsISupportsHashKey, mozilla::TimeStamp> mDetachedWindows;
/**
* True if we have an asynchronous call to CheckForGhostWindows pending.
*/
bool mCheckForGhostWindowsCallbackPending;
};
#endif // nsWindowMemoryReporter_h__

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_SUMMARY,
nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::UNITS_COUNT,
1, "");

View File

@ -3524,7 +3524,6 @@ pref("profiler.entries", 100000);
// Network API
pref("dom.network.enabled", true);
pref("dom.network.metered", false);
#ifdef XP_WIN
// On 32-bit Windows, fire a low-memory notification if we have less than this
// many mb of virtual address space available.
@ -3543,8 +3542,3 @@ pref("memory.low_physical_memory_threshold_mb", 0);
// low_memory_notification_interval_ms.
pref("memory.low_memory_notification_interval_ms", 10000);
#endif
// How long must we wait before declaring that a window is a "ghost" (i.e., a
// likely leak)? This should be longer than it usually takes for an eligible
// window to be collected via the GC/CC.
pref("memory.ghost_window_timeout_seconds", 60);

View File

@ -51,7 +51,6 @@ 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;
@ -164,12 +163,10 @@ function minimizeMemoryUsage3x(fAfter)
.getService(Ci.nsIObserverService);
os.notifyObservers(null, "memory-pressure", "heap-minimize");
if (++i < 3) {
if (++i < 3)
runSoon(sendHeapMinNotificationsInner);
} else {
os.notifyObservers(null, "after-minimize-memory-usage", "about:memory");
else
runSoon(fAfter);
}
}
sendHeapMinNotificationsInner();
@ -263,7 +260,7 @@ function processMemoryReporters(aMgr, aIgnoreSingle, aIgnoreMulti,
// This regexp matches sentences and sentence fragments, i.e. strings that
// start with a capital letter and ends with a '.'. (The final sentence may be
// in parentheses, so a ')' might appear after the '.'.)
const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
const gSentenceRegExp = /^[A-Z].*\.\)?$/;
function checkReport(aUnsafePath, aKind, aUnits, aAmount, aDescription)
{
@ -278,16 +275,17 @@ function checkReport(aUnsafePath, aKind, aUnits, aAmount, aDescription)
assert(aUnits === UNITS_BYTES, "bad smaps units");
assert(aDescription !== "", "empty smaps description");
} else if (aKind === KIND_SUMMARY) {
assert(!aUnsafePath.startsWith("explicit/") &&
!aUnsafePath.startsWith("smaps/"),
"bad SUMMARY path");
} 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 {
assert(aUnsafePath.indexOf("/") === -1, "'other' path contains '/'");
assert(aKind === KIND_OTHER, "bad other kind: " + aUnsafePath);
assert(aDescription.match(gSentenceRegExp),
"non-sentence other description " + aDescription);
"non-sentence other description");
}
}
@ -539,22 +537,20 @@ Report.prototype = {
function getReportsByProcess(aMgr)
{
// Ignore the "smaps" multi-reporter in non-verbose mode, and the
// "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.)
// "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.)
function ignoreSingle(aPath)
{
return (aPath.startsWith("smaps/") && !gVerbose) ||
aPath.startsWith("compartments/") ||
aPath.startsWith("ghost-windows/");
(aPath.startsWith("compartments/"))
}
function ignoreMulti(aName)
{
return (aName === "smaps" && !gVerbose) ||
aName === "compartments" ||
aName === "ghost-windows";
return ((aName === "smaps" && !gVerbose) ||
(aName === "compartments"));
}
let reportsByProcess = {};
@ -1556,21 +1552,15 @@ 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.
handleProcess('Main');
let compartmentsByProcess = getCompartmentsByProcess(mgr);
appendProcessCompartmentsElements(body, "Main",
compartmentsByProcess["Main"]);
for (let process in compartmentsByProcess) {
if (process !== "Main") {
handleProcess(process);
appendProcessCompartmentsElements(body, process,
compartmentsByProcess[process]);
}
}
@ -1666,89 +1656,30 @@ function getCompartmentsByProcess(aMgr)
return compartmentsByProcess;
}
function GhostWindow(aUnsafeURL)
{
// Call it _unsafeName rather than _unsafeURL for symmetry with the
// Compartment object.
this._unsafeName = aUnsafeURL;
// 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;
}
}
processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
return ghostWindowsByProcess;
}
//---------------------------------------------------------------------------
function appendProcessAboutCompartmentsElementsHelper(aP, aEntries, aKindString)
function appendProcessCompartmentsElementsHelper(aP, aCompartments, 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");
appendElementWithText(aP, "h2", "", aKindString + " Compartments\n");
let compartmentTextArray = [];
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 + ']';
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);
}
line += '\n';
lines.push(line);
}
lines.sort();
compartmentTextArray.sort();
for (let i = 0; i < lines.length; i++) {
appendElementWithText(uPre, "span", "", lines[i]);
for (let i = 0; i < compartmentTextArray.length; i++) {
appendElementWithText(uPre, "span", "", compartmentTextArray[i]);
}
appendTextNode(aP, "\n"); // gives nice spacing when we cut and paste
@ -1763,30 +1694,14 @@ function appendProcessAboutCompartmentsElementsHelper(aP, aEntries, 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 appendProcessAboutCompartmentsElements(aP, aProcess, aCompartments, aGhostWindows)
function appendProcessCompartmentsElements(aP, aProcess, aCompartments)
{
appendElementWithText(aP, "h1", "", aProcess + " Process");
appendTextNode(aP, "\n\n"); // gives nice spacing when we cut and paste
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");
appendProcessCompartmentsElementsHelper(aP, aCompartments, "User");
appendProcessCompartmentsElementsHelper(aP, aCompartments, "System");
}

View File

@ -47,7 +47,6 @@
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;
@ -72,8 +71,8 @@
f("", "other2", OTHER, COUNT, 888),
f("2nd", "explicit/c", HEAP, BYTES, 333 * MB),
f("2nd", "compartments/user/child-user-compartment", SUMMARY, COUNT, 1),
f("2nd", "compartments/system/child-system-compartment", SUMMARY, COUNT, 1)
f("2nd", "compartments/user/child-user-compartment", OTHER, COUNT, 1),
f("2nd", "compartments/system/child-system-compartment", OTHER, COUNT, 1)
];
var fakeMultiReporters = [
@ -91,7 +90,7 @@
{ name: "compartments",
collectReports: function(aCbObj, aClosure) {
function f(aP) {
aCbObj.callback("", aP, SUMMARY, COUNT, 1, "", aClosure);
aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
}
f("compartments/user/http:\\\\foo.com\\");
f("compartments/user/https:\\\\bar.com\\bar?baz");
@ -107,16 +106,6 @@
},
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) {
@ -160,10 +149,6 @@ 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\
@ -172,8 +157,6 @@ 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

@ -102,7 +102,6 @@ HISTOGRAM(MEMORY_STORAGE_SQLITE, 1024, 512 * 1024, 50, EXPONENTIAL, "Memory used
HISTOGRAM(MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED, 1024, 1024 * 1024, 50, EXPONENTIAL, "Memory used for uncompressed, in-use content images (KB)")
HISTOGRAM(MEMORY_HEAP_ALLOCATED, 1024, 1024 * 1024, 50, EXPONENTIAL, "Heap memory allocated (KB)")
HISTOGRAM(MEMORY_EXPLICIT, 1024, 1024 * 1024, 50, EXPONENTIAL, "Explicit memory allocations (KB)")
HISTOGRAM(GHOST_WINDOWS, 1, 128, 8, EXPONENTIAL, "Number of ghost windows")
#if defined(XP_MACOSX)
HISTOGRAM(MEMORY_FREE_PURGED_PAGES_MS, 1, 1024, 10, EXPONENTIAL, "Time(ms) to purge MADV_FREE'd heap pages.")
#elif defined(XP_WIN)

View File

@ -68,8 +68,7 @@ const MEM_HISTOGRAMS = {
"page-faults-hard": "PAGE_FAULTS_HARD",
"low-memory-events-virtual": "LOW_MEMORY_EVENTS_VIRTUAL",
"low-memory-events-commit-space": "LOW_MEMORY_EVENTS_COMMIT_SPACE",
"low-memory-events-physical": "LOW_MEMORY_EVENTS_PHYSICAL",
"ghost-windows": "GHOST_WINDOWS"
"low-memory-events-physical": "LOW_MEMORY_EVENTS_PHYSICAL"
};
// Seconds of idle time before pinging.
// On idle-daily a gather-telemetry notification is fired, during it probes can

View File

@ -117,8 +117,10 @@ interface nsIMemoryReporter : nsISupports
* Reporters in this category must have kind NONHEAP, units BYTES, and
* a non-empty description.
*
* - Reporters with kind SUMMARY may have any path which doesn't start with
* "explicit/" or "smaps/".
* - 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.
*
* - All other paths represent cross-cutting values and may overlap with any
* other reporter. Reporters in this category must have paths that do not
@ -144,22 +146,10 @@ 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