mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 20:30:41 +00:00
Bug 848560 (part 3) - Add support to about:memory for writing memory report dumps and reading gzipped memory report dumps. code=nnethercote,maierman. r=jlebar.
--HG-- extra : rebase_source : b93b45d1f4c22a388eedff890c19dc1952c576de
This commit is contained in:
parent
9f600a37f0
commit
05d64c5967
@ -494,14 +494,14 @@ ContentChild::DeallocPMemoryReportRequest(PMemoryReportRequestChild* actor)
|
||||
}
|
||||
|
||||
bool
|
||||
ContentChild::RecvDumpMemoryReportsToFile(const nsString& aIdentifier,
|
||||
ContentChild::RecvDumpMemoryInfoToTempDir(const nsString& aIdentifier,
|
||||
const bool& aMinimizeMemoryUsage,
|
||||
const bool& aDumpChildProcesses)
|
||||
{
|
||||
nsCOMPtr<nsIMemoryInfoDumper> dumper = do_GetService("@mozilla.org/memory-info-dumper;1");
|
||||
|
||||
dumper->DumpMemoryReportsToFile(
|
||||
aIdentifier, aMinimizeMemoryUsage, aDumpChildProcesses);
|
||||
dumper->DumpMemoryInfoToTempDir(aIdentifier, aMinimizeMemoryUsage,
|
||||
aDumpChildProcesses);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ public:
|
||||
RecvAudioChannelNotify();
|
||||
|
||||
virtual bool
|
||||
RecvDumpMemoryReportsToFile(const nsString& aIdentifier,
|
||||
RecvDumpMemoryInfoToTempDir(const nsString& aIdentifier,
|
||||
const bool& aMinimizeMemoryUsage,
|
||||
const bool& aDumpChildProcesses);
|
||||
virtual bool
|
||||
|
@ -309,12 +309,12 @@ child:
|
||||
async AudioChannelNotify();
|
||||
|
||||
/**
|
||||
* Dump the contents of about:memory to a file in our temp directory.
|
||||
* Do a memory info dump to a file in our temp directory.
|
||||
*
|
||||
* For documentation on the args, see
|
||||
* MemoryInfoDumper::dumpMemoryReportsToFile.
|
||||
* MemoryInfoDumper::dumpMemoryInfoToTempDir.
|
||||
*/
|
||||
async DumpMemoryReportsToFile(nsString identifier,
|
||||
async DumpMemoryInfoToTempDir(nsString identifier,
|
||||
bool minimizeMemoryUsage,
|
||||
bool dumpChildProcesses);
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
|
||||
const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
|
||||
@ -40,8 +41,22 @@ 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);
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "nsBinaryStream",
|
||||
() => CC("@mozilla.org/binaryinputstream;1",
|
||||
"nsIBinaryInputStream",
|
||||
"setInputStream"));
|
||||
XPCOMUtils.defineLazyGetter(this, "nsFile",
|
||||
() => CC("@mozilla.org/file/local;1",
|
||||
"nsIFile", "initWithPath"));
|
||||
XPCOMUtils.defineLazyGetter(this, "nsGzipConverter",
|
||||
() => CC("@mozilla.org/streamconv;1?from=gzip&to=uncompressed",
|
||||
"nsIStreamConverter"));
|
||||
|
||||
let gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
|
||||
.getService(Ci.nsIMemoryReporterManager);
|
||||
|
||||
let gUnnamedProcessStr = "Main Process";
|
||||
|
||||
@ -69,10 +84,6 @@ let gIsDiff = false;
|
||||
|
||||
let gChildMemoryListener = undefined;
|
||||
|
||||
// This is a useful function and an efficient way to implement it.
|
||||
String.prototype.startsWith =
|
||||
function(s) { return this.lastIndexOf(s, 0) === 0; }
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Forward slashes in URLs in paths are represented with backslashes to avoid
|
||||
@ -140,8 +151,8 @@ function badInput(x)
|
||||
|
||||
function addChildObserversAndUpdate(aUpdateFn)
|
||||
{
|
||||
let os = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
let os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
os.notifyObservers(null, "child-memory-reporter-request", null);
|
||||
|
||||
gChildMemoryListener = aUpdateFn;
|
||||
@ -167,8 +178,8 @@ function onUnload()
|
||||
// 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"].
|
||||
getService(Ci.nsIObserverService);
|
||||
let os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
os.removeObserver(gChildMemoryListener, "child-memory-reporter-update");
|
||||
}
|
||||
}
|
||||
@ -373,9 +384,7 @@ function onLoadAboutMemory()
|
||||
for (let i = 0; i < searchSplit.length; i++) {
|
||||
if (searchSplit[i].toLowerCase().startsWith('file=')) {
|
||||
let filename = searchSplit[i].substring('file='.length);
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(decodeURIComponent(filename));
|
||||
updateAboutMemoryFromFile(file);
|
||||
updateAboutMemoryFromFile(decodeURIComponent(filename));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -388,7 +397,7 @@ function doGlobalGC()
|
||||
{
|
||||
Cu.forceGC();
|
||||
let os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
.getService(Ci.nsIObserverService);
|
||||
os.notifyObservers(null, "child-gc-request", null);
|
||||
updateAboutMemory();
|
||||
}
|
||||
@ -399,7 +408,7 @@ function doCC()
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.cycleCollect();
|
||||
let os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
.getService(Ci.nsIObserverService);
|
||||
os.notifyObservers(null, "child-cc-request", null);
|
||||
updateAboutMemory();
|
||||
}
|
||||
@ -436,6 +445,18 @@ function updateAboutMemory()
|
||||
// Increment this if the JSON format changes.
|
||||
var gCurrentFileFormatVersion = 1;
|
||||
|
||||
/**
|
||||
* Handle an update exception that occurs while updating the page.
|
||||
*
|
||||
* @param aEx
|
||||
* The exception.
|
||||
*/
|
||||
function clearBodyAndHandleException(aEx) {
|
||||
let body = clearBody();
|
||||
handleException(aEx);
|
||||
appendAboutMemoryFooter(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate about:memory using the data in the given JSON string.
|
||||
*
|
||||
@ -472,37 +493,55 @@ function updateAboutMemoryFromJSONString(aJSONString)
|
||||
* Like updateAboutMemory(), but gets its data from a file instead of the
|
||||
* memory reporters.
|
||||
*
|
||||
* @param aFile
|
||||
* The File or nsILocalFile being read from.
|
||||
* @param aFilename
|
||||
* The name of the file being read from.
|
||||
*
|
||||
* The expected format of the file's contents is described in the
|
||||
* comment describing nsIMemoryReporterManager::dumpReports.
|
||||
*/
|
||||
function updateAboutMemoryFromFile(aFile)
|
||||
function updateAboutMemoryFromFile(aFilename)
|
||||
{
|
||||
// Note: reader.onload is called asynchronously, once FileReader.readAsText()
|
||||
// completes. Therefore its exception handling has to be distinct from that
|
||||
// surrounding the |reader.readAsText(aFile)| call.
|
||||
|
||||
try {
|
||||
// Convert nsILocalFile to a File object, if necessary.
|
||||
let file = aFile;
|
||||
if (aFile instanceof Ci.nsILocalFile) {
|
||||
file = new File(aFile);
|
||||
}
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onerror = function(aEvent) { throw "FileReader.onerror"; };
|
||||
reader.onabort = function(aEvent) { throw "FileReader.onabort"; };
|
||||
reader.onload = function(aEvent) {
|
||||
reader.onerror = () => { throw "FileReader.onerror"; };
|
||||
reader.onabort = () => { throw "FileReader.onabort"; };
|
||||
reader.onload = (aEvent) => {
|
||||
updateAboutMemoryFromJSONString(aEvent.target.result);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
// If it doesn't have a .gz suffix, read it as a (legacy) ungzipped file.
|
||||
if (!aFilename.endsWith(".gz")) {
|
||||
reader.readAsText(new File(aFilename));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read compressed gzip file.
|
||||
let converter = new nsGzipConverter();
|
||||
converter.asyncConvertData("gzip", "uncompressed", {
|
||||
data: [],
|
||||
onStartRequest: function(aR, aC) {},
|
||||
onDataAvailable: function(aR, aC, aStream, aO, aCount) {
|
||||
let bi = new nsBinaryStream(aStream);
|
||||
this.data.push(bi.readBytes(aCount));
|
||||
},
|
||||
onStopRequest: function(aR, aC, aStatusCode) {
|
||||
try {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
throw aStatusCode;
|
||||
}
|
||||
reader.readAsText(new Blob(this.data));
|
||||
} catch (ex) {
|
||||
clearBodyAndHandleException(ex);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
let file = new nsFile(aFilename);
|
||||
let fileChan = Services.io.newChannelFromURI(Services.io.newFileURI(file));
|
||||
fileChan.asyncOpen(converter, null);
|
||||
|
||||
} catch (ex) {
|
||||
let body = clearBody();
|
||||
handleException(ex);
|
||||
appendAboutMemoryFooter(body);
|
||||
clearBodyAndHandleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,8 +552,8 @@ function updateAboutMemoryFromFile(aFile)
|
||||
function updateAboutMemoryFromClipboard()
|
||||
{
|
||||
// Get the clipboard's contents.
|
||||
let cb = Cc["@mozilla.org/widget/clipboard;1"]
|
||||
.getService(Components.interfaces.nsIClipboard);
|
||||
let cb = Cc["@mozilla.org/widget/clipboard;1"].
|
||||
getService(Components.interfaces.nsIClipboard);
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"]
|
||||
.createInstance(Ci.nsITransferable);
|
||||
let loadContext = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
@ -532,10 +571,9 @@ function updateAboutMemoryFromClipboard()
|
||||
|
||||
// Success! Now use the string to generate about:memory.
|
||||
updateAboutMemoryFromJSONString(cbString);
|
||||
|
||||
} catch (ex) {
|
||||
let body = clearBody();
|
||||
handleException(ex);
|
||||
appendAboutMemoryFooter(body);
|
||||
clearBodyAndHandleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,6 +672,7 @@ function appendAboutMemoryFooter(aBody)
|
||||
"flushing various caches.";
|
||||
const RdDesc = "Read memory report data from a file.";
|
||||
const CbDesc = "Read memory report data from the clipboard.";
|
||||
const WrDesc = "Write memory report data to a file.";
|
||||
|
||||
function appendButton(aP, aTitle, aOnClick, aText, aId)
|
||||
{
|
||||
@ -662,7 +701,7 @@ function appendAboutMemoryFooter(aBody)
|
||||
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);
|
||||
updateAboutMemoryFromFile(file.mozFullPath);
|
||||
});
|
||||
appendButton(div1, RdDesc, function() { input.click() },
|
||||
"Read reports from a file", "readReportsFromFileButton");
|
||||
@ -670,6 +709,9 @@ function appendAboutMemoryFooter(aBody)
|
||||
appendButton(div1, CbDesc, updateAboutMemoryFromClipboard,
|
||||
"Read reports from clipboard", "readReportsFromClipboardButton");
|
||||
|
||||
appendButton(div1, WrDesc, writeReportsToFile,
|
||||
"Write reports to a file", "writeReportsToAFileButton");
|
||||
|
||||
let div2 = appendElement(section, "div");
|
||||
if (gVerbose) {
|
||||
let a = appendElementWithText(div2, "a", "option", "Less verbose");
|
||||
@ -1630,6 +1672,31 @@ function appendSectionHeader(aP, aText)
|
||||
return appendElement(aP, "pre", "entries");
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
function writeReportsToFile()
|
||||
{
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter("Zipped JSON files", "*.json.gz");
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterAll);
|
||||
fp.filterIndex = 0;
|
||||
fp.addToRecentDocs = true;
|
||||
fp.defaultString = "memory-report.json.gz";
|
||||
|
||||
let fpCallback = function(aResult) {
|
||||
if (aResult == Ci.nsIFilePicker.returnOK ||
|
||||
aResult == Ci.nsIFilePicker.returnReplace) {
|
||||
|
||||
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
|
||||
.getService(Ci.nsIMemoryInfoDumper);
|
||||
|
||||
dumper.dumpMemoryReportsToNamedFile(fp.file.path);
|
||||
}
|
||||
};
|
||||
fp.open(fpCallback);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Code specific to about:compartments
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -1695,7 +1762,7 @@ function Compartment(aUnsafeName, aIsSystemCompartment)
|
||||
}
|
||||
|
||||
Compartment.prototype = {
|
||||
merge: function(r) {
|
||||
merge: function(aR) {
|
||||
this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
|
||||
}
|
||||
};
|
||||
|
@ -2,13 +2,9 @@
|
||||
"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."}
|
||||
{"process": "Main Process (pid NNN)", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144000, "description": "Heap allocated."},
|
||||
{"process": "Main Process (pid NNN)", "path": "other/b", "kind": 2, "units": 0, "amount": 104857, "description": "Other b."},
|
||||
{"process": "Main Process (pid NNN)", "path": "other/a", "kind": 2, "units": 0, "amount": 209715, "description": "Other a."},
|
||||
{"process": "Main Process (pid NNN)", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 52428800, "description": "A b."}
|
||||
]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<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
|
||||
<!-- This file tests the saving and loading of memory reports to/from file in
|
||||
about:memory. -->
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
@ -47,21 +47,22 @@
|
||||
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
|
||||
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
|
||||
|
||||
function f(aPath, aKind, aAmount) {
|
||||
function f(aPath, aKind, aAmount, aDesc) {
|
||||
return {
|
||||
process: "",
|
||||
path: aPath,
|
||||
kind: aKind,
|
||||
units: BYTES,
|
||||
description: "Desc.",
|
||||
amount: aAmount
|
||||
amount: aAmount,
|
||||
description: aDesc
|
||||
};
|
||||
}
|
||||
|
||||
let fakeReporters = [
|
||||
f("heap-allocated", OTHER, 250 * MB),
|
||||
f("explicit/a/b", HEAP, 50 * MB),
|
||||
f("other", OTHER, 0.1 * MB),
|
||||
f("heap-allocated", OTHER, 250 * MB, "Heap allocated."),
|
||||
f("explicit/a/b", HEAP, 50 * MB, "A b."),
|
||||
f("other/a", OTHER, 0.2 * MB, "Other a."),
|
||||
f("other/b", OTHER, 0.1 * MB, "Other b."),
|
||||
];
|
||||
|
||||
for (let i = 0; i < fakeReporters.length; i++) {
|
||||
@ -71,8 +72,9 @@
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<iframe id="amGoodFrame" height="400" src="about:memory"></iframe>
|
||||
<iframe id="amBadFrame" height="400" src="about:memory"></iframe>
|
||||
<iframe id="amGoodFrame" height="200" src="about:memory"></iframe>
|
||||
<iframe id="amGoodFrame2" height="200" src="about:memory"></iframe>
|
||||
<iframe id="amBadFrame" height="200" src="about:memory"></iframe>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
@ -97,7 +99,7 @@
|
||||
|
||||
// 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) {
|
||||
function test(aFrameId, aFilename, aExpected, aDumpFirst, aNext) {
|
||||
let frame = document.getElementById(aFrameId);
|
||||
frame.focus();
|
||||
|
||||
@ -111,6 +113,15 @@
|
||||
file.append("tests");
|
||||
file.append(aFilename);
|
||||
|
||||
if (aDumpFirst) {
|
||||
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
|
||||
getService(Ci.nsIMemoryInfoDumper);
|
||||
|
||||
dumper.dumpMemoryReportsToNamedFile(file.path,
|
||||
/* minimizeMemoryUsage = */ false,
|
||||
/* dumpChildProcesses = */ false);
|
||||
}
|
||||
|
||||
let input = frame.contentWindow.document.getElementById("fileInput");
|
||||
input.value = file.path; // this works because it's a chrome test
|
||||
|
||||
@ -126,10 +137,12 @@
|
||||
// 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.
|
||||
// Copy and paste frame contents, and filter out non-deterministic
|
||||
// differences.
|
||||
synthesizeKey("A", {accelKey: true});
|
||||
synthesizeKey("C", {accelKey: true});
|
||||
let actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
actual = actual.replace(/\(pid \d+\)/, "(pid NNN)");
|
||||
|
||||
if (actual === aExpected) {
|
||||
SimpleTest.ok(true, "Clipboard has the expected contents");
|
||||
@ -137,6 +150,7 @@
|
||||
} else {
|
||||
numFailures++;
|
||||
if (numFailures === maxFailures) {
|
||||
ok(false, "pasted text doesn't match");
|
||||
dump("******EXPECTED******\n");
|
||||
dump(aExpected);
|
||||
dump("*******ACTUAL*******\n");
|
||||
@ -155,7 +169,7 @@
|
||||
function chain(aFrameIds) {
|
||||
let x = aFrameIds.shift();
|
||||
if (x) {
|
||||
return function() { test(x.frameId, x.filename, x.expected, chain(aFrameIds)); }
|
||||
return function() { test(x.frameId, x.filename, x.expected, x.dumpFirst, chain(aFrameIds)); }
|
||||
} else {
|
||||
return function() { finish(); };
|
||||
}
|
||||
@ -165,31 +179,38 @@
|
||||
// the loading of data from file. If we got this far, we're doing fine.
|
||||
let expectedGood =
|
||||
"\
|
||||
Main Process\n\
|
||||
Main Process (pid NNN)\n\
|
||||
Explicit Allocations\n\
|
||||
\n\
|
||||
2.86 MB (100.0%) -- explicit\n\
|
||||
├──1.91 MB (66.67%) ── foo/bar\n\
|
||||
└──0.95 MB (33.33%) ── heap-unclassified\n\
|
||||
250.00 MB (100.0%) -- explicit\n\
|
||||
├──200.00 MB (80.00%) ── heap-unclassified\n\
|
||||
└───50.00 MB (20.00%) ── a/b\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
\n\
|
||||
0.00 MB (100.0%) -- a\n\
|
||||
├──0.00 MB (50.00%) ── b\n\
|
||||
└──0.00 MB (50.00%) ── c\n\
|
||||
0.30 MB (100.0%) -- other\n\
|
||||
├──0.20 MB (66.67%) ── a\n\
|
||||
└──0.10 MB (33.33%) ── b\n\
|
||||
\n\
|
||||
2.86 MB ── heap-allocated\n\
|
||||
250.00 MB ── heap-allocated\n\
|
||||
\n\
|
||||
";
|
||||
|
||||
// This is the output for a malformed data file.
|
||||
let expectedBad =
|
||||
"\
|
||||
Invalid memory report(s): missing 'hasMozMallocUsableSize' property";
|
||||
Invalid memory report(s): missing 'hasMozMallocUsableSize' property\n\
|
||||
";
|
||||
|
||||
let frames = [
|
||||
{ frameId: "amGoodFrame", filename: "memory-reports-good.json", expected: expectedGood },
|
||||
{ frameId: "amBadFrame", filename: "memory-reports-bad.json", expected: expectedBad }
|
||||
// This loads a pre-existing file that is valid.
|
||||
{ frameId: "amGoodFrame", filename: "memory-reports-good.json", expected: expectedGood, dumpFirst: false },
|
||||
|
||||
// This dumps to a file and then reads it back in. The output is the same as the first test.
|
||||
{ frameId: "amGoodFrame2", filename: "memory-reports-dumped.json.gz", expected: expectedGood, dumpFirst: true },
|
||||
|
||||
// This loads a pre-existing file that is invalid.
|
||||
{ frameId: "amBadFrame", filename: "memory-reports-bad.json", expected: expectedBad, dumpFirst: false }
|
||||
];
|
||||
|
||||
SimpleTest.waitForFocus(chain(frames));
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@ -39,10 +40,10 @@ nsGZFileWriter::Init(nsIFile* aFile)
|
||||
// gzip can own. Then close our FILE, leaving only gzip's fd open.
|
||||
|
||||
FILE* file;
|
||||
nsresult rv = aFile->OpenANSIFileDesc("w", &file);
|
||||
nsresult rv = aFile->OpenANSIFileDesc("wb", &file);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mGZFile = gzdopen(dup(fileno(file)), "w");
|
||||
mGZFile = gzdopen(dup(fileno(file)), "wb");
|
||||
fclose(file);
|
||||
|
||||
// gzdopen returns NULL on error.
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@ -62,9 +63,9 @@ interface nsIGZFileWriter : nsISupports
|
||||
%}
|
||||
|
||||
/**
|
||||
* Close this nsIGZFileWriter. This method is run when the underlying object
|
||||
* is destroyed, so it's not strictly necessary to explicitly call it from
|
||||
* your code.
|
||||
* Close this nsIGZFileWriter. Classes implementing nsIGZFileWriter will run
|
||||
* this method when the underlying object is destroyed, so it's not strictly
|
||||
* necessary to explicitly call it from your code.
|
||||
*
|
||||
* It's an error to call this method twice, and it's an error to call write()
|
||||
* after finish() has been called.
|
||||
|
@ -5,46 +5,27 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, builtinclass, uuid(ede09623-d793-4355-9682-b3adfe307126)]
|
||||
[scriptable, builtinclass, uuid(73d23ad8-e77c-4079-b8c0-d71bf0ebc5b2)]
|
||||
interface nsIMemoryInfoDumper : nsISupports
|
||||
{
|
||||
/**
|
||||
* DumpMemoryReportsToFile dumps the memory reports for this process and
|
||||
* possibly our child processes (and their children, recursively) to a file in
|
||||
* the tmp directory called memory-reports-<identifier>-<pid>.json.gz (or
|
||||
* something similar, such as memory-reports-<identifier>-<pid>-1.json.gz; no
|
||||
* existing file will be overwritten).
|
||||
*
|
||||
* @param aIdentifier this identifier will appear in the filename of our
|
||||
* about:memory dump and those of our children (if aDumpChildProcesses is
|
||||
* true).
|
||||
*
|
||||
* If the identifier is empty, the dumpMemoryReportsToFile implementation
|
||||
* may set it arbitrarily and use that new value for its own dump and the
|
||||
* dumps of its child processes. For example, the dumpMemoryReportsToFile
|
||||
* implementation may set |aIdentifier| to the number of seconds since the
|
||||
* epoch.
|
||||
*
|
||||
* @param aMinimizeMemoryUsage indicates whether we should run a series of
|
||||
* gc/cc's in an attempt to reduce our memory usage before collecting our
|
||||
* memory report.
|
||||
*
|
||||
* @param aDumpChildProcesses indicates whether we should call
|
||||
* dumpMemoryReportsToFile in our child processes. If so, the child
|
||||
* processes will also dump their children, and so on.
|
||||
* This dumps gzipped memory reports for this process. If a file of the
|
||||
* given name exists, it will be overwritten. Nothing is done for any child
|
||||
* processes (and their children, recursively).
|
||||
*
|
||||
* @param aFilename The output file.
|
||||
*
|
||||
* 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."}
|
||||
* {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar",
|
||||
* "kind":1, "units":0, "amount":2000000, "description":"Foo bar."},
|
||||
* {"process":"Main Process (pid 12345)", "path":"heap-allocated",
|
||||
* "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."},
|
||||
* {"process":"Main Process (pid 12345)", "path":"vsize",
|
||||
* "kind":1, "units":0, "amount":10000000, "description":"Vsize."}
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
@ -102,9 +83,41 @@ interface nsIMemoryInfoDumper : nsISupports
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
void dumpMemoryReportsToFile(in AString aIdentifier,
|
||||
in bool aMinimizeMemoryUsage,
|
||||
in bool aDumpChildProcesses);
|
||||
void dumpMemoryReportsToNamedFile(in AString aFilename);
|
||||
|
||||
/**
|
||||
* Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory
|
||||
* reports for this process and possibly our child processes (and their
|
||||
* children, recursively) to a file in the tmp directory called
|
||||
* memory-reports-<identifier>-<pid>.json.gz (or something similar, such as
|
||||
* memory-reports-<identifier>-<pid>-1.json.gz; no existing file will be
|
||||
* overwritten).
|
||||
*
|
||||
* If DMD is enabled, this method also dump gzipped DMD output to a file in
|
||||
* the tmp directory called dmd-<identifier>-<pid>.txt.gz (or something
|
||||
* similar; again, no existing file will be overwritten).
|
||||
*
|
||||
* @param aIdentifier this identifier will appear in the filename of our
|
||||
* about:memory dump and those of our children (if aDumpChildProcesses is
|
||||
* true).
|
||||
*
|
||||
* If the identifier is empty, the implementation may set it arbitrarily
|
||||
* and use that new value for its own dump and the dumps of its child
|
||||
* processes. For example, the implementation may set |aIdentifier| to the
|
||||
* number of seconds since the epoch.
|
||||
*
|
||||
* @param aMinimizeMemoryUsage indicates whether we should run a series of
|
||||
* gc/cc's in an attempt to reduce our memory usage before collecting our
|
||||
* memory report.
|
||||
*
|
||||
* @param aDumpChildProcesses indicates whether we should call
|
||||
* dumpMemoryInfoToTempDir in our child processes. If
|
||||
* so, the child processes will also dump their children, and so on.
|
||||
*/
|
||||
void dumpMemoryInfoToTempDir(
|
||||
in AString aIdentifier,
|
||||
in bool aMinimizeMemoryUsage,
|
||||
in bool aDumpChildProcesses);
|
||||
|
||||
/**
|
||||
* Dump GC and CC logs to files in the OS's temp directory (or in
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=50 et cin tw=80 : */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@ -52,13 +52,12 @@ using namespace mozilla::dom;
|
||||
|
||||
namespace {
|
||||
|
||||
class DumpMemoryReportsRunnable : public nsRunnable
|
||||
class DumpMemoryInfoToTempDirRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DumpMemoryReportsRunnable(const nsAString& aIdentifier,
|
||||
bool aMinimizeMemoryUsage,
|
||||
bool aDumpChildProcesses)
|
||||
|
||||
DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier,
|
||||
bool aMinimizeMemoryUsage,
|
||||
bool aDumpChildProcesses)
|
||||
: mIdentifier(aIdentifier)
|
||||
, mMinimizeMemoryUsage(aMinimizeMemoryUsage)
|
||||
, mDumpChildProcesses(aDumpChildProcesses)
|
||||
@ -67,8 +66,8 @@ public:
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
nsCOMPtr<nsIMemoryInfoDumper> dumper = do_GetService("@mozilla.org/memory-info-dumper;1");
|
||||
dumper->DumpMemoryReportsToFile(
|
||||
mIdentifier, mMinimizeMemoryUsage, mDumpChildProcesses);
|
||||
dumper->DumpMemoryInfoToTempDir(mIdentifier, mMinimizeMemoryUsage,
|
||||
mDumpChildProcesses);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -347,11 +346,10 @@ public:
|
||||
signum == sDumpAboutMemoryAfterMMUSignum) {
|
||||
// Dump our memory reports (but run this on the main thread!).
|
||||
bool doMMUFirst = signum == sDumpAboutMemoryAfterMMUSignum;
|
||||
nsRefPtr<DumpMemoryReportsRunnable> runnable =
|
||||
new DumpMemoryReportsRunnable(
|
||||
/* identifier = */ EmptyString(),
|
||||
doMMUFirst,
|
||||
/* dumpChildProcesses = */ true);
|
||||
nsRefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
|
||||
new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(),
|
||||
doMMUFirst,
|
||||
/* dumpChildProcesses = */ true);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
}
|
||||
else if (signum == sGCAndCCDumpSignum) {
|
||||
@ -506,11 +504,10 @@ public:
|
||||
|
||||
if (doMemoryReport || doMMUMemoryReport) {
|
||||
LOG("FifoWatcher dispatching memory report runnable.");
|
||||
nsRefPtr<DumpMemoryReportsRunnable> runnable =
|
||||
new DumpMemoryReportsRunnable(
|
||||
/* identifier = */ EmptyString(),
|
||||
doMMUMemoryReport,
|
||||
/* dumpChildProcesses = */ true);
|
||||
nsRefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
|
||||
new DumpMemoryInfoToTempDirRunnable(/* identifier = */ EmptyString(),
|
||||
doMMUMemoryReport,
|
||||
/* dumpChildProcesses = */ true);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
} else if (doGCCCDump) {
|
||||
LOG("FifoWatcher dispatching GC/CC log runnable.");
|
||||
@ -561,45 +558,6 @@ EnsureNonEmptyIdentifier(nsAString& aIdentifier)
|
||||
aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMemoryInfoDumper::DumpMemoryReportsToFile(
|
||||
const nsAString& aIdentifier,
|
||||
bool aMinimizeMemoryUsage,
|
||||
bool aDumpChildProcesses)
|
||||
{
|
||||
nsString identifier(aIdentifier);
|
||||
EnsureNonEmptyIdentifier(identifier);
|
||||
|
||||
// Kick off memory report dumps in our child processes, if applicable. We
|
||||
// do this before doing our own report because writing a report may be I/O
|
||||
// bound, in which case we want to busy the CPU with other reports while we
|
||||
// work on our own.
|
||||
if (aDumpChildProcesses) {
|
||||
nsTArray<ContentParent*> children;
|
||||
ContentParent::GetAll(children);
|
||||
for (uint32_t i = 0; i < children.Length(); i++) {
|
||||
unused << children[i]->SendDumpMemoryReportsToFile(
|
||||
identifier, aMinimizeMemoryUsage, aDumpChildProcesses);
|
||||
}
|
||||
}
|
||||
|
||||
if (aMinimizeMemoryUsage) {
|
||||
// Minimize memory usage, then run DumpMemoryReportsToFile again.
|
||||
nsRefPtr<DumpMemoryReportsRunnable> callback =
|
||||
new DumpMemoryReportsRunnable(identifier,
|
||||
/* minimizeMemoryUsage = */ false,
|
||||
/* dumpChildProcesses = */ false);
|
||||
nsCOMPtr<nsIMemoryReporterManager> mgr =
|
||||
do_GetService("@mozilla.org/memory-reporter-manager;1");
|
||||
NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
|
||||
nsCOMPtr<nsICancelableRunnable> runnable;
|
||||
mgr->MinimizeMemoryUsage(callback, getter_AddRefs(runnable));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return DumpMemoryReportsToFileImpl(identifier);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMemoryInfoDumper::DumpGCAndCCLogsToFile(
|
||||
const nsAString& aIdentifier,
|
||||
@ -712,7 +670,7 @@ class DumpMultiReporterCallback MOZ_FINAL : public nsIMemoryMultiReporterCallbac
|
||||
|
||||
// The |isFirst = false| assumes that at least one single reporter is
|
||||
// present and so will have been processed in
|
||||
// DumpMemoryReportsToFileImpl() below.
|
||||
// DumpProcessMemoryReportsToGZFileWriter() below.
|
||||
return DumpReport(writer, /* isFirst = */ false, aProcess, aPath,
|
||||
aKind, aUnits, aAmount, aDescription);
|
||||
return NS_OK;
|
||||
@ -777,7 +735,8 @@ struct DMDWriteState
|
||||
{}
|
||||
};
|
||||
|
||||
static void DMDWrite(void* aState, const char* aFmt, va_list ap)
|
||||
static void
|
||||
DMDWrite(void* aState, const char* aFmt, va_list ap)
|
||||
{
|
||||
DMDWriteState *state = (DMDWriteState*)aState;
|
||||
vsnprintf(state->mBuf, state->kBufSize, aFmt, ap);
|
||||
@ -785,62 +744,27 @@ static void DMDWrite(void* aState, const char* aFmt, va_list ap)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* static */ nsresult
|
||||
nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(
|
||||
const nsAString& aIdentifier)
|
||||
static nsresult
|
||||
DumpProcessMemoryReportsToGZFileWriter(nsIGZFileWriter *aWriter)
|
||||
{
|
||||
MOZ_ASSERT(!aIdentifier.IsEmpty());
|
||||
|
||||
// Open a new file named something like
|
||||
//
|
||||
// incomplete-memory-report-<identifier>-<pid>.json.gz
|
||||
//
|
||||
// in NS_OS_TEMP_DIR for writing. When we're finished writing the report,
|
||||
// we'll rename this file and get rid of the "incomplete-" prefix.
|
||||
//
|
||||
// We do this because we don't want scripts which poll the filesystem
|
||||
// looking for memory report dumps to grab a file before we're finished
|
||||
// writing to it.
|
||||
|
||||
// Note that |mrFilename| is missing the "incomplete-" prefix; we'll tack
|
||||
// that on in a moment.
|
||||
nsCString mrFilename;
|
||||
MakeFilename("memory-report", aIdentifier, "json.gz", mrFilename);
|
||||
|
||||
nsCOMPtr<nsIFile> mrTmpFile;
|
||||
nsresult rv;
|
||||
rv = OpenTempFile(NS_LITERAL_CSTRING("incomplete-") + mrFilename,
|
||||
getter_AddRefs(mrTmpFile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsRefPtr<nsGZFileWriter> writer = new nsGZFileWriter();
|
||||
rv = writer->Init(mrTmpFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Clear DMD's reportedness state before running the reporters, to avoid
|
||||
// spurious twice-reported warnings.
|
||||
#ifdef MOZ_DMD
|
||||
dmd::ClearReports();
|
||||
#endif
|
||||
|
||||
// Dump the memory reports to the file.
|
||||
|
||||
// Increment this number if the format changes.
|
||||
//
|
||||
// This is the first write to the file, and it causes |writer| to allocate
|
||||
// This is the first write to the file, and it causes |aWriter| to allocate
|
||||
// over 200 KiB of memory.
|
||||
//
|
||||
DUMP(writer, "{\n \"version\": 1,\n");
|
||||
DUMP(aWriter, "{\n \"version\": 1,\n");
|
||||
|
||||
DUMP(writer, " \"hasMozMallocUsableSize\": ");
|
||||
DUMP(aWriter, " \"hasMozMallocUsableSize\": ");
|
||||
|
||||
nsCOMPtr<nsIMemoryReporterManager> mgr =
|
||||
do_GetService("@mozilla.org/memory-reporter-manager;1");
|
||||
NS_ENSURE_STATE(mgr);
|
||||
|
||||
DUMP(writer, mgr->GetHasMozMallocUsableSize() ? "true" : "false");
|
||||
DUMP(writer, ",\n");
|
||||
DUMP(writer, " \"reports\": ");
|
||||
DUMP(aWriter, mgr->GetHasMozMallocUsableSize() ? "true" : "false");
|
||||
DUMP(aWriter, ",\n");
|
||||
DUMP(aWriter, " \"reports\": ");
|
||||
|
||||
// Process single reporters.
|
||||
bool isFirst = true;
|
||||
@ -875,7 +799,7 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(
|
||||
rv = r->GetDescription(description);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = DumpReport(writer, isFirst, process, path, kind, units, amount,
|
||||
rv = DumpReport(aWriter, isFirst, process, path, kind, units, amount,
|
||||
description);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
@ -889,26 +813,68 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(
|
||||
while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
|
||||
nsCOMPtr<nsIMemoryMultiReporter> r;
|
||||
e2->GetNext(getter_AddRefs(r));
|
||||
r->CollectReports(cb, writer);
|
||||
r->CollectReports(cb, aWriter);
|
||||
}
|
||||
|
||||
DUMP(writer, "\n ]\n}");
|
||||
DUMP(aWriter, "\n ]\n}\n");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DumpProcessMemoryInfoToTempDir(const nsAString& aIdentifier)
|
||||
{
|
||||
MOZ_ASSERT(!aIdentifier.IsEmpty());
|
||||
|
||||
#ifdef MOZ_DMD
|
||||
// Clear DMD's reportedness state before running the memory reporters, to
|
||||
// avoid spurious twice-reported warnings.
|
||||
dmd::ClearReports();
|
||||
#endif
|
||||
|
||||
// Open a new file named something like
|
||||
//
|
||||
// dmd-<identifier>-<pid>.txt.gz
|
||||
// incomplete-memory-report-<identifier>-<pid>.json.gz
|
||||
//
|
||||
// in NS_OS_TEMP_DIR for writing, and dump DMD output to it. This must occur
|
||||
// after the memory reporters have been run (above), but before the
|
||||
// memory-reports file has been renamed (so scripts can detect the DMD file,
|
||||
// if present).
|
||||
// in NS_OS_TEMP_DIR for writing. When we're finished writing the report,
|
||||
// we'll rename this file and get rid of the "incomplete-" prefix.
|
||||
//
|
||||
// We do this because we don't want scripts which poll the filesystem
|
||||
// looking for memory report dumps to grab a file before we're finished
|
||||
// writing to it.
|
||||
|
||||
// Note that |mrFilename| is missing the "incomplete-" prefix; we'll tack
|
||||
// that on in a moment.
|
||||
nsCString mrFilename;
|
||||
MakeFilename("memory-report", aIdentifier, "json.gz", mrFilename);
|
||||
|
||||
nsCOMPtr<nsIFile> mrTmpFile;
|
||||
nsresult rv;
|
||||
rv = nsMemoryInfoDumper::OpenTempFile(NS_LITERAL_CSTRING("incomplete-") +
|
||||
mrFilename,
|
||||
getter_AddRefs(mrTmpFile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsRefPtr<nsGZFileWriter> mrWriter = new nsGZFileWriter();
|
||||
rv = mrWriter->Init(mrTmpFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Dump the memory reports to the file.
|
||||
DumpProcessMemoryReportsToGZFileWriter(mrWriter);
|
||||
|
||||
#ifdef MOZ_DMD
|
||||
// Create a filename like dmd-<identifier>-<pid>.txt.gz, which will be used
|
||||
// if DMD is enabled.
|
||||
nsCString dmdFilename;
|
||||
MakeFilename("dmd", aIdentifier, "txt.gz", dmdFilename);
|
||||
|
||||
// Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
|
||||
// and dump DMD output to it. This must occur after the memory reporters
|
||||
// have been run (above), but before the memory-reports file has been
|
||||
// renamed (so scripts can detect the DMD file, if present).
|
||||
|
||||
nsCOMPtr<nsIFile> dmdFile;
|
||||
rv = OpenTempFile(dmdFilename, getter_AddRefs(dmdFile));
|
||||
rv = nsMemoryInfoDumper::OpenTempFile(dmdFilename, getter_AddRefs(dmdFile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsRefPtr<nsGZFileWriter> dmdWriter = new nsGZFileWriter();
|
||||
@ -925,16 +891,16 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
#endif // MOZ_DMD
|
||||
|
||||
// The call to Finish() deallocates the memory allocated by the first DUMP()
|
||||
// above. Because that memory was live while the memory reporters ran and
|
||||
// thus measured by them -- by "heap-allocated" if nothing else -- we want
|
||||
// DMD to see it as well. So we deliberately don't call Finish() until after
|
||||
// DMD finishes.
|
||||
rv = writer->Finish();
|
||||
// The call to Finish() deallocates the memory allocated by mrWriter's first
|
||||
// DUMP() call (within DumpProcessMemoryReportsToGZFileWriter()). Because
|
||||
// that memory was live while the memory reporters ran and thus measured by
|
||||
// them -- by "heap-allocated" if nothing else -- we want DMD to see it as
|
||||
// well. So we deliberately don't call Finish() until after DMD finishes.
|
||||
rv = mrWriter->Finish();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Rename the file, now that we're done dumping the report. The file's
|
||||
// ultimate destination is "memory-report<-identifier>-<pid>.json.gz".
|
||||
// Rename the memory reports file, now that we're done writing all the files.
|
||||
// Its final name is "memory-report<-identifier>-<pid>.json.gz".
|
||||
|
||||
nsCOMPtr<nsIFile> mrFinalFile;
|
||||
rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mrFinalFile));
|
||||
@ -953,6 +919,8 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(
|
||||
rv = mrTmpFile->MoveTo(/* directory */ nullptr, mrActualFinalFilename);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Write a message to the console.
|
||||
|
||||
nsCOMPtr<nsIConsoleService> cs =
|
||||
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -967,4 +935,79 @@ nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(
|
||||
return cs->LogStringMessage(msg.get());
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
|
||||
bool aMinimizeMemoryUsage,
|
||||
bool aDumpChildProcesses)
|
||||
{
|
||||
nsString identifier(aIdentifier);
|
||||
EnsureNonEmptyIdentifier(identifier);
|
||||
|
||||
// Kick off memory report dumps in our child processes, if applicable. We
|
||||
// do this before doing our own report because writing a report may be I/O
|
||||
// bound, in which case we want to busy the CPU with other reports while we
|
||||
// work on our own.
|
||||
if (aDumpChildProcesses) {
|
||||
nsTArray<ContentParent*> children;
|
||||
ContentParent::GetAll(children);
|
||||
for (uint32_t i = 0; i < children.Length(); i++) {
|
||||
unused << children[i]->SendDumpMemoryInfoToTempDir(
|
||||
identifier, aMinimizeMemoryUsage, aDumpChildProcesses);
|
||||
}
|
||||
}
|
||||
|
||||
if (aMinimizeMemoryUsage) {
|
||||
// Minimize memory usage, then run DumpMemoryInfoToTempDir again.
|
||||
nsRefPtr<DumpMemoryInfoToTempDirRunnable> callback =
|
||||
new DumpMemoryInfoToTempDirRunnable(identifier,
|
||||
/* minimizeMemoryUsage = */ false,
|
||||
/* dumpChildProcesses = */ false);
|
||||
nsCOMPtr<nsIMemoryReporterManager> mgr =
|
||||
do_GetService("@mozilla.org/memory-reporter-manager;1");
|
||||
NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
|
||||
nsCOMPtr<nsICancelableRunnable> runnable;
|
||||
mgr->MinimizeMemoryUsage(callback, getter_AddRefs(runnable));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return DumpProcessMemoryInfoToTempDir(identifier);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(const nsAString& aFilename)
|
||||
{
|
||||
MOZ_ASSERT(!aFilename.IsEmpty());
|
||||
|
||||
// Create the file.
|
||||
|
||||
nsCOMPtr<nsIFile> mrFile;
|
||||
nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(mrFile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mrFile->InitWithPath(aFilename);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool exists;
|
||||
rv = mrFile->Exists(&exists);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!exists) {
|
||||
rv = mrFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Write the memory reports to the file.
|
||||
|
||||
nsRefPtr<nsGZFileWriter> mrWriter = new nsGZFileWriter();
|
||||
rv = mrWriter->Init(mrFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
DumpProcessMemoryReportsToGZFileWriter(mrWriter);
|
||||
|
||||
rv = mrWriter->Finish();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#undef DUMP
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=50 et cin tw=80 : */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@ -37,10 +37,6 @@ public:
|
||||
* instead.
|
||||
*/
|
||||
static nsresult OpenTempFile(const nsACString &aFilename, nsIFile* *aFile);
|
||||
|
||||
private:
|
||||
static nsresult
|
||||
DumpMemoryReportsToFileImpl(const nsAString& aIdentifier);
|
||||
};
|
||||
|
||||
#define NS_MEMORY_INFO_DUMPER_CID \
|
||||
|
Loading…
x
Reference in New Issue
Block a user