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:
Nicholas Nethercote 2013-03-27 20:31:26 -07:00
parent 9f600a37f0
commit 05d64c5967
12 changed files with 386 additions and 247 deletions

View File

@ -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;
}

View File

@ -116,7 +116,7 @@ public:
RecvAudioChannelNotify();
virtual bool
RecvDumpMemoryReportsToFile(const nsString& aIdentifier,
RecvDumpMemoryInfoToTempDir(const nsString& aIdentifier,
const bool& aMinimizeMemoryUsage,
const bool& aDumpChildProcesses);
virtual bool

View File

@ -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);

View File

@ -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;
}
};

View File

@ -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."}
]
}

View File

@ -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));

View File

@ -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.

View File

@ -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/. */

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 \