Bug 669611 - Add memory reporter for GC heap fragmentation. r=jlebar.

This commit is contained in:
Nicholas Nethercote 2011-07-20 21:08:24 -07:00
parent b4cb3cd58d
commit 29686fd711
4 changed files with 174 additions and 93 deletions

View File

@ -23,6 +23,7 @@
*
* Contributor(s):
* John Bandhauer <jband@netscape.com> (original author)
* Nicholas Nethercote <nnethercote@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -1552,14 +1553,19 @@ public:
NS_NAMED_LITERAL_CSTRING(p, "");
PRInt64 gcHeapChunkTotal = gXPCJSChunkAllocator.GetGCChunkBytesInUse();
// This is initialized to gcHeapChunkUnused, and then we subtract used
// This is initialized to gcHeapChunkTotal, and then we subtract used
// space from it each time around the loop.
PRInt64 gcHeapChunkUnused = gcHeapChunkTotal;
PRInt64 gcHeapArenaUnused = 0;
#define DO(path, kind, amount, desc) \
#define BYTES(path, kind, amount, desc) \
callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_BYTES, \
amount, NS_LITERAL_CSTRING(desc), closure);
#define PERCENTAGE(path, kind, amount, desc) \
callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_PERCENTAGE, \
amount, NS_LITERAL_CSTRING(desc), closure);
// This is the second step (see above).
for (CompartmentStats *stats = data.compartmentStatsVector.begin();
stats != data.compartmentStatsVector.end();
@ -1573,85 +1579,95 @@ public:
stats->gcHeapObjects + stats->gcHeapStrings +
stats->gcHeapShapes + stats->gcHeapXml;
DO(mkPath(name, "gc-heap/arena-headers"),
gcHeapArenaUnused += stats->gcHeapArenaUnused;
BYTES(mkPath(name, "gc-heap/arena-headers"),
JS_GC_HEAP_KIND, stats->gcHeapArenaHeaders,
"Memory on the garbage-collected JavaScript heap, within arenas, that is "
"used to hold internal book-keeping information.");
"Memory on the compartment's garbage-collected JavaScript heap, within "
"arenas, that is used to hold internal book-keeping information.");
DO(mkPath(name, "gc-heap/arena-padding"),
BYTES(mkPath(name, "gc-heap/arena-padding"),
JS_GC_HEAP_KIND, stats->gcHeapArenaPadding,
"Memory on the garbage-collected JavaScript heap, within arenas, that is "
"unused and present only so that other data is aligned.");
"Memory on the compartment's garbage-collected JavaScript heap, within "
"arenas, that is unused and present only so that other data is aligned. "
"This constitutes internal fragmentation.");
DO(mkPath(name, "gc-heap/arena-unused"),
BYTES(mkPath(name, "gc-heap/arena-unused"),
JS_GC_HEAP_KIND, stats->gcHeapArenaUnused,
"Memory on the garbage-collected JavaScript heap, within arenas, that "
"could be holding useful data but currently isn't.");
"Memory on the compartment's garbage-collected JavaScript heap, within "
"arenas, that could be holding useful data but currently isn't.");
DO(mkPath(name, "gc-heap/objects"),
BYTES(mkPath(name, "gc-heap/objects"),
JS_GC_HEAP_KIND, stats->gcHeapObjects,
"Memory on the garbage-collected JavaScript heap that holds objects.");
DO(mkPath(name, "gc-heap/strings"),
JS_GC_HEAP_KIND, stats->gcHeapStrings,
"Memory on the garbage-collected JavaScript heap that holds string "
"headers.");
DO(mkPath(name, "gc-heap/shapes"),
JS_GC_HEAP_KIND, stats->gcHeapShapes,
"Memory on the garbage-collected JavaScript heap that holds shapes. "
"A shape is an internal data structure that makes property accesses "
"fast.");
DO(mkPath(name, "gc-heap/xml"),
JS_GC_HEAP_KIND, stats->gcHeapXml,
"Memory on the garbage-collected JavaScript heap that holds E4X XML "
"Memory on the compartment's garbage-collected JavaScript heap that holds "
"objects.");
DO(mkPath(name, "object-slots"),
BYTES(mkPath(name, "gc-heap/strings"),
JS_GC_HEAP_KIND, stats->gcHeapStrings,
"Memory on the compartment's garbage-collected JavaScript heap that holds "
"string headers. String headers contain various pieces of information "
"about a string, but do not contain (except in the case of very short "
"strings) the string characters; characters in longer strings are counted "
"under 'gc-heap/string-chars' instead.");
BYTES(mkPath(name, "gc-heap/shapes"),
JS_GC_HEAP_KIND, stats->gcHeapShapes,
"Memory on the compartment's garbage-collected JavaScript heap that holds "
"shapes. A shape is an internal data structure that makes JavaScript "
"property accesses fast.");
BYTES(mkPath(name, "gc-heap/xml"),
JS_GC_HEAP_KIND, stats->gcHeapXml,
"Memory on the compartment's garbage-collected JavaScript heap that holds "
"E4X XML objects.");
BYTES(mkPath(name, "object-slots"),
nsIMemoryReporter::KIND_HEAP, stats->objectSlots,
"Memory allocated for non-fixed object slot arrays, which are used "
"to represent object properties. Some objects also contain a fixed "
"number of slots which are stored on the JavaScript heap; those slots "
"are not counted here, but in 'gc-heap/objects'.");
"Memory allocated for the compartment's non-fixed object slot arrays, "
"which are used to represent object properties. Some objects also "
"contain a fixed number of slots which are stored on the compartment's "
"JavaScript heap; those slots are not counted here, but in "
"'gc-heap/objects' instead.");
DO(mkPath(name, "string-chars"),
BYTES(mkPath(name, "string-chars"),
nsIMemoryReporter::KIND_HEAP, stats->stringChars,
"Memory allocated to hold string characters. Not all of this allocated "
"memory is necessarily used to hold characters. Each string also "
"includes a header which is stored on the JavaScript heap; that header "
"is not counted here, but in 'gc-heap/strings'.");
"Memory allocated to hold the compartment's string characters. Sometimes "
"more memory is allocated than necessary, to simplify string "
"concatenation. Each string also includes a header which is stored on the "
"compartment's JavaScript heap; that header is not counted here, but in "
"'gc-heap/strings' instead.");
DO(mkPath(name, "scripts"),
BYTES(mkPath(name, "scripts"),
nsIMemoryReporter::KIND_HEAP, stats->scripts,
"Memory allocated for JSScripts. A JSScript is created for each "
"user-defined function in a script. One is also created for "
"Memory allocated for the compartment's JSScripts. A JSScript is created "
"for each user-defined function in a script. One is also created for "
"the top-level code in a script. Each JSScript includes byte-code and "
"various other things.");
#ifdef JS_METHODJIT
DO(mkPath(name, "mjit-code"),
BYTES(mkPath(name, "mjit-code"),
nsIMemoryReporter::KIND_NONHEAP, stats->mjitCode,
"Memory used by the method JIT to hold generated code.");
"Memory used by the method JIT to hold the compartment's generated code.");
DO(mkPath(name, "mjit-data"),
BYTES(mkPath(name, "mjit-data"),
nsIMemoryReporter::KIND_HEAP, stats->mjitData,
"Memory used by the method JIT for the following data: "
"Memory used by the method JIT for the compartment's compilation data: "
"JITScripts, native maps, and inline cache structs.");
#endif
#ifdef JS_TRACER
DO(mkPath(name, "tjit-code"),
BYTES(mkPath(name, "tjit-code"),
nsIMemoryReporter::KIND_NONHEAP, stats->tjitCode,
"Memory used by the trace JIT to hold generated code.");
"Memory used by the trace JIT to hold the compartment's generated code.");
DO(mkPath(name, "tjit-data/allocators-main"),
BYTES(mkPath(name, "tjit-data/allocators-main"),
nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsMain,
"Memory used by the trace JIT's VMAllocators.");
"Memory used by the trace JIT to store the compartment's trace-related "
"data. This data is allocated via the compartment's VMAllocators.");
DO(mkPath(name, "tjit-data/allocators-reserve"),
BYTES(mkPath(name, "tjit-data/allocators-reserve"),
nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsReserve,
"Memory used by the trace JIT and held in reserve for VMAllocators "
"in case of OOM.");
"Memory used by the trace JIT and held in reserve for the compartment's "
"VMAllocators in case of OOM.");
#endif
}
@ -1662,16 +1678,40 @@ public:
PRInt64 gcHeapChunkAdmin = numChunks * perChunkAdmin;
gcHeapChunkUnused -= gcHeapChunkAdmin;
DO(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-unused"),
// Why 10000x? 100x because it's a percentage, and another 100x
// because nsIMemoryReporter requires that for percentage amounts so
// they can be fractional.
PRInt64 gcHeapUnusedPercentage =
(gcHeapChunkUnused + gcHeapArenaUnused) * 10000 /
gXPCJSChunkAllocator.GetGCChunkBytesInUse();
BYTES(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-unused"),
JS_GC_HEAP_KIND, gcHeapChunkUnused,
"Memory on the garbage-collected JavaScript heap, within chunks, that "
"could be holding useful data but currently isn't.");
DO(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-admin"),
BYTES(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"),
nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused,
"The same as 'explicit/js/gc-heap-chunk-unused'. Shown here for "
"easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'.");
BYTES(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-admin"),
JS_GC_HEAP_KIND, gcHeapChunkAdmin,
"Memory on the garbage-collected JavaScript heap, within chunks, that is "
"used to hold internal book-keeping information.");
BYTES(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"),
nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused,
"Memory on the garbage-collected JavaScript heap, within arenas, that "
"could be holding useful data but currently isn't. This is the sum of "
"all compartments' 'gc-heap/arena-unused' numbers.");
PERCENTAGE(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"),
nsIMemoryReporter::KIND_OTHER, gcHeapUnusedPercentage,
"Fraction of the garbage-collected JavaScript heap that is unused. "
"Computed as ('js-gc-heap-chunk-unused' + 'js-gc-heap-arena-unused') / "
"'js-gc-heap'.");
return NS_OK;
}
};

View File

@ -48,11 +48,12 @@ var gVerbose = (location.href.split(/[\?,]/).indexOf("verbose") !== -1);
var gAddedObserver = false;
const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const KIND_OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const UNITS_BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const KIND_OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const UNITS_BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
const kUnknown = -1; // used for _amount if a memory reporter failed
@ -258,24 +259,22 @@ function update()
content.appendChild(div);
}
// Compare two memory reporter nodes. The primary sort is on the _units
// property. The secondary sort is on the _path property if the _units is
// UNIT_COUNT, otherwise it is on the _amount property.
function cmpAmount(a, b)
// Compare two 'explicit' memory reporter nodes.
function cmpExplicitReporters(a, b)
{
if (a._units != b._units) {
return a._units - b._units; // use the enum order from nsIMemoryReporter
}
if (a._units == UNITS_COUNT) {
if (a._path < b._path)
return -1;
if (a._path > b._path)
return 1;
return 0;
}
assert(a._units === undefined && b._units === undefined,
"'explicit' tree nodes must not have _units defined");
return b._amount - a._amount;
};
// Compare two memory reporter nodes from the 'other measurements' list.
function cmpOtherReporters(a, b)
{
return a._path < b._path ? -1 :
a._path > b._path ? 1 :
0;
};
/**
* Generates the text for a single process.
*
@ -302,6 +301,8 @@ function genProcessText(aProcess, aReporters)
* _hasProblem: boolean; (only defined if 'true')
* _nMerged: number; (only defined if >= 2)
* }
* _units isn't needed because it's always UNITS_BYTES for 'explicit'
* reporters.
*/
function buildTree()
{
@ -319,9 +320,9 @@ function genProcessText(aProcess, aReporters)
}
// We want to process all reporters that begin with 'treeName'. First we
// build the tree but only filling in '_name', '_kind', '_units', '_kids',
// maybe '_hasReporter' and maybe '_nMerged'. This is done top-down from
// the reporters.
// build the tree but only fill in '_name', '_kind', '_kids', maybe
// '_hasReporter' and maybe '_nMerged'. This is done top-down from the
// reporters.
var t = {
_name: "falseRoot",
_kind: KIND_OTHER,
@ -330,6 +331,9 @@ function genProcessText(aProcess, aReporters)
for (var path in aReporters) {
var r = aReporters[path];
if (r._path.slice(0, treeName.length) === treeName) {
assert(r._kind === KIND_HEAP || r._kind === KIND_NONHEAP,
"reporters in the tree must have KIND_HEAP or KIND_NONHEAP");
assert(r._units === UNITS_BYTES);
var names = r._path.split('/');
var u = t;
for (var i = 0; i < names.length; i++) {
@ -471,7 +475,7 @@ function genProcessText(aProcess, aReporters)
*/
function filterTree(aT)
{
aT._kids.sort(cmpAmount);
aT._kids.sort(cmpExplicitReporters);
for (var i = 0; i < aT._kids.length; i++) {
if (shouldOmit(aT._kids[i]._amount)) {
@ -499,7 +503,7 @@ function genProcessText(aProcess, aReporters)
// sum of the omitted sub-trees may be larger than some of the
// shown sub-trees.
aT._kids[i0] = rSub;
aT._kids.sort(cmpAmount);
aT._kids.sort(cmpExplicitReporters);
break;
}
filterTree(aT._kids[i]);
@ -530,9 +534,10 @@ function genProcessText(aProcess, aReporters)
function formatReporterAmount(aReporter)
{
switch(aReporter._units) {
case UNITS_BYTES: return formatBytes(aReporter._amount);
case UNITS_COUNT: return formatInt(aReporter._amount);
default: return "(???)"
case UNITS_BYTES: return formatBytes(aReporter._amount);
case UNITS_COUNT: return formatInt(aReporter._amount);
case UNITS_PERCENTAGE: return formatPercentage(aReporter._amount);
default: return "(???)"
}
}
@ -592,6 +597,18 @@ function formatBytes(aBytes)
return s;
}
/**
* Converts a percentage to an appropriate string representation.
*
* @param aPerc100x
* The percentage, multiplied by 100 (see nsIMemoryReporter)
* @return The string representation
*/
function formatPercentage(aPerc100x)
{
return (aPerc100x / 100).toFixed(2) + "%";
}
/**
* Right-justifies a string in a field of a given width, padding as necessary
*
@ -879,12 +896,14 @@ function genOtherText(aReporters)
}
}
}
rArray.sort(cmpAmount);
rArray.sort(cmpOtherReporters);
// Generate text for the not-yet-printed values.
var text = "";
for (var i = 0; i < rArray.length; i++) {
var elem = rArray[i];
assert(elem._kind === KIND_OTHER,
"elem._kind is not KIND_OTHER for " + elem._path);
text += genMrValueText(
pad(formatReporterAmount(elem), maxAmountLength, ' ')) + " ";
text += genMrNameText(elem._kind, elem._description, elem._path,
@ -898,6 +917,13 @@ function genOtherText(aReporters)
"<pre>" + text + "</pre>\n";
}
function assert(aCond, aMsg)
{
if (!aCond) {
throw("assertion failed: " + aMsg);
}
}
function debug(x)
{
var content = $("content");

View File

@ -51,8 +51,9 @@
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
function f2(aProcess, aPath, aKind, aUnits, aAmount) {
return {
@ -100,8 +101,10 @@
{ collectReports: function(cbObj, closure) {
function f(p, k, u, a) { cbObj.callback("", p, k, u, a, "(desc)", closure); }
f("explicit/g", HEAP, BYTES, 14 * MB); // internal
f("other2", OTHER, BYTES, 222 * MB);
f("other3", OTHER, COUNT, 777);
f("other2", OTHER, BYTES, 222 * MB);
f("perc2", OTHER, PERCENTAGE, 10000);
f("perc1", OTHER, PERCENTAGE, 4567);
}
}
];
@ -192,11 +195,13 @@ Explicit Allocations\n\
\n\
Other Measurements\n\
500.00 MB -- heap-allocated\n\
222.00 MB -- other2\n\
111.00 MB -- other1\n\
100.00 MB -- heap-unallocated\n\
111.00 MB -- other1\n\
222.00 MB -- other2\n\
777 -- other3\n\
888 -- other4\n\
45.67% -- perc1\n\
100.00% -- perc2\n\
(???) -- unknown-unit\n\
\n\
2nd Process\n\
@ -211,10 +216,10 @@ Explicit Allocations\n\
└────101.00 MB (10.10%) -- heap-unclassified\n\
\n\
Other Measurements\n\
1,000.00 MB -- heap-allocated\n\
666.00 MB -- danger<script>window.alert(1)</script>\n\
111.00 MB -- other1\n\
1,000.00 MB -- heap-allocated\n\
100.00 MB -- heap-unallocated\n\
111.00 MB -- other1\n\
\n\
3rd Process\n\
\n\
@ -263,11 +268,13 @@ Explicit Allocations\n\
\n\
Other Measurements\n\
524,288,000 B -- heap-allocated\n\
232,783,872 B -- other2\n\
116,391,936 B -- other1\n\
104,857,600 B -- heap-unallocated\n\
116,391,936 B -- other1\n\
232,783,872 B -- other2\n\
777 -- other3\n\
888 -- other4\n\
45.67% -- perc1\n\
100.00% -- perc2\n\
(???) -- unknown-unit\n\
\n\
2nd Process\n\
@ -282,10 +289,10 @@ Explicit Allocations\n\
└────105,906,176 B (10.10%) -- heap-unclassified\n\
\n\
Other Measurements\n\
1,048,576,000 B -- heap-allocated\n\
698,351,616 B -- danger<script>window.alert(1)</script>\n\
116,391,936 B -- other1\n\
1,048,576,000 B -- heap-allocated\n\
104,857,600 B -- heap-unallocated\n\
116,391,936 B -- other1\n\
\n\
3rd Process\n\
\n\

View File

@ -140,9 +140,17 @@ interface nsIMemoryReporter : nsISupports
* has occurred since the application started up. For instance, a
* reporter reporting the number of page faults since startup should have
* units UNITS_COUNT.
*
* - PERCENTAGE: The amount contains a fraction that should be expressed as
* a percentage. NOTE! The |amount| field should be given a value 100x
* the actual percentage; this number will be divided by 100 when shown.
* This allows a fractional percentage to be shown even though |amount| is
* an integer. E.g. if the actual percentage is 12.34%, |amount| should
* be 1234.
*/
const PRInt32 UNITS_BYTES = 0;
const PRInt32 UNITS_COUNT = 1;
const PRInt32 UNITS_PERCENTAGE = 2;
/*
* The units on the reporter's amount. See UNITS_* above.