Bug 768470 - Add ability to import/export memory reports as JSON. r=jlebar.

--HG--
extra : rebase_source : 8e03a7e8c82204ea8e353a9ba583c4d53917a267
This commit is contained in:
Nicholas Nethercote 2012-08-27 18:14:14 -07:00
parent 9589f29bae
commit 584b3df110
13 changed files with 754 additions and 138 deletions

View File

@ -20,6 +20,10 @@ h2 {
}
.accuracyWarning {
color: #d22;
}
.badInputWarning {
color: #f00;
}

View File

@ -23,6 +23,11 @@ const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
let gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
let gUnnamedProcessStr = "Main Process";
// Because about:memory and about:compartments are non-standard URLs,
// location.search is undefined, so we have to use location.href here.
// The toLowerCase() calls ensure that addresses like "ABOUT:MEMORY" work.
@ -54,6 +59,8 @@ function flipBackslashes(aUnsafeStr)
const gAssertionFailureMsgPrefix = "aboutMemory.js assertion failed: ";
// This is used for things that should never fail, and indicate a defect in
// this file if they do.
function assert(aCond, aMsg)
{
if (!aCond) {
@ -62,6 +69,24 @@ function assert(aCond, aMsg)
}
}
// This is used for malformed input from memory reporters.
function assertInput(aCond, aMsg)
{
if (!aCond) {
throw "Invalid memory report(s): " + aMsg;
}
}
function handleException(ex)
{
let str = ex.toString();
if (str.startsWith(gAssertionFailureMsgPrefix)) {
throw ex; // Argh, assertion failure within this file! Give up.
} else {
badInput(ex); // File or memory reporter problem. Print a message.
}
}
function reportAssertionFailure(aMsg)
{
let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
@ -75,6 +100,11 @@ function debug(x)
appendElementWithText(document.body, "div", "debug", JSON.stringify(x));
}
function badInput(x)
{
appendElementWithText(document.body, "div", "badInputWarning", x);
}
//---------------------------------------------------------------------------
function addChildObserversAndUpdate(aUpdateFn)
@ -103,7 +133,7 @@ function onLoad()
function onUnload()
{
// We need to check if the observer has been added before removing; in some
// circumstances (eg. reloading the page quickly) it might not have because
// circumstances (e.g. reloading the page quickly) it might not have because
// onLoadAbout{Memory,Compartments} might not fire.
if (gChildMemoryListener) {
let os = Cc["@mozilla.org/observer-service;1"].
@ -149,8 +179,6 @@ function minimizeMemoryUsage3x(fAfter)
/**
* Iterates over each reporter and multi-reporter.
*
* @param aMgr
* The memory reporter manager.
* @param aIgnoreSingle
* Function that indicates if we should skip a single reporter, based
* on its path.
@ -160,8 +188,7 @@ function minimizeMemoryUsage3x(fAfter)
* @param aHandleReport
* The function that's called for each report.
*/
function processMemoryReporters(aMgr, aIgnoreSingle, aIgnoreMulti,
aHandleReport)
function processMemoryReporters(aIgnoreSingle, aIgnoreMulti, aHandleReport)
{
// Process each memory reporter with aHandleReport.
//
@ -173,47 +200,45 @@ function processMemoryReporters(aMgr, aIgnoreSingle, aIgnoreMulti,
//
// - After this point we never use the original memory report again.
let e = aMgr.enumerateReporters();
let e = gMgr.enumerateReporters();
while (e.hasMoreElements()) {
let rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
let unsafePath;
try {
unsafePath = rOrig.path;
if (!aIgnoreSingle(unsafePath)) {
aHandleReport(rOrig.process, unsafePath, rOrig.kind, rOrig.units,
rOrig.amount, rOrig.description);
}
}
catch (ex) {
debug("Exception thrown by memory reporter: " + unsafePath + ": " + ex);
let unsafePath = rOrig.path;
if (!aIgnoreSingle(unsafePath)) {
aHandleReport(rOrig.process, unsafePath, rOrig.kind, rOrig.units,
rOrig.amount, rOrig.description);
}
}
let e = aMgr.enumerateMultiReporters();
let e = gMgr.enumerateMultiReporters();
while (e.hasMoreElements()) {
let mr = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
let name = mr.name;
try {
if (!aIgnoreMulti(name)) {
mr.collectReports(aHandleReport, null);
}
if (!aIgnoreMulti(mr.name)) {
mr.collectReports(aHandleReport, null);
}
catch (ex) {
// There are two exception cases that must be distinguished here.
//
// - We want to halt proceedings on exceptions thrown within this file
// (i.e. assertion failures in aHandleReport); such exceptions contain
// gAssertionFailureMsgPrefix in their string representation.
//
// - We want to continue on when faced with exceptions thrown outside
// this file (i.e. when measuring an amount in collectReports).
let str = ex.toString();
if (str.search(gAssertionFailureMsgPrefix) >= 0) {
throw(ex);
} else {
debug("Exception thrown within memory multi-reporter: " + name + ": " +
ex);
}
}
}
/**
* Iterates over each report.
*
* @param aReports
* Array of reports, read from file.
* @param aIgnoreSingle
* Function that indicates if we should skip a single reporter, based
* on its path.
* @param aHandleReport
* The function that's called for each report.
*/
function processMemoryReportsFromFile(aReports, aIgnoreSingle, aHandleReport)
{
// Process each memory reporter with aHandleReport.
for (let i = 0; i < aReports.length; i++) {
let r = aReports[i];
if (!aIgnoreSingle(r.path)) {
aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
r.description);
}
}
}
@ -367,7 +392,8 @@ function doCC()
//---------------------------------------------------------------------------
/**
* Top-level function that does the work of generating the page.
* Top-level function that does the work of generating the page from the memory
* reporters.
*/
function updateAboutMemory()
{
@ -376,30 +402,120 @@ function updateAboutMemory()
// observer.
let body = clearBody();
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
try {
// Process the reports from the memory reporters.
let process = function(aIgnoreSingle, aIgnoreMulti, aHandleReport) {
processMemoryReporters(aIgnoreSingle, aIgnoreMulti, aHandleReport);
}
appendAboutMemoryMain(body, process, gMgr.hasMozMallocUsableSize);
} catch (ex) {
handleException(ex);
} finally {
appendAboutMemoryFooter(body);
}
}
// Increment this if the JSON format changes.
var gCurrentFileFormatVersion = 1;
/**
* Like updateAboutMemory(), but gets its data from file instead of the memory
* reporters.
*
* @param aFile
* The File being read from. Accepted format is described in the
* comment describing nsIMemoryReporterManager::dumpReports.
*/
function updateAboutMemoryFromFile(aFile)
{
// Note: readerOnload is called asynchronously, once FileReader.readAsText()
// completes. Therefore its exception handling has to be distinct from that
// surrounding the |reader.readAsText(aFile)| call.
function readerOnload(aEvent) {
try {
let json = JSON.parse(aEvent.target.result);
assertInput(json.version === gCurrentFileFormatVersion,
"data version number missing or doesn't match");
assertInput(json.hasMozMallocUsableSize !== undefined,
"missing 'hasMozMallocUsableSize' property");
assertInput(json.reports && json.reports instanceof Array,
"missing or non-array 'reports' property");
let process = function(aIgnoreSingle, aIgnoreMulti, aHandleReport) {
processMemoryReportsFromFile(json.reports, aIgnoreSingle,
aHandleReport);
}
appendAboutMemoryMain(body, process, json.hasMozMallocUsableSize);
} catch (ex) {
handleException(ex);
} finally {
appendAboutMemoryFooter(body);
}
};
let body = clearBody();
try {
let reader = new FileReader();
reader.onerror = function(aEvent) { throw "FileReader.onerror"; }
reader.onabort = function(aEvent) { throw "FileReader.onabort"; }
reader.onload = readerOnload;
reader.readAsText(aFile);
} catch (ex) {
handleException(ex);
appendAboutMemoryFooter(body);
}
}
/**
* Processes reports (whether from reporters or from file) and append the main
* part of the page.
*
* @param aBody
* The DOM body element.
* @param aProcess
* Function that extracts the memory reports from the reporters or from
* file.
* @param aHasMozMallocUsableSize
* Boolean indicating if moz_malloc_usable_size works.
*/
function appendAboutMemoryMain(aBody, aProcess, aHasMozMallocUsableSize)
{
let treesByProcess = {}, degeneratesByProcess = {}, heapTotalByProcess = {};
getTreesByProcess(mgr, treesByProcess, degeneratesByProcess,
getTreesByProcess(aProcess, treesByProcess, degeneratesByProcess,
heapTotalByProcess);
// Generate output for one process at a time. Always start with the
// Main process.
let hasMozMallocUsableSize = mgr.hasMozMallocUsableSize;
appendProcessAboutMemoryElements(body, "Main", treesByProcess["Main"],
degeneratesByProcess["Main"],
heapTotalByProcess["Main"],
hasMozMallocUsableSize);
if (treesByProcess[gUnnamedProcessStr]) {
appendProcessAboutMemoryElements(aBody, gUnnamedProcessStr,
treesByProcess[gUnnamedProcessStr],
degeneratesByProcess[gUnnamedProcessStr],
heapTotalByProcess[gUnnamedProcessStr],
aHasMozMallocUsableSize);
}
for (let process in treesByProcess) {
if (process !== "Main") {
appendProcessAboutMemoryElements(body, process, treesByProcess[process],
if (process !== gUnnamedProcessStr) {
appendProcessAboutMemoryElements(aBody, process, treesByProcess[process],
degeneratesByProcess[process],
heapTotalByProcess[process],
hasMozMallocUsableSize);
aHasMozMallocUsableSize);
}
}
}
appendElement(body, "hr");
/**
* Appends the page footer.
*
* @param aBody
* The DOM body element.
*/
function appendAboutMemoryFooter(aBody)
{
appendElement(aBody, "hr");
// Memory-related actions.
const UpDesc = "Re-measure.";
@ -410,10 +526,11 @@ function updateAboutMemory()
"collection followed by a cycle collection, and causes the " +
"process to reduce memory usage in other ways, e.g. by " +
"flushing various caches.";
const RdDesc = "Read memory report data from file.";
function appendButton(aTitle, aOnClick, aText, aId)
function appendButton(aP, aTitle, aOnClick, aText, aId)
{
let b = appendElementWithText(body, "button", "", aText);
let b = appendElementWithText(aP, "button", "", aText);
b.title = aTitle;
b.onclick = aOnClick
if (aId) {
@ -421,24 +538,39 @@ function updateAboutMemory()
}
}
// The "Update" button has an id so it can be clicked in a test.
appendButton(UpDesc, updateAboutMemory, "Update", "updateButton");
appendButton(GCDesc, doGlobalGC, "GC");
appendButton(CCDesc, doCC, "CC");
appendButton(MPDesc, function() { minimizeMemoryUsage3x(updateAboutMemory); },
"Minimize memory usage");
let div1 = appendElement(aBody, "div");
let div1 = appendElement(body, "div");
// The "Update" button has an id so it can be clicked in a test.
appendButton(div1, UpDesc, updateAboutMemory, "Update", "updateButton");
appendButton(div1, GCDesc, doGlobalGC, "GC");
appendButton(div1, CCDesc, doCC, "CC");
appendButton(div1, MPDesc,
function() { minimizeMemoryUsage3x(updateAboutMemory); },
"Minimize memory usage");
// The standard file input element is ugly. So we hide it, and add a button
// that when clicked invokes the input element.
let input = appendElementWithText(div1, "input", "hidden", "input text");
input.type = "file";
input.id = "fileInput"; // has an id so it can be invoked by a test
input.addEventListener("change", function() {
let file = this.files[0];
updateAboutMemoryFromFile(file);
});
appendButton(div1, RdDesc, function() { input.click() },
"Read reports from file", "readReportsButton");
let div2 = appendElement(aBody, "div");
if (gVerbose) {
let a = appendElementWithText(div1, "a", "option", "Less verbose");
let a = appendElementWithText(div2, "a", "option", "Less verbose");
a.href = "about:memory";
} else {
let a = appendElementWithText(div1, "a", "option", "More verbose");
let a = appendElementWithText(div2, "a", "option", "More verbose");
a.href = "about:memory?verbose";
}
let div2 = appendElement(body, "div");
let a = appendElementWithText(div2, "a", "option",
let div3 = appendElement(aBody, "div");
let a = appendElementWithText(div3, "a", "option",
"Troubleshooting information");
a.href = "about:support";
@ -447,8 +579,8 @@ function updateAboutMemory()
let legendText2 = "Hover the pointer over the name of a memory report " +
"to see a description of what it measures.";
appendElementWithText(body, "div", "legend", legendText1);
appendElementWithText(body, "div", "legend", legendText2);
appendElementWithText(aBody, "div", "legend", legendText1);
appendElementWithText(aBody, "div", "legend", legendText2);
}
//---------------------------------------------------------------------------
@ -462,8 +594,9 @@ const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
* This function reads all the memory reports, and puts that data in structures
* that will be used to generate the page.
*
* @param aMgr
* The memory reporter manager.
* @param aProcessMemoryReports
* Function that extracts the memory reports from the reporters or from
* file.
* @param aTreesByProcess
* Table of non-degenerate trees, indexed by process, which this
* function appends to.
@ -474,8 +607,8 @@ const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
* Table of heap total counts, indexed by process, which this function
* appends to.
*/
function getTreesByProcess(aMgr, aTreesByProcess, aDegeneratesByProcess,
aHeapTotalByProcess)
function getTreesByProcess(aProcessMemoryReports, aTreesByProcess,
aDegeneratesByProcess, aHeapTotalByProcess)
{
// Ignore the "smaps" multi-reporter in non-verbose mode, and the
// "compartments" and "ghost-windows" multi-reporters all the time. (Note
@ -500,22 +633,22 @@ function getTreesByProcess(aMgr, aTreesByProcess, aDegeneratesByProcess,
aDescription)
{
if (isExplicitPath(aUnsafePath)) {
assert(aKind === KIND_HEAP || aKind === KIND_NONHEAP, "bad explicit kind");
assert(aUnits === UNITS_BYTES, "bad explicit units");
assert(gSentenceRegExp.test(aDescription),
"non-sentence explicit description");
assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP, "bad explicit kind");
assertInput(aUnits === UNITS_BYTES, "bad explicit units");
assertInput(gSentenceRegExp.test(aDescription),
"non-sentence explicit description");
} else if (isSmapsPath(aUnsafePath)) {
assert(aKind === KIND_NONHEAP, "bad smaps kind");
assert(aUnits === UNITS_BYTES, "bad smaps units");
assert(aDescription !== "", "empty smaps description");
assertInput(aKind === KIND_NONHEAP, "bad smaps kind");
assertInput(aUnits === UNITS_BYTES, "bad smaps units");
assertInput(aDescription !== "", "empty smaps description");
} else {
assert(gSentenceRegExp.test(aDescription),
"non-sentence other description");
assertInput(gSentenceRegExp.test(aDescription),
"non-sentence other description");
}
let process = aProcess === "" ? "Main" : aProcess;
let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
let unsafeNames = aUnsafePath.split('/');
let unsafeName0 = unsafeNames[0];
let isDegenerate = unsafeNames.length === 1;
@ -573,7 +706,7 @@ function getTreesByProcess(aMgr, aTreesByProcess, aDegeneratesByProcess,
}
}
processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
aProcessMemoryReports(ignoreSingle, ignoreMulti, handleReport);
}
//---------------------------------------------------------------------------
@ -623,7 +756,7 @@ TreeNode.prototype = {
case UNITS_COUNT_CUMULATIVE: return formatInt(this._amount);
case UNITS_PERCENTAGE: return formatPercentage(this._amount);
default:
assert(false, "bad units in TreeNode.toString");
assertInput(false, "bad units in TreeNode.toString");
}
}
};
@ -866,7 +999,7 @@ function appendWarningElements(aP, aHasKnownHeapAllocated,
function appendProcessAboutMemoryElements(aP, aProcess, aTrees, aDegenerates,
aHeapTotal, aHasMozMallocUsableSize)
{
appendElementWithText(aP, "h1", "", aProcess + " Process\n\n");
appendElementWithText(aP, "h1", "", aProcess + "\n\n");
// We'll fill this in later.
let warningsDiv = appendElement(aP, "div", "accuracyWarning");
@ -876,9 +1009,10 @@ function appendProcessAboutMemoryElements(aP, aProcess, aTrees, aDegenerates,
{
let treeName = "explicit";
let t = aTrees[treeName];
assert(t, "no explicit tree");
assertInput(t, "no explicit reports");
fillInTree(t);
hasKnownHeapAllocated =
aDegenerates &&
addHeapUnclassifiedNode(t, aDegenerates["heap-allocated"], aHeapTotal);
sortTreeAndInsertAggregateNodes(t._amount, t);
t._description = kTreeDescriptions[treeName];
@ -1253,7 +1387,8 @@ function appendTreeElements(aP, aRoot, aProcess, aPadText)
appendElementWithText(aP, "span", "treeline", treelineText);
// Detect and record invalid values.
assert(aRoot._units === aT._units, "units within a tree are inconsistent");
assertInput(aRoot._units === aT._units,
"units within a tree are inconsistent");
let tIsInvalid = false;
if (!(0 <= aT._amount && aT._amount <= aRoot._amount)) {
tIsInvalid = true;
@ -1378,11 +1513,8 @@ function updateAboutCompartments()
// "child-memory-reporter-update" observer.
let body = clearBody();
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
let compartmentsByProcess = getCompartmentsByProcess(mgr);
let ghostWindowsByProcess = getGhostWindowsByProcess(mgr);
let compartmentsByProcess = getCompartmentsByProcess();
let ghostWindowsByProcess = getGhostWindowsByProcess();
function handleProcess(aProcess) {
appendProcessAboutCompartmentsElements(body, aProcess,
@ -1392,9 +1524,9 @@ function updateAboutCompartments()
// Generate output for one process at a time. Always start with the
// Main process.
handleProcess('Main');
handleProcess(gUnnamedProcessStr);
for (let process in compartmentsByProcess) {
if (process !== "Main") {
if (process !== gUnnamedProcessStr) {
handleProcess(process);
}
}
@ -1427,7 +1559,7 @@ Compartment.prototype = {
}
};
function getCompartmentsByProcess(aMgr)
function getCompartmentsByProcess()
{
// Ignore anything that didn't come from the "compartments" multi-reporter.
// (Note that some such reports can reach here as single reports if they were
@ -1448,7 +1580,7 @@ function getCompartmentsByProcess(aMgr)
function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
aDescription)
{
let process = aProcess === "" ? "Main" : aProcess;
let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
let unsafeNames = aUnsafePath.split('/');
let isSystemCompartment;
if (unsafeNames[0] === "compartments" && unsafeNames[1] == "system" &&
@ -1468,12 +1600,12 @@ function getCompartmentsByProcess(aMgr)
}
} else {
assert(false, "bad compartments path: " + aUnsafePath);
assertInput(false, "bad compartments path: " + aUnsafePath);
}
assert(aKind === KIND_OTHER, "bad compartments kind");
assert(aUnits === UNITS_COUNT, "bad compartments units");
assert(aAmount === 1, "bad compartments amount");
assert(aDescription === "", "bad compartments description");
assertInput(aKind === KIND_OTHER, "bad compartments kind");
assertInput(aUnits === UNITS_COUNT, "bad compartments units");
assertInput(aAmount === 1, "bad compartments amount");
assertInput(aDescription === "", "bad compartments description");
let c = new Compartment(unsafeNames[2], isSystemCompartment);
@ -1491,7 +1623,7 @@ function getCompartmentsByProcess(aMgr)
}
}
processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
processMemoryReporters(ignoreSingle, ignoreMulti, handleReport);
return compartmentsByProcess;
}
@ -1511,7 +1643,7 @@ GhostWindow.prototype = {
}
};
function getGhostWindowsByProcess(aMgr)
function getGhostWindowsByProcess()
{
function ignoreSingle(aUnsafePath)
{
@ -1529,17 +1661,17 @@ function getGhostWindowsByProcess(aMgr)
aDescription)
{
let unsafeSplit = aUnsafePath.split('/');
assert(unsafeSplit[0] === 'ghost-windows' && unsafeSplit.length === 2,
assertInput(unsafeSplit[0] === 'ghost-windows' && unsafeSplit.length === 2,
'Unexpected path in getGhostWindowsByProcess: ' + aUnsafePath);
assert(aKind === KIND_OTHER, "bad ghost-windows kind");
assert(aUnits === UNITS_COUNT, "bad ghost-windows units");
assert(aAmount === 1, "bad ghost-windows amount");
assert(aDescription === "", "bad ghost-windows description");
assertInput(aKind === KIND_OTHER, "bad ghost-windows kind");
assertInput(aUnits === UNITS_COUNT, "bad ghost-windows units");
assertInput(aAmount === 1, "bad ghost-windows amount");
assertInput(aDescription === "", "bad ghost-windows description");
let unsafeURL = unsafeSplit[1];
let ghostWindow = new GhostWindow(unsafeURL);
let process = aProcess === "" ? "Main" : aProcess;
let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
if (!ghostWindowsByProcess[process]) {
ghostWindowsByProcess[process] = {};
}
@ -1552,7 +1684,7 @@ function getGhostWindowsByProcess(aMgr)
}
}
processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
processMemoryReporters(ignoreSingle, ignoreMulti, handleReport);
return ghostWindowsByProcess;
}
@ -1602,7 +1734,7 @@ function appendProcessAboutCompartmentsElementsHelper(aP, aEntries, aKindString)
*/
function appendProcessAboutCompartmentsElements(aP, aProcess, aCompartments, aGhostWindows)
{
appendElementWithText(aP, "h1", "", aProcess + " Process\n\n");
appendElementWithText(aP, "h1", "", aProcess + "\n\n");
let userCompartments = {};
let systemCompartments = {};

View File

@ -12,9 +12,12 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
MOCHITEST_CHROME_FILES = \
memory-reports-good.json \
memory-reports-bad.json \
test_aboutcompartments.xul \
test_aboutmemory.xul \
test_aboutmemory2.xul \
test_aboutmemory3.xul \
test_memoryReporters.xul \
test_sqliteMultiReporter.xul \
$(NULL)

View File

@ -0,0 +1,3 @@
{
"version": 1
}

View File

@ -0,0 +1,14 @@
{
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process":"", "path":"explicit/foo/bar", "kind":1, "units":0,
"amount":2000000, "description":"Foo bar."},
{"process":"", "path":"heap-allocated", "kind":1, "units":0,
"amount":3000000, "description":"Heap allocated."},
{"process":"", "path":"a/b", "kind":1, "units":0,
"amount":10, "description":"A b."},
{"process":"", "path":"a/c", "kind":1, "units":0,
"amount":10, "description":"A c."}
]
}

View File

@ -163,7 +163,7 @@ 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\
2nd\n\
\n\
User Compartments\n\
child-user-compartment\n\

View File

@ -299,7 +299,7 @@ Other Measurements\n\
45.67% ── perc1\n\
100.00% ── perc2\n\
\n\
2nd Process\n\
2nd\n\
\n\
Explicit Allocations\n\
1,000.00 MB (100.0%) -- explicit\n\
@ -314,7 +314,7 @@ Other Measurements\n\
666.00 MB ── other0\n\
111.00 MB ── other1\n\
\n\
3rd Process\n\
3rd\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
\n\
@ -327,7 +327,7 @@ Explicit Allocations\n\
Other Measurements\n\
1.00 MB ── other1\n\
\n\
4th Process\n\
4th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
explicit/js/compartment(http://too-big.com/)/stuff\n\
@ -358,7 +358,7 @@ Other Measurements\n\
-5.55% ── other5 [?!]\n\
666.66% ── other6\n\
\n\
5th Process\n\
5th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
explicit/(3 tiny)/a/neg2\n\
@ -458,7 +458,7 @@ Other Measurements\n\
45.67% ── perc1\n\
100.00% ── perc2\n\
\n\
2nd Process\n\
2nd\n\
\n\
Explicit Allocations\n\
1,048,576,000 B (100.0%) -- explicit\n\
@ -476,7 +476,7 @@ Other Measurements\n\
698,351,616 B ── other0\n\
116,391,936 B ── other1\n\
\n\
3rd Process\n\
3rd\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
\n\
@ -489,7 +489,7 @@ Explicit Allocations\n\
Other Measurements\n\
1,048,576 B ── other1\n\
\n\
4th Process\n\
4th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
explicit/js/compartment(http://too-big.com/)/stuff\n\
@ -518,7 +518,7 @@ Other Measurements\n\
-5.55% ── other5 [?!]\n\
666.66% ── other6\n\
\n\
5th Process\n\
5th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
explicit/a/neg2\n\

View File

@ -405,15 +405,15 @@ Other Measurements\n\
//
let idsToClick = [
{ id: "", expected: startExpected },
{ id: "Main:explicit/a/c", expected: acCollapsedExpected },
{ id: "Main:explicit/a/l", expected: alExpandedExpected },
{ id: "Main:explicit/a", expected: aCollapsedExpected },
{ id: "Main:explicit/h", expected: hCollapsedExpected },
{ id: "Main:explicit/j", expected: jExpandedExpected },
{ id: "Main Process:explicit/a/c", expected: acCollapsedExpected },
{ id: "Main Process:explicit/a/l", expected: alExpandedExpected },
{ id: "Main Process:explicit/a", expected: aCollapsedExpected },
{ id: "Main Process:explicit/h", expected: hCollapsedExpected },
{ id: "Main Process:explicit/j", expected: jExpandedExpected },
{ id: "updateButton", expected: updatedExpected },
{ id: "Main:explicit/a", expected: aExpandedExpected },
{ id: "Main:explicit/a/c", expected: acExpandedExpected },
{ id: "Main:explicit/a/l", expected: alCollapsedExpected }
{ id: "Main Process:explicit/a", expected: aExpandedExpected },
{ id: "Main Process:explicit/a/c", expected: acExpandedExpected },
{ id: "Main Process:explicit/a/l", expected: alCollapsedExpected }
];
SimpleTest.waitForFocus(chain(idsToClick));

View File

@ -0,0 +1,196 @@
<?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 loading of memory reports from file in
about:memory. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Remove all the real reporters and multi-reporters; save them to
// restore at the end.
let e = mgr.enumerateReporters();
let realReporters = [];
while (e.hasMoreElements()) {
let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
mgr.unregisterReporter(r);
realReporters.push(r);
}
e = mgr.enumerateMultiReporters();
let realMultiReporters = [];
while (e.hasMoreElements()) {
let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
mgr.unregisterMultiReporter(r);
realMultiReporters.push(r);
}
// Setup a minimal number of fake reporters.
const KB = 1024;
const MB = KB * KB;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
function f(aPath, aKind, aAmount) {
return {
process: "",
path: aPath,
kind: aKind,
units: BYTES,
description: "Desc.",
amount: aAmount
};
}
let fakeReporters = [
f("heap-allocated", OTHER, 250 * MB),
f("explicit/a/b", HEAP, 50 * MB),
f("other", OTHER, 0.1 * MB),
];
for (let i = 0; i < fakeReporters.length; i++) {
mgr.registerReporter(fakeReporters[i]);
}
]]>
</script>
<iframe id="amGoodFrame" height="400" src="about:memory"></iframe>
<iframe id="amBadFrame" height="400" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
function finish()
{
// Unregister fake reporters and multi-reporters, re-register the real
// reporters and multi-reporters, just in case subsequent tests rely on
// them.
for (let i = 0; i < fakeReporters.length; i++) {
mgr.unregisterReporter(fakeReporters[i]);
}
for (let i = 0; i < realReporters.length; i++) {
mgr.registerReporter(realReporters[i]);
}
for (let i = 0; i < realMultiReporters.length; i++) {
mgr.registerMultiReporter(realMultiReporters[i]);
}
SimpleTest.finish();
}
// Load the given file into the frame, then copy+paste the entire frame and
// check that the cut text matches what we expect.
function test(aFrameId, aFilename, aExpected, aNext) {
let frame = document.getElementById(aFrameId);
frame.focus();
let file = Components.classes["@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);
let input = frame.contentWindow.document.getElementById("fileInput");
input.value = file.path; // this works because it's a chrome test
var e = document.createEvent('Event');
e.initEvent('change', true, true);
input.dispatchEvent(e);
// 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.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
if (actual === aExpected) {
SimpleTest.ok(true, "Clipboard has the expected contents");
aNext();
} else {
numFailures++;
if (numFailures === maxFailures) {
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.
function chain(aFrameIds) {
let x = aFrameIds.shift();
if (x) {
return function() { test(x.frameId, x.filename, x.expected, chain(aFrameIds)); }
} else {
return function() { finish(); };
}
}
// This is pretty simple output, but that's ok; this file is about testing
// the loading of data from file. If we got this far, we're doing fine.
let expectedGood =
"\
Main Process\n\
\n\
Explicit Allocations\n\
2.86 MB (100.0%) -- explicit\n\
├──1.91 MB (66.67%) ── foo/bar\n\
└──0.95 MB (33.33%) ── heap-unclassified\n\
\n\
Other Measurements\n\
0.00 MB (100.0%) -- a\n\
├──0.00 MB (50.00%) ── b\n\
└──0.00 MB (50.00%) ── c\n\
\n\
2.86 MB ── heap-allocated\n\
\n\
";
// This is the output for a malformed data file.
let expectedBad =
"\
Invalid memory report(s): missing 'hasMozMallocUsableSize' property";
let frames = [
{ frameId: "amGoodFrame", filename: "memory-reports-good.json", expected: expectedGood },
{ frameId: "amBadFrame", filename: "memory-reports-bad.json", expected: expectedBad }
];
SimpleTest.waitForFocus(chain(frames));
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View File

@ -48,7 +48,7 @@
// If we haven't crashed, we've passed, but the test harness requires that
// we explicitly check something.
ok(true);
ok(true, "didn't crash");
]]>
</script>

View File

@ -13,6 +13,7 @@
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "mozilla/Attributes.h"
#include "mozilla/unused.h"
#include <stdio.h>
namespace mozilla {
@ -313,7 +314,7 @@ MapsReporter::ParseMapping(
devMinor, &inode, path);
// Eat up any whitespace at the end of this line, including the newline.
fscanf(aFile, " ");
unused << fscanf(aFile, " ");
// We might or might not have a path, but the rest of the arguments should be
// there.

View File

@ -225,7 +225,7 @@ interface nsIMemoryMultiReporter : nsISupports
readonly attribute int64_t explicitNonHeap;
};
[scriptable, uuid(4527b1d8-a81f-4af3-9623-80e4120392c7)]
[scriptable, uuid(46a09443-ec1d-4aa8-ae40-28642f138a04)]
interface nsIMemoryReporterManager : nsISupports
{
/*
@ -291,6 +291,81 @@ interface nsIMemoryReporterManager : nsISupports
* This attribute indicates if moz_malloc_usable_size() works.
*/
readonly attribute boolean hasMozMallocUsableSize;
/*
* This dumps the memory reports for this process to a file in the tmp
* directory called memory-reports-<pid>.json (or something similar, such as
* memory-reports-<pid>-1.json; no existing file will be overwritten).
*
* Sample output:
*
* {
* "hasMozMallocUsableSize":true,
* "reports": [
* {"process":"", "path":"explicit/foo/bar", "kind":1, "units":0,
* "amount":2000000, "description":"Foo bar."},
* {"process":"", "path":"heap-allocated", "kind":1, "units":0,
* "amount":3000000, "description":"Heap allocated."},
* {"process":"", "path":"vsize", "kind":1, "units":0,
* "amount":10000000, "description":"Vsize."}
* ]
* }
*
* JSON schema for the output.
*
* {
* "properties": {
* "hasMozMallocUsableSize": {
* "type": "boolean",
* "description": "nsIMemoryReporterManager::hasMozMallocUsableSize",
* "required": true
* },
* "reports": {
* "type": "array",
* "description": "The memory reports.",
* "required": true
* "minItems": 1,
* "items": {
* "type": "object",
* "properties": {
* "process": {
* "type": "string",
* "description": "nsIMemoryReporter::process",
* "required": true
* },
* "path": {
* "type": "string",
* "description": "nsIMemoryReporter::path",
* "required": true,
* "minLength": 1
* },
* "kind": {
* "type": "integer",
* "description": "nsIMemoryReporter::kind",
* "required": true
* },
* "units": {
* "type": "integer",
* "description": "nsIMemoryReporter::units",
* "required": true
* },
* "amount": {
* "type": "integer",
* "description": "nsIMemoryReporter::amount",
* "required": true
* },
* "description": {
* "type": "string",
* "description": "nsIMemoryReporter::description",
* "required": true
* }
* }
* }
* }
* }
* }
*/
void dumpReports ();
};
%{C++

View File

@ -6,13 +6,25 @@
#include "nsAtomTable.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsMemoryReporterManager.h"
#include "nsArrayEnumerator.h"
#include "nsISimpleEnumerator.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsPrintfCString.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Attributes.h"
#ifdef XP_WIN
#include <process.h>
#define getpid _getpid
#else
#include <unistd.h>
#endif
using namespace mozilla;
#if defined(MOZ_MEMORY)
@ -296,7 +308,7 @@ NS_FALLIBLE_MEMORY_REPORTER_IMPLEMENT(PageFaultsSoft,
KIND_OTHER,
UNITS_COUNT_CUMULATIVE,
GetSoftPageFaults,
"The number of soft page faults (also known as \"minor page faults\") that "
"The number of soft page faults (also known as 'minor page faults') that "
"have occurred since the process started. A soft page fault occurs when the "
"process tries to access a page which is present in physical memory but is "
"not mapped into the process's address space. For instance, a process might "
@ -311,7 +323,7 @@ NS_FALLIBLE_MEMORY_REPORTER_IMPLEMENT(PageFaultsHard,
KIND_OTHER,
UNITS_COUNT_CUMULATIVE,
GetHardPageFaults,
"The number of hard page faults (also known as \"major page faults\") that "
"The number of hard page faults (also known as 'major page faults') that "
"have occurred since the process started. A hard page fault occurs when a "
"process tries to access a page which is not present in physical memory. "
"The operating system must access the disk in order to fulfill a hard page "
@ -688,13 +700,13 @@ struct MemoryReport {
#ifdef DEBUG
// This is just a wrapper for int64_t that implements nsISupports, so it can be
// passed to nsIMemoryMultiReporter::CollectReports.
class PRInt64Wrapper MOZ_FINAL : public nsISupports {
class Int64Wrapper MOZ_FINAL : public nsISupports {
public:
NS_DECL_ISUPPORTS
PRInt64Wrapper() : mValue(0) { }
Int64Wrapper() : mValue(0) { }
int64_t mValue;
};
NS_IMPL_ISUPPORTS0(PRInt64Wrapper)
NS_IMPL_ISUPPORTS0(Int64Wrapper)
class ExplicitNonHeapCountingCallback MOZ_FINAL : public nsIMemoryMultiReporterCallback
{
@ -710,8 +722,8 @@ public:
PromiseFlatCString(aPath).Find("explicit") == 0 &&
aAmount != int64_t(-1))
{
PRInt64Wrapper *wrappedPRInt64 =
static_cast<PRInt64Wrapper *>(aWrappedExplicitNonHeap);
Int64Wrapper *wrappedPRInt64 =
static_cast<Int64Wrapper *>(aWrappedExplicitNonHeap);
wrappedPRInt64->mValue += aAmount;
}
return NS_OK;
@ -797,8 +809,8 @@ nsMemoryReporterManager::GetExplicit(int64_t *aExplicit)
#ifdef DEBUG
nsRefPtr<ExplicitNonHeapCountingCallback> cb =
new ExplicitNonHeapCountingCallback();
nsRefPtr<PRInt64Wrapper> wrappedExplicitNonHeapMultiSize2 =
new PRInt64Wrapper();
nsRefPtr<Int64Wrapper> wrappedExplicitNonHeapMultiSize2 =
new Int64Wrapper();
nsCOMPtr<nsISimpleEnumerator> e3;
EnumerateMultiReporters(getter_AddRefs(e3));
while (NS_SUCCEEDED(e3->HasMoreElements(&more)) && more) {
@ -839,6 +851,182 @@ nsMemoryReporterManager::GetHasMozMallocUsableSize(bool *aHas)
return NS_OK;
}
#define DUMP(o, s) \
do { \
const char* s2 = (s); \
uint32_t dummy; \
nsresult rv = (o)->Write((s2), strlen(s2), &dummy); \
NS_ENSURE_SUCCESS(rv, rv); \
} while (0)
static nsresult
DumpReport(nsIFileOutputStream *aOStream, bool isFirst,
const nsACString &aProcess, const nsACString &aPath, int32_t aKind,
int32_t aUnits, int64_t aAmount, const nsACString &aDescription)
{
DUMP(aOStream, isFirst ? "[" : ",");
// We only want to dump reports for this process. If |aProcess| is
// non-NULL 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;
}
unsigned pid = getpid();
nsPrintfCString pidStr("Process %u", pid);
DUMP(aOStream, "\n {\"process\": \"");
DUMP(aOStream, pidStr.get());
DUMP(aOStream, "\", \"path\": \"");
nsCString path(aPath);
path.ReplaceSubstring("\\", "\\\\"); // escape backslashes for JSON
DUMP(aOStream, path.get());
DUMP(aOStream, "\", \"kind\": ");
DUMP(aOStream, nsPrintfCString("%d", aKind).get());
DUMP(aOStream, ", \"units\": ");
DUMP(aOStream, nsPrintfCString("%d", aUnits).get());
DUMP(aOStream, ", \"amount\": ");
DUMP(aOStream, nsPrintfCString("%lld", aAmount).get());
nsCString description(aDescription);
description.ReplaceSubstring("\\", "\\\\"); /* <backslash> --> \\ */
description.ReplaceSubstring("\"", "\\\""); // " --> \"
description.ReplaceSubstring("\n", "\\n"); // <newline> --> \n
DUMP(aOStream, ", \"description\": \"");
DUMP(aOStream, description.get());
DUMP(aOStream, "\"}");
return NS_OK;
}
class DumpMultiReporterCallback : public nsIMemoryMultiReporterCallback
{
public:
NS_DECL_ISUPPORTS
NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
int32_t aKind, int32_t aUnits, int64_t aAmount,
const nsACString &aDescription,
nsISupports *aData)
{
nsCOMPtr<nsIFileOutputStream> ostream = do_QueryInterface(aData);
if (!ostream)
return NS_ERROR_FAILURE;
// The |isFirst = false| assumes that at least one single reporter is
// present and so will have been processed in DumpReports() below.
return DumpReport(ostream, /* isFirst = */ false, aProcess, aPath,
aKind, aUnits, aAmount, aDescription);
}
};
NS_IMPL_ISUPPORTS1(
DumpMultiReporterCallback
, nsIMemoryMultiReporterCallback
)
NS_IMETHODIMP
nsMemoryReporterManager::DumpReports()
{
// Open a file in NS_OS_TEMP_DIR for writing.
nsCOMPtr<nsIFile> tmpFile;
nsresult rv =
NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
NS_ENSURE_SUCCESS(rv, rv);
// Basic filename form: "memory-reports-<pid>.json".
nsCString filename("memory-reports-");
filename.AppendInt(getpid());
filename.AppendLiteral(".json");
rv = tmpFile->AppendNative(filename);
NS_ENSURE_SUCCESS(rv, rv);
rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFileOutputStream> ostream =
do_CreateInstance("@mozilla.org/network/file-output-stream;1");
rv = ostream->Init(tmpFile, -1, -1, 0);
NS_ENSURE_SUCCESS(rv, rv);
// Dump the memory reports to the file.
// Increment this number if the format changes.
DUMP(ostream, "{\n \"version\": 1,\n");
DUMP(ostream, " \"hasMozMallocUsableSize\": ");
bool hasMozMallocUsableSize;
GetHasMozMallocUsableSize(&hasMozMallocUsableSize);
DUMP(ostream, hasMozMallocUsableSize ? "true" : "false");
DUMP(ostream, ",\n");
DUMP(ostream, " \"reports\": ");
// Process single reporters.
bool isFirst = true;
bool more;
nsCOMPtr<nsISimpleEnumerator> e;
EnumerateReporters(getter_AddRefs(e));
while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryReporter> r;
e->GetNext(getter_AddRefs(r));
nsCString process;
rv = r->GetProcess(process);
NS_ENSURE_SUCCESS(rv, rv);
nsCString path;
rv = r->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
int32_t kind;
rv = r->GetKind(&kind);
NS_ENSURE_SUCCESS(rv, rv);
int32_t units;
rv = r->GetUnits(&units);
NS_ENSURE_SUCCESS(rv, rv);
int64_t amount;
rv = r->GetAmount(&amount);
NS_ENSURE_SUCCESS(rv, rv);
nsCString description;
rv = r->GetDescription(description);
NS_ENSURE_SUCCESS(rv, rv);
rv = DumpReport(ostream, isFirst, process, path, kind, units, amount,
description);
NS_ENSURE_SUCCESS(rv, rv);
isFirst = false;
}
// Process multi-reporters.
nsCOMPtr<nsISimpleEnumerator> e2;
EnumerateMultiReporters(getter_AddRefs(e2));
nsRefPtr<DumpMultiReporterCallback> cb = new DumpMultiReporterCallback();
while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
nsCOMPtr<nsIMemoryMultiReporter> r;
e2->GetNext(getter_AddRefs(r));
r->CollectReports(cb, ostream);
}
DUMP(ostream, "\n ]\n}");
rv = ostream->Close();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
#undef DUMP
NS_IMPL_ISUPPORTS1(nsMemoryReporter, nsIMemoryReporter)
nsMemoryReporter::nsMemoryReporter(nsACString& process,