mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 768470 - Add ability to import/export memory reports as JSON. r=jlebar.
--HG-- extra : rebase_source : 8e03a7e8c82204ea8e353a9ba583c4d53917a267
This commit is contained in:
parent
9589f29bae
commit
584b3df110
@ -20,6 +20,10 @@ h2 {
|
||||
}
|
||||
|
||||
.accuracyWarning {
|
||||
color: #d22;
|
||||
}
|
||||
|
||||
.badInputWarning {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
|
@ -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 = {};
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": 1
|
||||
}
|
@ -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."}
|
||||
]
|
||||
}
|
@ -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\
|
||||
|
@ -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\
|
||||
|
@ -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));
|
||||
|
196
toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
Normal file
196
toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
Normal 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>
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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++
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user