Bug 719335 - Allow arbitrary collapsing/expanding of sub-trees in about:memory. r=jlebar.

--HG--
extra : rebase_source : fc26877e0c181a77c2ec15967ab33b75d1f96606
This commit is contained in:
Nicholas Nethercote 2012-01-26 14:02:49 -08:00
Родитель f7ec7b1eda
Коммит 694f1deae5
5 изменённых файлов: 567 добавлений и 235 удалений

Просмотреть файл

@ -35,10 +35,29 @@
*
* ***** END LICENSE BLOCK ***** */
body.verbose {
/* override setting in about.css */
max-width: 100% !important;
}
body.non-verbose pre.tree {
overflow-x: hidden;
text-overflow: ellipsis;
}
.sectionHeader {
background: #ddd;
padding-left: .1em;
}
.accuracyWarning {
color: #f00;
}
.treeLine {
color: #888;
}
.mrValue {
font-weight: bold;
color: #400;
@ -47,20 +66,23 @@
.mrPerc {
}
.mrName {
color: #004;
.mrSep {
}
.hasDesc:hover {
text-decoration: underline;
.mrName {
color: #004;
}
.mrStar {
color: #604;
}
.treeLine {
color: #888;
.hasKids {
cursor: pointer;
}
.hasKids:hover {
text-decoration: underline;
}
.option {
@ -73,22 +95,6 @@
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
body.verbose {
/* override setting in about.css */
max-width: 100% !important;
}
h2.tree {
cursor: pointer;
background: #ddd;
padding-left: .1em;
}
body.non-verbose pre.tree {
overflow-x: hidden;
text-overflow: ellipsis;
}
pre.collapsed {
.hidden {
display: none;
}

Просмотреть файл

@ -199,17 +199,6 @@ function sendHeapMinNotifications()
sendHeapMinNotificationsInner();
}
function toggleTreeVisibility(aEvent)
{
var headerElem = aEvent.target;
// Replace "header-" with "pre-" in the header element's id to get the id of
// the corresponding pre element.
var treeElem = $(headerElem.id.replace(/^header-/, 'pre-'));
treeElem.classList.toggle('collapsed');
}
function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
{
this._path = aPath;
@ -337,6 +326,7 @@ function update()
}
// Memory-related actions.
const UpDesc = "Re-measure.";
const GCDesc = "Do a global garbage collection.";
const CCDesc = "Do a cycle collection.";
const MPDesc = "Send three \"heap-minimize\" notifications in a " +
@ -345,7 +335,9 @@ function update()
"process to reduce memory usage in other ways, e.g. by " +
"flushing various caches.";
// The "Update" button has an id so it can be clicked in a test.
text += "<div>" +
"<button title='" + UpDesc + "' onclick='update()' id='updateButton'>Update</button>" +
"<button title='" + GCDesc + "' onclick='doGlobalGC()'>GC</button>" +
"<button title='" + CCDesc + "' onclick='doCC()'>CC</button>" +
"<button title='" + MPDesc + "' onclick='sendHeapMinNotifications()'>" + "Minimize memory usage</button>" +
@ -363,10 +355,12 @@ function update()
"</div>";
text += "<div>" +
"<span class='legend'>Hover the pointer over the name of a memory " +
"reporter to see a detailed description of what it measures. Click a " +
"heading to expand or collapse its tree.</span>" +
"<span class='legend'>Click on a non-leaf node in a tree to expand ('++') " +
"or collapse ('--') its children.</span>" +
"</div>";
text += "<div>" +
"<span class='legend'>Hover the pointer over the name of a memory " +
"reporter to see a description of what it measures.</span>";
var div = document.createElement("div");
div.innerHTML = text;
@ -374,7 +368,7 @@ function update()
}
// There are two kinds of TreeNode.
// - Leaf TreeNodes correspond to Reporters and have more properties.
// - Leaf TreeNodes correspond to Reporters and have more properties.
// - Non-leaf TreeNodes are just scaffolding nodes for the tree; their values
// are derived from their children.
function TreeNode(aName)
@ -390,6 +384,9 @@ function TreeNode(aName)
// - _kind
// - _nMerged (if > 1)
// - _hasProblem (only defined if true)
//
// Non-leaf TreeNodes have these properties added later:
// - _hideKids (only defined if true)
}
TreeNode.prototype = {
@ -524,6 +521,25 @@ function buildTree(aReporters, aTreeName)
return t;
}
/**
* Ignore all the memory reporters that belong to a tree; this involves
* explicitly marking them as done.
*
* @param aReporters
* The table of Reporters, indexed by path.
* @param aTreeName
* The name of the tree being built.
*/
function ignoreTree(aReporters, aTreeName)
{
for (var path in aReporters) {
var r = aReporters[path];
if (r.treeNameMatches(aTreeName)) {
var dummy = getBytes(aReporters, path);
}
}
}
/**
* Do some work which only makes sense for the 'explicit' tree.
*
@ -581,58 +597,81 @@ function fixUpExplicitTree(aT, aReporters)
}
/**
* Sort all kid nodes from largest to smallest and aggregate insignificant
* nodes.
* Sort all kid nodes from largest to smallest, and insert aggregate nodes
* where appropriate.
*
* @param aTotalBytes
* The size of the tree's root node.
* @param aT
* The tree.
*/
function filterTree(aTotalBytes, aT)
function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
{
const omitThresholdPerc = 0.5; /* percent */
const kSignificanceThresholdPerc = 1;
function shouldOmit(aBytes)
function isInsignificant(aT)
{
return !gVerbose &&
aTotalBytes !== kUnknown &&
(100 * aBytes / aTotalBytes) < omitThresholdPerc;
(100 * aT._amount / aTotalBytes) < kSignificanceThresholdPerc;
}
if (aT._kids.length === 0) {
return;
}
aT._kids.sort(TreeNode.compare);
for (var i = 0; i < aT._kids.length; i++) {
if (shouldOmit(aT._kids[i]._amount)) {
// This sub-tree is below the significance threshold
// Remove it and all remaining (smaller) sub-trees, and
// replace them with a single aggregate node.
// If the first child is insignificant, they all are, and there's no point
// creating an aggregate node that lacks siblings. Just set the parent's
// _hideKids property and process all children.
if (isInsignificant(aT._kids[0])) {
aT._hideKids = true;
for (var i = 0; i < aT._kids.length; i++) {
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
}
return;
}
// Look at all children except the last one.
for (var i = 0; i < aT._kids.length - 1; i++) {
if (isInsignificant(aT._kids[i])) {
// This child is below the significance threshold. If there are other
// (smaller) children remaining, move them under an aggregate node.
var i0 = i;
var nAgg = aT._kids.length - i0;
// Create an aggregate node.
var aggT = new TreeNode("(" + nAgg + " tiny)");
var aggBytes = 0;
for ( ; i < aT._kids.length; i++) {
aggBytes += aT._kids[i]._amount;
aggT._kids.push(aT._kids[i]);
}
aT._kids.splice(i0, aT._kids.length);
var n = i - i0;
var rSub = new TreeNode("(" + n + " omitted)");
rSub._amount = aggBytes;
rSub._description =
n + " sub-trees that were below the " + omitThresholdPerc +
"% significance threshold. Click 'More verbose' at the bottom of " +
"this page to see them.";
// Add the "omitted" sub-tree at the end and then re-sort, because the
// sum of the omitted sub-trees may be larger than some of the shown
// sub-trees.
aT._kids[i0] = rSub;
aggT._hideKids = true;
aggT._amount = aggBytes;
aggT._description =
nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
"% significance threshold.";
aT._kids.splice(i0, nAgg, aggT);
aT._kids.sort(TreeNode.compare);
break;
// Process the moved children.
for (i = 0; i < aggT._kids.length; i++) {
sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
}
return;
}
filterTree(aTotalBytes, aT._kids[i]);
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
}
// The first n-1 children were significant. Don't consider if the last child
// is significant; there's no point creating an aggregate node that only has
// one child. Just process it.
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
}
function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
{
var warningText = "";
@ -677,7 +716,7 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
{
var explicitTree = buildTree(aReporters, 'explicit');
var hasKnownHeapAllocated = fixUpExplicitTree(explicitTree, aReporters);
filterTree(explicitTree._amount, explicitTree);
sortTreeAndInsertAggregateNodes(explicitTree._amount, explicitTree);
var explicitText = genTreeText(explicitTree, aProcess);
// Generate any warnings about inaccuracies due to platform limitations.
@ -687,14 +726,20 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
var warningText =
genWarningText(hasKnownHeapAllocated, aHasMozMallocUsableSize);
var mapTreeText = '';
// We only show these breakdown trees in verbose mode.
var mapTreeText = "";
kMapTreePaths.forEach(function(t) {
var tree = buildTree(aReporters, t);
if (gVerbose) {
var tree = buildTree(aReporters, t);
// |tree| will be null if we don't have any reporters for the given path.
if (tree) {
filterTree(tree._amount, tree);
mapTreeText += genTreeText(tree, aProcess);
// |tree| will be null if we don't have any reporters for the given path.
if (tree) {
sortTreeAndInsertAggregateNodes(tree._amount, tree);
tree._hideKids = true; // map trees are always initially collapsed
mapTreeText += genTreeText(tree, aProcess);
}
} else {
ignoreTree(aReporters, t);
}
});
@ -835,9 +880,18 @@ function getDescription(aReporters, aPath)
return r._description;
}
// There's a subset of the Unicode "light" box-drawing chars that are widely
// implemented in terminals, and this code sticks to that subset to maximize
// the chance that cutting and pasting about:memory output to a terminal will
// work correctly:
const kHorizontal = "\u2500",
kVertical = "\u2502",
kUpAndRight = "\u2514",
kVerticalAndRight = "\u251c";
function genMrValueText(aValue)
{
return "<span class='mrValue'>" + aValue + "</span>";
return "<span class='mrValue'>" + aValue + " </span>";
}
function kindToString(aKind)
@ -861,7 +915,7 @@ function escapeAll(aStr)
// Compartment reporter names are URLs and so can include forward slashes. But
// forward slash is the memory reporter path separator. So the memory
// reporters change them to backslashes. Undo that here.
// reporters change them to backslashes. Undo that here.
function flipBackslashes(aStr)
{
return aStr.replace(/\\/g, '/');
@ -877,25 +931,86 @@ function prepDesc(aStr)
return escapeAll(flipBackslashes(aStr));
}
function genMrNameText(aKind, aDesc, aName, aHasProblem, aNMerged)
function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
aHasProblem, aNMerged)
{
var text = "-- <span class='mrName hasDesc' title='" +
kindToString(aKind) + prepDesc(aDesc) +
"'>" + prepName(aName) + "</span>";
var text = "";
if (aHasKids) {
if (aShowSubtrees) {
text += "<span class='mrSep hidden'>++ </span>";
text += "<span class='mrSep'>-- </span>";
} else {
text += "<span class='mrSep'>++ </span>";
text += "<span class='mrSep hidden'>-- </span>";
}
} else {
text += "<span class='mrSep'>" + kHorizontal + kHorizontal + " </span>";
}
text += "<span class='mrName' title='" +
kindToString(aKind) + prepDesc(aDesc) + "'>" +
prepName(aName) + "</span>";
if (aHasProblem) {
const problemDesc =
"Warning: this memory reporter was unable to compute a useful value. ";
text += " <span class='mrStar' title=\"" + problemDesc + "\">[*]</span>";
text += "<span class='mrStar' title=\"" + problemDesc + "\"> [*]</span>";
}
if (aNMerged) {
const dupDesc = "This value is the sum of " + aNMerged +
" memory reporters that all have the same path.";
text += " <span class='mrStar' title=\"" + dupDesc + "\">[" +
text += "<span class='mrStar' title=\"" + dupDesc + "\"> [" +
aNMerged + "]</span>";
}
return text + '\n';
}
// This is used to record which sub-trees have been toggled, so the
// collapsed/expanded state can be replicated when the page is regenerated.
// It can end up holding IDs of nodes that no longer exist, e.g. for
// compartments that have been closed. This doesn't seem like a big deal,
// because the number is limited by the number of entries the user has changed
// from their original state.
var gToggles = {};
function toggle(aEvent)
{
// This relies on each line being a span that contains at least five spans:
// mrValue, mrPerc, mrSep ('++'), mrSep ('--'), mrName, and then zero or more
// mrStars. All whitespace must be within one of these spans for this
// function to find the right nodes. And the span containing the children of
// this line must immediately follow. Assertions check this.
function assertClassName(span, className) {
assert(span, "undefined " + className);
assert(span.nodeName === "span", "non-span " + className);
assert(span.classList.contains(className), "bad " + className);
}
// |aEvent.target| will be one of the five spans. Get the outer span.
var outerSpan = aEvent.target.parentNode;
assertClassName(outerSpan, "hasKids");
// Toggle visibility of the '++' and '--' separators.
var plusSpan = outerSpan.childNodes[2];
var minusSpan = outerSpan.childNodes[3];
assertClassName(plusSpan, "mrSep");
assertClassName(minusSpan, "mrSep");
plusSpan .classList.toggle("hidden");
minusSpan.classList.toggle("hidden");
// Toggle visibility of the span containing this node's children.
var subTreeSpan = outerSpan.nextSibling;
assertClassName(subTreeSpan, "kids");
subTreeSpan.classList.toggle("hidden");
// Record/unrecord that this sub-tree was toggled.
var treeId = outerSpan.id;
if (gToggles[treeId]) {
delete gToggles[treeId];
} else {
gToggles[treeId] = true;
}
}
/**
* Generates the text for the tree, including its heading.
*
@ -914,6 +1029,8 @@ function genTreeText(aT, aProcess)
/**
* Generates the text for a particular tree, without a heading.
*
* @param aPrePath
* The partial path leading up to this node.
* @param aT
* The tree.
* @param aIndentGuide
@ -925,7 +1042,7 @@ function genTreeText(aT, aProcess)
* The length of the formatted byte count of the top node in the tree.
* @return The generated text.
*/
function genTreeText2(aT, aIndentGuide, aParentStringLength)
function genTreeText2(aPrePath, aT, aIndentGuide, aParentStringLength)
{
function repeatStr(aC, aN)
{
@ -936,15 +1053,7 @@ function genTreeText(aT, aProcess)
return s;
}
// Generate the indent. There's a subset of the Unicode "light"
// box-drawing chars that are widely implemented in terminals, and
// this code sticks to that subset to maximize the chance that
// cutting and pasting about:memory output to a terminal will work
// correctly:
const kHorizontal = "\u2500",
kVertical = "\u2502",
kUpAndRight = "\u2514",
kVerticalAndRight = "\u251c";
// Generate the indent.
var indent = "<span class='treeLine'>";
if (aIndentGuide.length > 0) {
for (var i = 0; i < aIndentGuide.length - 1; i++) {
@ -954,7 +1063,6 @@ function genTreeText(aT, aProcess)
indent += aIndentGuide[i]._isLastKid ? kUpAndRight : kVerticalAndRight;
indent += repeatStr(kHorizontal, aIndentGuide[i]._depth - 1);
}
// Indent more if this entry is narrower than its parent, and update
// aIndentGuide accordingly.
var tString = aT.toString();
@ -967,40 +1075,66 @@ function genTreeText(aT, aProcess)
}
indent += "</span>";
// Generate the percentage.
var perc = "";
// Generate the percentage, and determine if we should show subtrees.
var percText = "";
var showSubtrees = !aT._hideKids;
if (aT._amount === treeBytes) {
perc = "100.0";
percText = "100.0";
} else {
perc = (100 * aT._amount / treeBytes).toFixed(2);
perc = pad(perc, 5, '0');
var perc = (100 * aT._amount / treeBytes);
percText = (100 * aT._amount / treeBytes).toFixed(2);
percText = pad(percText, 5, '0');
}
percText = "<span class='mrPerc'>(" + percText + "%) </span>";
// Reinstate any previous toggling of this sub-tree.
var path = aPrePath + aT._name;
var treeId = escapeAll(aProcess + ":" + path);
if (gToggles[treeId]) {
showSubtrees = !showSubtrees;
}
perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
// We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
// whole tree is non-heap.
var kind = isExplicitTree ? aT._kind : undefined;
var text = indent + genMrValueText(tString) + " " + perc +
genMrNameText(kind, aT._description, aT._name,
aT._hasProblem, aT._nMerged);
// For non-leaf nodes, the entire sub-tree is put within a span so it can
// be collapsed if the node is clicked on.
var hasKids = aT._kids.length > 0;
if (!hasKids) {
assert(!aT._hideKids, "leaf node with _hideKids set")
}
var text = indent;
if (hasKids) {
text +=
"<span onclick='toggle(event)' class='hasKids' id='" + treeId + "'>";
}
text += genMrValueText(tString) + percText;
text += genMrNameText(kind, showSubtrees, hasKids, aT._description,
aT._name, aT._hasProblem, aT._nMerged);
if (hasKids) {
var hiddenText = showSubtrees ? "" : " hidden";
// The 'kids' class is just used for sanity checking in toggle().
text += "</span><span class='kids" + hiddenText + "'>";
}
for (var i = 0; i < aT._kids.length; i++) {
// 3 is the standard depth, the callee adjusts it if necessary.
aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
text += genTreeText2(aT._kids[i], aIndentGuide, tString.length);
text += genTreeText2(path + "/", aT._kids[i], aIndentGuide,
tString.length);
aIndentGuide.pop();
}
text += hasKids ? "</span>" : "";
return text;
}
var text = genTreeText2(aT, [], rootStringLength);
var text = genTreeText2(/* prePath = */"", aT, [], rootStringLength);
// The explicit tree is not collapsed, but all other trees are, so pass
// !isExplicitTree for genSectionMarkup's aCollapsed parameter.
return genSectionMarkup(aProcess, aT._name, text, !isExplicitTree);
return genSectionMarkup(aT._name, text);
}
function OtherReporter(aPath, aUnits, aAmount, aDescription,
function OtherReporter(aPath, aUnits, aAmount, aDescription,
aNMerged)
{
// Nb: _kind is not needed, it's always KIND_OTHER.
@ -1055,7 +1189,7 @@ function genOtherText(aReportersByProcess, aProcess)
var r = aReportersByProcess[path];
if (!r._done) {
assert(r._kind === KIND_OTHER, "_kind !== KIND_OTHER for " + r._path);
assert(r.nMerged === undefined); // we don't allow dup'd OTHER reporters
assert(r.nMerged === undefined); // we don't allow dup'd OTHER reporters
var hasProblem = false;
if (r._amount === kUnknown) {
hasProblem = true;
@ -1073,29 +1207,19 @@ function genOtherText(aReportersByProcess, aProcess)
var text = "";
for (var i = 0; i < otherReporters.length; i++) {
var o = otherReporters[i];
text += genMrValueText(pad(o.asString, maxStringLength, ' ')) + " ";
text += genMrNameText(KIND_OTHER, o._description, o._path, o._hasProblem);
text += genMrValueText(pad(o.asString, maxStringLength, ' '));
text += genMrNameText(KIND_OTHER, /* showSubtrees = */true,
/* hasKids = */false, o._description, o._path,
o._hasProblem);
}
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
const desc = "This list contains other memory measurements that cross-cut " +
"the requested memory measurements above."
return genSectionMarkup(aProcess, 'other', text, false);
return genSectionMarkup('other', text);
}
function genSectionMarkup(aProcess, aName, aText, aCollapsed)
function genSectionMarkup(aName, aText)
{
var headerId = 'header-' + aProcess + '-' + aName;
var preId = 'pre-' + aProcess + '-' + aName;
var elemClass = (aCollapsed ? 'collapsed' : '') + ' tree';
// Ugh.
return '<h2 id="' + headerId + '" class="' + elemClass + '" ' +
'onclick="toggleTreeVisibility(event)">' +
kTreeNames[aName] +
'</h2>\n' +
'<pre id="' + preId + '" class="' + elemClass + '">' + aText + '</pre>\n';
return "<h2 class='sectionHeader'>" + kTreeNames[aName] + "</h2>\n" +
"<pre class='tree'>" + aText + "</pre>\n";
}
function assert(aCond, aMsg)

Просмотреть файл

@ -46,6 +46,7 @@ include $(topsrcdir)/config/rules.mk
_CHROME_FILES = \
test_aboutmemory.xul \
test_aboutmemory2.xul \
test_sqliteMultiReporter.xul \
$(NULL)

Просмотреть файл

@ -123,6 +123,7 @@
f("map/vsize/a", 19);
f("map/swap/b/c", 10);
f("map/resident/a", 42);
f("map/pss/a", 43);
},
explicitNonHeap: 0
}
@ -186,50 +187,33 @@ Main Process\n\
Explicit Allocations\n\
623.58 MB (100.0%) -- explicit\n\
├──232.00 MB (37.20%) -- b\n\
│ ├───85.00 MB (13.63%) -- a\n\
│ ├───75.00 MB (12.03%) -- b\n\
│ ├───85.00 MB (13.63%) ── a\n\
│ ├───75.00 MB (12.03%) ── b\n\
│ └───72.00 MB (11.55%) -- c\n\
│ ├──70.00 MB (11.23%) -- a\n\
│ └───2.00 MB (00.32%) -- (1 omitted)\n\
├──222.00 MB (35.60%) -- a\n\
│ ├──70.00 MB (11.23%) ── a\n\
│ └───2.00 MB (00.32%) ── b\n\
├──222.00 MB (35.60%) ── a\n\
├──100.00 MB (16.04%) -- c\n\
│ ├───77.00 MB (12.35%) -- other\n\
│ └───23.00 MB (03.69%) -- d [2]\n\
├───23.00 MB (03.69%) -- cc [2]\n\
│ ├───77.00 MB (12.35%) ── other\n\
│ └───23.00 MB (03.69%) ── d [2]\n\
├───23.00 MB (03.69%) ── cc [2]\n\
├───20.00 MB (03.21%) -- f\n\
│ └──20.00 MB (03.21%) -- g\n\
│ └──20.00 MB (03.21%) -- h\n\
│ └──20.00 MB (03.21%) -- i\n\
├───15.00 MB (02.41%) -- g\n\
│ ├───6.00 MB (00.96%) -- a\n\
│ ├───5.00 MB (00.80%) -- b\n\
│ └───4.00 MB (00.64%) -- other\n\
├───11.00 MB (01.76%) -- heap-unclassified\n\
└────0.58 MB (00.09%) -- (2 omitted)\n\
\n\
Resident Set Size (RSS) Breakdown\n\
0.16 MB (100.0%) -- resident\n\
└──0.16 MB (100.0%) -- a\n\
\n\
Virtual Size Breakdown\n\
0.17 MB (100.0%) -- vsize\n\
└──0.17 MB (100.0%) -- a [2]\n\
\n\
Swap Usage Breakdown\n\
0.05 MB (100.0%) -- swap\n\
├──0.04 MB (76.92%) -- b\n\
│ └──0.04 MB (76.92%) -- c\n\
└──0.01 MB (23.08%) -- a [2]\n\
│ └──20.00 MB (03.21%) ── i\n\
├───15.00 MB (02.41%) ++ g\n\
├───11.00 MB (01.76%) ── heap-unclassified\n\
└────0.58 MB (00.09%) ++ (2 tiny)\n\
\n\
Other Measurements\n\
500.00 MB -- heap-allocated\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\
500.00 MB ── heap-allocated\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\
\n\
2nd Process\n\
\n\
@ -237,16 +221,16 @@ Explicit Allocations\n\
1,000.00 MB (100.0%) -- explicit\n\
├────499.00 MB (49.90%) -- a\n\
│ └──499.00 MB (49.90%) -- b\n\
│ └──499.00 MB (49.90%) -- c [3]\n\
├────200.00 MB (20.00%) -- flip/the/backslashes\n\
├────200.00 MB (20.00%) -- compartment(compartment-url)\n\
└────101.00 MB (10.10%) -- heap-unclassified\n\
│ └──499.00 MB (49.90%) ── c [3]\n\
├────200.00 MB (20.00%) ── flip/the/backslashes\n\
├────200.00 MB (20.00%) ── compartment(compartment-url)\n\
└────101.00 MB (10.10%) ── heap-unclassified\n\
\n\
Other Measurements\n\
666.00 MB -- danger<script>window.alert(1)</script>\n\
1,000.00 MB -- heap-allocated\n\
100.00 MB -- heap-unallocated\n\
111.00 MB -- other1\n\
666.00 MB ── danger<script>window.alert(1)</script>\n\
1,000.00 MB ── heap-allocated\n\
100.00 MB ── heap-unallocated\n\
111.00 MB ── other1\n\
\n\
3rd Process\n\
\n\
@ -255,14 +239,14 @@ WARNING: the 'heap-allocated' memory reporter does not work for this platform an
Explicit Allocations\n\
777.00 MB (100.0%) -- explicit\n\
├──777.00 MB (100.0%) -- a\n\
│ ├──444.00 MB (57.14%) -- c [2]\n\
│ ├──333.00 MB (42.86%) -- b\n\
│ └────0.00 MB (00.00%) -- (1 omitted)\n\
└────0.00 MB (00.00%) -- (2 omitted)\n\
│ ├──444.00 MB (57.14%) ── c [2]\n\
│ ├──333.00 MB (42.86%) ── b\n\
│ └────0.00 MB (00.00%) ── d [*] [2]\n\
└────0.00 MB (00.00%) ++ (2 tiny)\n\
\n\
Other Measurements\n\
0.00 MB -- heap-allocated [*]\n\
0.00 MB -- other1 [*]\n\
0.00 MB ── heap-allocated [*]\n\
0.00 MB ── other1 [*]\n\
\n\
";
@ -273,51 +257,49 @@ Main Process\n\
Explicit Allocations\n\
653,876,224 B (100.0%) -- explicit\n\
├──243,269,632 B (37.20%) -- b\n\
│ ├───89,128,960 B (13.63%) -- a\n\
│ ├───78,643,200 B (12.03%) -- b\n\
│ ├───89,128,960 B (13.63%) ── a\n\
│ ├───78,643,200 B (12.03%) ── b\n\
│ └───75,497,472 B (11.55%) -- c\n\
│ ├──73,400,320 B (11.23%) -- a\n\
│ └───2,097,152 B (00.32%) -- b\n\
├──232,783,872 B (35.60%) -- a\n\
│ ├──73,400,320 B (11.23%) ── a\n\
│ └───2,097,152 B (00.32%) ── b\n\
├──232,783,872 B (35.60%) ── a\n\
├──104,857,600 B (16.04%) -- c\n\
│ ├───80,740,352 B (12.35%) -- other\n\
│ └───24,117,248 B (03.69%) -- d [2]\n\
├───24,117,248 B (03.69%) -- cc [2]\n\
│ ├───80,740,352 B (12.35%) ── other\n\
│ └───24,117,248 B (03.69%) ── d [2]\n\
├───24,117,248 B (03.69%) ── cc [2]\n\
├───20,971,520 B (03.21%) -- f\n\
│ └──20,971,520 B (03.21%) -- g\n\
│ └──20,971,520 B (03.21%) -- h\n\
│ └──20,971,520 B (03.21%) -- i\n\
│ └──20,971,520 B (03.21%) ── i\n\
├───15,728,640 B (02.41%) -- g\n\
│ ├───6,291,456 B (00.96%) -- a\n\
│ ├───5,242,880 B (00.80%) -- b\n\
│ └───4,194,304 B (00.64%) -- other\n\
├───11,534,336 B (01.76%) -- heap-unclassified\n\
├──────510,976 B (00.08%) -- d\n\
└──────102,400 B (00.02%) -- e\n\
│ ├───6,291,456 B (00.96%) ── a\n\
│ ├───5,242,880 B (00.80%) ── b\n\
│ └───4,194,304 B (00.64%) ── other\n\
├───11,534,336 B (01.76%) ── heap-unclassified\n\
├──────510,976 B (00.08%) ── d\n\
└──────102,400 B (00.02%) ── e\n\
\n\
Resident Set Size (RSS) Breakdown\n\
172,032 B (100.0%) -- resident\n\
└──172,032 B (100.0%) -- a\n\
172,032 B (100.0%) ++ resident\n\
\n\
Proportional Set Size (PSS) Breakdown\n\
176,128 B (100.0%) ++ pss\n\
\n\
Virtual Size Breakdown\n\
176,128 B (100.0%) -- vsize\n\
└──176,128 B (100.0%) -- a [2]\n\
176,128 B (100.0%) ++ vsize\n\
\n\
Swap Usage Breakdown\n\
53,248 B (100.0%) -- swap\n\
├──40,960 B (76.92%) -- b\n\
│ └──40,960 B (76.92%) -- c\n\
└──12,288 B (23.08%) -- a [2]\n\
53,248 B (100.0%) ++ swap\n\
\n\
Other Measurements\n\
524,288,000 B -- heap-allocated\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\
524,288,000 B ── heap-allocated\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\
\n\
2nd Process\n\
\n\
@ -325,16 +307,16 @@ Explicit Allocations\n\
1,048,576,000 B (100.0%) -- explicit\n\
├────523,239,424 B (49.90%) -- a\n\
│ └──523,239,424 B (49.90%) -- b\n\
│ └──523,239,424 B (49.90%) -- c [3]\n\
├────209,715,200 B (20.00%) -- flip/the/backslashes\n\
├────209,715,200 B (20.00%) -- compartment(compartment-url)\n\
└────105,906,176 B (10.10%) -- heap-unclassified\n\
│ └──523,239,424 B (49.90%) ── c [3]\n\
├────209,715,200 B (20.00%) ── flip/the/backslashes\n\
├────209,715,200 B (20.00%) ── compartment(compartment-url)\n\
└────105,906,176 B (10.10%) ── heap-unclassified\n\
\n\
Other Measurements\n\
698,351,616 B -- danger<script>window.alert(1)</script>\n\
1,048,576,000 B -- heap-allocated\n\
104,857,600 B -- heap-unallocated\n\
116,391,936 B -- other1\n\
698,351,616 B ── danger<script>window.alert(1)</script>\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\
@ -343,15 +325,15 @@ WARNING: the 'heap-allocated' memory reporter does not work for this platform an
Explicit Allocations\n\
814,743,552 B (100.0%) -- explicit\n\
├──814,743,552 B (100.0%) -- a\n\
│ ├──465,567,744 B (57.14%) -- c [2]\n\
│ ├──349,175,808 B (42.86%) -- b\n\
│ └────────────0 B (00.00%) -- d [*] [2]\n\
├────────────0 B (00.00%) -- b [*]\n\
└────────────0 B (00.00%) -- heap-unclassified [*]\n\
│ ├──465,567,744 B (57.14%) ── c [2]\n\
│ ├──349,175,808 B (42.86%) ── b\n\
│ └────────────0 B (00.00%) ── d [*] [2]\n\
├────────────0 B (00.00%) ── b [*]\n\
└────────────0 B (00.00%) ── heap-unclassified [*]\n\
\n\
Other Measurements\n\
0 B -- heap-allocated [*]\n\
0 B -- other1 [*]\n\
0 B ── heap-allocated [*]\n\
0 B ── other1 [*]\n\
\n\
"
@ -375,13 +357,18 @@ Other Measurements\n\
SimpleTest.finish();
}
var gHaveDumped = false;
function checkClipboard(actual, expected) {
if (actual != expected) {
dump("*******ACTUAL*******\n");
dump(actual);
dump("******EXPECTED******\n");
dump(expected);
dump("********************\n");
if (!gHaveDumped) {
dump("******EXPECTED******\n");
dump(expected);
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
gHaveDumped = true;
}
return false;
}
return true;
@ -391,15 +378,8 @@ Other Measurements\n\
// expect. This tests the output in general and also that the cutting and
// pasting works as expected.
function test(aFrame, aExpectedText, aNext) {
// Click all h2.collapsed elements so they expand.
var win = document.querySelector("#" + aFrame).contentWindow;
var nodes = win.document.querySelectorAll("pre.collapsed");
for (var i = 0; i < nodes.length; i++) {
nodes[i].classList.toggle('collapsed');
}
SimpleTest.executeSoon(function() {
document.querySelector("#" + aFrame).focus();
document.getElementById(aFrame).focus();
SimpleTest.waitForClipboard(
function(actual) { return checkClipboard(actual, aExpectedText) },
function() {

Просмотреть файл

@ -0,0 +1,221 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the collapsing and expanding of sub-trees in
about:memory. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Remove all the real reporters and multi-reporters; save them to
// restore at the end.
var e = mgr.enumerateReporters();
var realReporters = [];
while (e.hasMoreElements()) {
var r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
mgr.unregisterReporter(r);
realReporters.push(r);
}
e = mgr.enumerateMultiReporters();
var realMultiReporters = [];
while (e.hasMoreElements()) {
var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
mgr.unregisterMultiReporter(r);
realMultiReporters.push(r);
}
// Setup various fake-but-deterministic reporters.
const KB = 1024;
const MB = KB * KB;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
function f(aPath, aKind, aAmount) {
return {
process: "",
path: aPath,
kind: aKind,
units: BYTES,
description: "(description)",
amount: aAmount
};
}
var fakeReporters = [
f("heap-allocated", OTHER, 250 * MB),
f("explicit/a/b", HEAP, 50 * MB),
f("explicit/a/c/d", HEAP, 30 * MB),
f("explicit/a/c/e", HEAP, 20 * MB),
f("explicit/a/f", HEAP, 40 * MB),
f("explicit/g", HEAP, 100 * MB)
];
for (var i = 0; i < fakeReporters.length; i++) {
mgr.registerReporter(fakeReporters[i]);
}
]]>
</script>
<iframe id="amFrame" height="500" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
function finish()
{
// Unregister fake reporters and multi-reporters, re-register the real
// reporters and multi-reporters, just in case subsequent tests rely on
// them.
for (var i = 0; i < fakeReporters.length; i++) {
mgr.unregisterReporter(fakeReporters[i]);
}
for (var i = 0; i < realReporters.length; i++) {
mgr.registerReporter(realReporters[i]);
}
for (var i = 0; i < realMultiReporters.length; i++) {
mgr.registerMultiReporter(realMultiReporters[i]);
}
SimpleTest.finish();
}
var gHaveDumped = false;
function checkClipboard(actual, expected) {
if (actual != expected) {
if (!gHaveDumped) {
dump("******EXPECTED******\n");
dump(expected);
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
gHaveDumped = true;
}
return false;
}
return true;
}
// Click on the identified element, then cut+paste the entire page and
// check that the cut text matches what we expect.
function test(aId, aExpectedText, aNext) {
var win = document.getElementById("amFrame").contentWindow;
var node = win.document.getElementById(aId);
// Yuk: clicking a button is easy; but for tree entries we need to
// click on a child of the span identified via |id|.
if (node.nodeName === "button") {
node.click();
} else {
node.childNodes[0].click();
}
SimpleTest.executeSoon(function() {
document.getElementById("amFrame").focus();
SimpleTest.waitForClipboard(
function(actual) { return checkClipboard(actual, aExpectedText) },
function() {
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
},
aNext,
function() {
ok(false, "pasted text doesn't match");
finish();
}
);
});
}
// Returns a function that chains together one test() call per id.
function chain(ids) {
var x = ids.shift();
if (x) {
return function() { test(x.id, x.expected, chain(ids)); }
} else {
return function() { finish(); };
}
}
var openExpected =
"\
Main Process\n\
\n\
Explicit Allocations\n\
250.00 MB (100.0%) -- explicit\n\
├──140.00 MB (56.00%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───50.00 MB (20.00%) -- c\n\
│ │ ├──30.00 MB (12.00%) ── d\n\
│ │ └──20.00 MB (08.00%) ── e\n\
│ └───40.00 MB (16.00%) ── f\n\
├──100.00 MB (40.00%) ── g\n\
└───10.00 MB (04.00%) ── heap-unclassified\n\
\n\
Other Measurements\n\
250.00 MB ── heap-allocated\n\
\n\
";
var cClosedExpected =
"\
Main Process\n\
\n\
Explicit Allocations\n\
250.00 MB (100.0%) -- explicit\n\
├──140.00 MB (56.00%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───50.00 MB (20.00%) ++ c\n\
│ └───40.00 MB (16.00%) ── f\n\
├──100.00 MB (40.00%) ── g\n\
└───10.00 MB (04.00%) ── heap-unclassified\n\
\n\
Other Measurements\n\
250.00 MB ── heap-allocated\n\
\n\
";
var aClosedExpected =
"\
Main Process\n\
\n\
Explicit Allocations\n\
250.00 MB (100.0%) -- explicit\n\
├──140.00 MB (56.00%) ++ a\n\
├──100.00 MB (40.00%) ── g\n\
└───10.00 MB (04.00%) ── heap-unclassified\n\
\n\
Other Measurements\n\
250.00 MB ── heap-allocated\n\
\n\
";
// We close two sub-trees, hit the "Update" button, then reopen the two
// sub-trees in reverse order. After each step, we check the output.
var idsToClick = [
{ id: "Main:explicit/a/c", expected: cClosedExpected },
{ id: "Main:explicit/a", expected: aClosedExpected },
{ id: "updateButton", expected: aClosedExpected },
{ id: "Main:explicit/a", expected: cClosedExpected },
{ id: "Main:explicit/a/c", expected: openExpected }
];
addLoadEvent(chain(idsToClick));
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>