Reclassify heapsnapshot and nsJSInspector as not part of devtools

This resolves Issue #316
This commit is contained in:
Matt A. Tobin 2020-02-22 17:32:39 -05:00 committed by Roy Tam
commit 95d5dcd8c5
142 changed files with 19 additions and 15 deletions

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the common devtools xpcshell eslintrc config.
"extends": "../../../../.eslintrc.xpcshell.js"
};

View file

@ -0,0 +1,165 @@
// Functions for checking results returned by
// Debugger.Memory.prototype.takeCensus and
// HeapSnapshot.prototype.takeCensus. Adapted from js/src/jit-test/lib/census.js.
this.EXPORTED_SYMBOLS = ["Census"];
this.Census = (function () {
const Census = {};
function dumpn(msg) {
dump("DBG-TEST: Census.jsm: " + msg + "\n");
}
// Census.walkCensus(subject, name, walker)
//
// Use |walker| to check |subject|, a census object of the sort returned by
// Debugger.Memory.prototype.takeCensus: a tree of objects with integers at the
// leaves. Use |name| as the name for |subject| in diagnostic messages. Return
// the number of leaves of |subject| we visited.
//
// A walker is an object with three methods:
//
// - enter(prop): Return the walker we should use to check the property of the
// subject census named |prop|. This is for recursing into the subobjects of
// the subject.
//
// - done(): Called after we have called 'enter' on every property of the
// subject.
//
// - check(value): Check |value|, a leaf in the subject.
//
// Walker methods are expected to simply throw if a node we visit doesn't look
// right.
Census.walkCensus = (subject, name, walker) => walk(subject, name, walker, 0);
function walk(subject, name, walker, count) {
if (typeof subject === "object") {
dumpn(name);
for (let prop in subject) {
count = walk(subject[prop],
name + "[" + uneval(prop) + "]",
walker.enter(prop),
count);
}
walker.done();
} else {
dumpn(name + " = " + uneval(subject));
walker.check(subject);
count++;
}
return count;
}
// A walker that doesn't check anything.
Census.walkAnything = {
enter: () => Census.walkAnything,
done: () => undefined,
check: () => undefined
};
// A walker that requires all leaves to be zeros.
Census.assertAllZeros = {
enter: () => Census.assertAllZeros,
done: () => undefined,
check: elt => { if (elt !== 0) throw new Error("Census mismatch: expected zero, found " + elt); }
};
function expectedObject() {
throw new Error("Census mismatch: subject has leaf where basis has nested object");
}
function expectedLeaf() {
throw new Error("Census mismatch: subject has nested object where basis has leaf");
}
// Return a function that, given a 'basis' census, returns a census walker that
// compares the subject census against the basis. The returned walker calls the
// given |compare|, |missing|, and |extra| functions as follows:
//
// - compare(subjectLeaf, basisLeaf): Check a leaf of the subject against the
// corresponding leaf of the basis.
//
// - missing(prop, value): Called when the subject is missing a property named
// |prop| which is present in the basis with value |value|.
//
// - extra(prop): Called when the subject has a property named |prop|, but the
// basis has no such property. This should return a walker that can check
// the subject's value.
function makeBasisChecker({compare, missing, extra}) {
return function makeWalker(basis) {
if (typeof basis === "object") {
var unvisited = new Set(Object.getOwnPropertyNames(basis));
return {
enter: prop => {
unvisited.delete(prop);
if (prop in basis) {
return makeWalker(basis[prop]);
} else {
return extra(prop);
}
},
done: () => unvisited.forEach(prop => missing(prop, basis[prop])),
check: expectedObject
};
} else {
return {
enter: expectedLeaf,
done: expectedLeaf,
check: elt => compare(elt, basis)
};
}
};
}
function missingProp(prop) {
throw new Error("Census mismatch: subject lacks property present in basis: " + prop);
}
function extraProp(prop) {
throw new Error("Census mismatch: subject has property not present in basis: " + prop);
}
// Return a walker that checks that the subject census has counts all equal to
// |basis|.
Census.assertAllEqual = makeBasisChecker({
compare: (a, b) => { if (a !== b) throw new Error("Census mismatch: expected " + a + " got " + b);},
missing: missingProp,
extra: extraProp
});
function ok(val) {
if (!val) {
throw new Error("Census mismatch: expected truthy, got " + val);
}
}
// Return a walker that checks that the subject census has at least as many
// items of each category as |basis|.
Census.assertAllNotLessThan = makeBasisChecker({
compare: (subject, basis) => ok(subject >= basis),
missing: missingProp,
extra: () => Census.walkAnything
});
// Return a walker that checks that the subject census has at most as many
// items of each category as |basis|.
Census.assertAllNotMoreThan = makeBasisChecker({
compare: (subject, basis) => ok(subject <= basis),
missing: missingProp,
extra: () => Census.walkAnything
});
// Return a walker that checks that the subject census has within |fudge|
// items of each category of the count in |basis|.
Census.assertAllWithin = function (fudge, basis) {
return makeBasisChecker({
compare: (subject, basis) => ok(Math.abs(subject - basis) <= fudge),
missing: missingProp,
extra: () => Census.walkAnything
})(basis);
};
return Census;
}());

View file

@ -0,0 +1,190 @@
// A little pattern-matching library.
//
// Ported from js/src/tests/js1_8_5/reflect-parse/Match.js for use with devtools
// server xpcshell tests.
this.EXPORTED_SYMBOLS = ["Match"];
this.Match = (function() {
function Pattern(template) {
// act like a constructor even as a function
if (!(this instanceof Pattern))
return new Pattern(template);
this.template = template;
}
Pattern.prototype = {
match: function(act) {
return match(act, this.template);
},
matches: function(act) {
try {
return this.match(act);
}
catch (e if e instanceof MatchError) {
return false;
}
},
assert: function(act, message) {
try {
return this.match(act);
}
catch (e if e instanceof MatchError) {
throw new Error((message || "failed match") + ": " + e.message);
}
},
toString: () => "[object Pattern]"
};
Pattern.ANY = new Pattern;
Pattern.ANY.template = Pattern.ANY;
Pattern.NUMBER = new Pattern;
Pattern.NUMBER.match = function (act) {
if (typeof act !== 'number') {
throw new MatchError("Expected number, got: " + quote(act));
}
}
Pattern.NATURAL = new Pattern
Pattern.NATURAL.match = function (act) {
if (typeof act !== 'number' || act !== Math.floor(act) || act < 0) {
throw new MatchError("Expected natural number, got: " + quote(act));
}
}
var quote = uneval;
function MatchError(msg) {
this.message = msg;
}
MatchError.prototype = {
toString: function() {
return "match error: " + this.message;
}
};
function isAtom(x) {
return (typeof x === "number") ||
(typeof x === "string") ||
(typeof x === "boolean") ||
(x === null) ||
(typeof x === "object" && x instanceof RegExp);
}
function isObject(x) {
return (x !== null) && (typeof x === "object");
}
function isFunction(x) {
return typeof x === "function";
}
function isArrayLike(x) {
return isObject(x) && ("length" in x);
}
function matchAtom(act, exp) {
if ((typeof exp) === "number" && isNaN(exp)) {
if ((typeof act) !== "number" || !isNaN(act))
throw new MatchError("expected NaN, got: " + quote(act));
return true;
}
if (exp === null) {
if (act !== null)
throw new MatchError("expected null, got: " + quote(act));
return true;
}
if (exp instanceof RegExp) {
if (!(act instanceof RegExp) || exp.source !== act.source)
throw new MatchError("expected " + quote(exp) + ", got: " + quote(act));
return true;
}
switch (typeof exp) {
case "string":
if (act !== exp)
throw new MatchError("expected " + quote(exp) + ", got " + quote(act));
return true;
case "boolean":
case "number":
if (exp !== act)
throw new MatchError("expected " + exp + ", got " + quote(act));
return true;
}
throw new Error("bad pattern: " + exp.toSource());
}
function matchObject(act, exp) {
if (!isObject(act))
throw new MatchError("expected object, got " + quote(act));
for (var key in exp) {
if (!(key in act))
throw new MatchError("expected property " + quote(key) + " not found in " + quote(act));
match(act[key], exp[key]);
}
return true;
}
function matchFunction(act, exp) {
if (!isFunction(act))
throw new MatchError("expected function, got " + quote(act));
if (act !== exp)
throw new MatchError("expected function: " + exp +
"\nbut got different function: " + act);
}
function matchArray(act, exp) {
if (!isObject(act) || !("length" in act))
throw new MatchError("expected array-like object, got " + quote(act));
var length = exp.length;
if (act.length !== exp.length)
throw new MatchError("expected array-like object of length " + length + ", got " + quote(act));
for (var i = 0; i < length; i++) {
if (i in exp) {
if (!(i in act))
throw new MatchError("expected array property " + i + " not found in " + quote(act));
match(act[i], exp[i]);
}
}
return true;
}
function match(act, exp) {
if (exp === Pattern.ANY)
return true;
if (exp instanceof Pattern)
return exp.match(act);
if (isAtom(exp))
return matchAtom(act, exp);
if (isArrayLike(exp))
return matchArray(act, exp);
if (isFunction(exp))
return matchFunction(act, exp);
return matchObject(act, exp);
}
return { Pattern: Pattern,
MatchError: MatchError };
})();

View file

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
console.log("Initializing worker.");
self.onmessage = e => {
console.log("Starting test.");
try {
const path = ThreadSafeChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
ok(dominatorTree);
ok(dominatorTree instanceof DominatorTree);
let threw = false;
try {
new DominatorTree();
} catch (e) {
threw = true;
}
ok(threw, "Constructor shouldn't be usable");
} catch (e) {
ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
} finally {
done();
}
};
// Proxy assertions to the main thread.
function ok(val, msg) {
console.log("ok(" + !!val + ", \"" + msg + "\")");
self.postMessage({
type: "assertion",
passed: !!val,
msg,
stack: Error().stack
});
}
// Tell the main thread we are done with the tests.
function done() {
console.log("done()");
self.postMessage({
type: "done"
});
}

View file

@ -0,0 +1,448 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;
var CC = Components.Constructor;
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { Match } = Cu.import("resource://test/Match.jsm", {});
const { Census } = Cu.import("resource://test/Census.jsm", {});
const { addDebuggerToGlobal } =
Cu.import("resource://gre/modules/jsdebugger.jsm", {});
const { Task } = require("devtools/shared/task");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const flags = require("devtools/shared/flags");
const HeapAnalysesClient =
require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const Services = require("Services");
const { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
const DominatorTreeNode = require("devtools/shared/heapsnapshot/DominatorTreeNode");
const { deduplicatePaths } = require("devtools/shared/heapsnapshot/shortest-paths");
const { LabelAndShallowSizeVisitor } = DominatorTreeNode;
// Always log packets when running tests. runxpcshelltests.py will throw
// the output away anyway, unless you give it the --verbose flag.
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
Services.prefs.setBoolPref("devtools.debugger.log", true);
}
flags.wantLogging = true;
const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
function dumpn(msg) {
dump("HEAPSNAPSHOT-TEST: " + msg + "\n");
}
function addTestingFunctionsToGlobal(global) {
global.eval(
`
const testingFunctions = Components.utils.getJSTestingFunctions();
for (let k in testingFunctions) {
this[k] = testingFunctions[k];
}
`
);
if (!global.print) {
global.print = do_print;
}
if (!global.newGlobal) {
global.newGlobal = newGlobal;
}
if (!global.Debugger) {
addDebuggerToGlobal(global);
}
}
addTestingFunctionsToGlobal(this);
/**
* Create a new global, with all the JS shell testing functions. Similar to the
* newGlobal function exposed to JS shells, and useful for porting JS shell
* tests to xpcshell tests.
*/
function newGlobal() {
const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true });
addTestingFunctionsToGlobal(global);
return global;
}
function assertThrowsValue(f, val, msg) {
var fullmsg;
try {
f();
} catch (exc) {
if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
return;
fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
}
if (fullmsg === undefined)
fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
if (msg !== undefined)
fullmsg += " - " + msg;
throw new Error(fullmsg);
}
/**
* Returns the full path of the file with the specified name in a
* platform-independent and URL-like form.
*/
function getFilePath(aName, aAllowMissing = false, aUsePlatformPathSeparator = false)
{
let file = do_get_file(aName, aAllowMissing);
let path = Services.io.newFileURI(file).spec;
let filePrePath = "file://";
if ("nsILocalFileWin" in Ci &&
file instanceof Ci.nsILocalFileWin) {
filePrePath += "/";
}
path = path.slice(filePrePath.length);
if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
path = path.replace(/\//g, "\\");
}
return path;
}
function saveNewHeapSnapshot(opts = { runtime: true }) {
const filePath = ChromeUtils.saveHeapSnapshot(opts);
ok(filePath, "Should get a file path to save the core dump to.");
ok(true, "Saved a heap snapshot to " + filePath);
return filePath;
}
function readHeapSnapshot(filePath) {
const snapshot = ChromeUtils.readHeapSnapshot(filePath);
ok(snapshot, "Should have read a heap snapshot back from " + filePath);
ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
return snapshot;
}
/**
* Save a heap snapshot to the file with the given name in the current
* directory, read it back as a HeapSnapshot instance, and then take a census of
* the heap snapshot's serialized heap graph with the provided census options.
*
* @param {Object|undefined} censusOptions
* Options that should be passed through to the takeCensus method. See
* js/src/doc/Debugger/Debugger.Memory.md for details.
*
* @param {Debugger|null} dbg
* If a Debugger object is given, only serialize the subgraph covered by
* the Debugger's debuggees. If null, serialize the whole heap graph.
*
* @param {String} fileName
* The file name to save the heap snapshot's core dump file to, within
* the current directory.
*
* @returns Census
*/
function saveHeapSnapshotAndTakeCensus(dbg = null, censusOptions = undefined) {
const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
const filePath = saveNewHeapSnapshot(snapshotOptions);
const snapshot = readHeapSnapshot(filePath);
equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
return snapshot.takeCensus(censusOptions);
}
/**
* Save a heap snapshot to disk, read it back as a HeapSnapshot instance, and
* then compute its dominator tree.
*
* @param {Debugger|null} dbg
* If a Debugger object is given, only serialize the subgraph covered by
* the Debugger's debuggees. If null, serialize the whole heap graph.
*
* @returns {DominatorTree}
*/
function saveHeapSnapshotAndComputeDominatorTree(dbg = null) {
const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
const filePath = saveNewHeapSnapshot(snapshotOptions);
const snapshot = readHeapSnapshot(filePath);
equal(typeof snapshot.computeDominatorTree, "function",
"snapshot should have a `computeDominatorTree` method");
const dominatorTree = snapshot.computeDominatorTree();
ok(dominatorTree, "Should be able to compute a dominator tree");
ok(dominatorTree instanceof DominatorTree, "Should be an instance of DominatorTree");
return dominatorTree;
}
function isSavedFrame(obj) {
return Object.prototype.toString.call(obj) === "[object SavedFrame]";
}
function savedFrameReplacer(key, val) {
if (isSavedFrame(val)) {
return `<SavedFrame '${val.toString().split(/\n/g).shift()}'>`;
} else {
return val;
}
}
/**
* Assert that creating a CensusTreeNode from the given `report` with the
* specified `breakdown` creates the given `expected` CensusTreeNode.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} report
* The census report.
*
* @param {Object} expected
* The expected CensusTreeNode result.
*
* @param {Object} options
* The options to pass through to `censusReportToCensusTreeNode`.
*/
function compareCensusViewData(breakdown, report, expected, options) {
dumpn("Generating CensusTreeNode from report:");
dumpn("breakdown: " + JSON.stringify(breakdown, null, 4));
dumpn("report: " + JSON.stringify(report, null, 4));
dumpn("expected: " + JSON.stringify(expected, savedFrameReplacer, 4));
const actual = censusReportToCensusTreeNode(breakdown, report, options);
dumpn("actual: " + JSON.stringify(actual, savedFrameReplacer, 4));
assertStructurallyEquivalent(actual, expected);
}
// Deep structural equivalence that can handle Map objects in addition to plain
// objects.
function assertStructurallyEquivalent(actual, expected, path = "root") {
if (actual === expected) {
equal(actual, expected, "actual and expected are the same");
return;
}
equal(typeof actual, typeof expected, `${path}: typeof should be the same`);
if (actual && typeof actual === "object") {
const actualProtoString = Object.prototype.toString.call(actual);
const expectedProtoString = Object.prototype.toString.call(expected);
equal(actualProtoString, expectedProtoString,
`${path}: Object.prototype.toString.call() should be the same`);
if (actualProtoString === "[object Map]") {
const expectedKeys = new Set([...expected.keys()]);
for (let key of actual.keys()) {
ok(expectedKeys.has(key),
`${path}: every key in actual should exist in expected: ${String(key).slice(0, 10)}`);
expectedKeys.delete(key);
assertStructurallyEquivalent(actual.get(key), expected.get(key),
path + ".get(" + String(key).slice(0, 20) + ")");
}
equal(expectedKeys.size, 0,
`${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
} else if (actualProtoString === "[object Set]") {
const expectedItems = new Set([...expected]);
for (let item of actual) {
ok(expectedItems.has(item),
`${path}: every set item in actual should exist in expected: ${item}`);
expectedItems.delete(item);
}
equal(expectedItems.size, 0,
`${path}: every set item in expected should also exist in actual, did not see ${[...expectedItems]}`);
} else {
const expectedKeys = new Set(Object.keys(expected));
for (let key of Object.keys(actual)) {
ok(expectedKeys.has(key),
`${path}: every key in actual should exist in expected: ${key}`);
expectedKeys.delete(key);
assertStructurallyEquivalent(actual[key], expected[key], path + "." + key);
}
equal(expectedKeys.size, 0,
`${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
}
} else {
equal(actual, expected, `${path}: primitives should be equal`);
}
}
/**
* Assert that creating a diff of the `first` and `second` census reports
* creates the `expected` delta-report.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} first
* The first census report.
*
* @param {Object} second
* The second census report.
*
* @param {Object} expected
* The expected delta-report.
*/
function assertDiff(breakdown, first, second, expected) {
dumpn("Diffing census reports:");
dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
dumpn("First census report: " + JSON.stringify(first, null, 4));
dumpn("Second census report: " + JSON.stringify(second, null, 4));
dumpn("Expected delta-report: " + JSON.stringify(expected, null, 4));
const actual = CensusUtils.diff(breakdown, first, second);
dumpn("Actual delta-report: " + JSON.stringify(actual, null, 4));
assertStructurallyEquivalent(actual, expected);
}
/**
* Assert that creating a label and getting a shallow size from the given node
* description with the specified breakdown is as expected.
*
* @param {Object} breakdown
* @param {Object} givenDescription
* @param {Number} expectedShallowSize
* @param {Object} expectedLabel
*/
function assertLabelAndShallowSize(breakdown, givenDescription, expectedShallowSize, expectedLabel) {
dumpn("Computing label and shallow size from node description:");
dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
dumpn("Given description: " + JSON.stringify(givenDescription, null, 4));
const visitor = new LabelAndShallowSizeVisitor();
CensusUtils.walk(breakdown, description, visitor);
dumpn("Expected shallow size: " + expectedShallowSize);
dumpn("Actual shallow size: " + visitor.shallowSize());
equal(visitor.shallowSize(), expectedShallowSize, "Shallow size should be correct");
dumpn("Expected label: " + JSON.stringify(expectedLabel, null, 4));
dumpn("Actual label: " + JSON.stringify(visitor.label(), null, 4));
assertStructurallyEquivalent(visitor.label(), expectedLabel);
}
// Counter for mock DominatorTreeNode ids.
let TEST_NODE_ID_COUNTER = 0;
/**
* Create a mock DominatorTreeNode for testing, with sane defaults. Override any
* property by providing it on `opts`. Optionally pass child nodes as well.
*
* @param {Object} opts
* @param {Array<DominatorTreeNode>?} children
*
* @returns {DominatorTreeNode}
*/
function makeTestDominatorTreeNode(opts, children) {
const nodeId = TEST_NODE_ID_COUNTER++;
const node = Object.assign({
nodeId,
label: undefined,
shallowSize: 1,
retainedSize: (children || []).reduce((size, c) => size + c.retainedSize, 1),
parentId: undefined,
children,
moreChildrenAvailable: true,
}, opts);
if (children && children.length) {
children.map(c => c.parentId = node.nodeId);
}
return node;
}
/**
* Insert `newChildren` into the given dominator `tree` as specified by the
* `path` from the root to the node the `newChildren` should be inserted
* beneath. Assert that the resulting tree matches `expected`.
*/
function assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected) {
dumpn("Inserting new children into a dominator tree:");
dumpn("Dominator tree: " + JSON.stringify(tree, null, 2));
dumpn("Path: " + JSON.stringify(path, null, 2));
dumpn("New children: " + JSON.stringify(newChildren, null, 2));
dumpn("Expected resulting tree: " + JSON.stringify(expected, null, 2));
const actual = DominatorTreeNode.insert(tree, path, newChildren, moreChildrenAvailable);
dumpn("Actual resulting tree: " + JSON.stringify(actual, null, 2));
assertStructurallyEquivalent(actual, expected);
}
function assertDeduplicatedPaths({ target, paths, expectedNodes, expectedEdges }) {
dumpn("Deduplicating paths:");
dumpn("target = " + target);
dumpn("paths = " + JSON.stringify(paths, null, 2));
dumpn("expectedNodes = " + expectedNodes);
dumpn("expectedEdges = " + JSON.stringify(expectedEdges, null, 2));
const { nodes, edges } = deduplicatePaths(target, paths);
dumpn("Actual nodes = " + nodes);
dumpn("Actual edges = " + JSON.stringify(edges, null, 2));
equal(nodes.length, expectedNodes.length,
"actual number of nodes is equal to the expected number of nodes");
equal(edges.length, expectedEdges.length,
"actual number of edges is equal to the expected number of edges");
const expectedNodeSet = new Set(expectedNodes);
const nodeSet = new Set(nodes);
ok(nodeSet.size === nodes.length,
"each returned node should be unique");
for (let node of nodes) {
ok(expectedNodeSet.has(node), `the ${node} node was expected`);
}
for (let expectedEdge of expectedEdges) {
let count = 0;
for (let edge of edges) {
if (edge.from === expectedEdge.from &&
edge.to === expectedEdge.to &&
edge.name === expectedEdge.name) {
count++;
}
}
equal(count, 1,
"should have exactly one matching edge for the expected edge = " + JSON.stringify(edge));
}
}
function assertCountToBucketBreakdown(breakdown, expected) {
dumpn("count => bucket breakdown");
dumpn("Initial breakdown = ", JSON.stringify(breakdown, null, 2));
dumpn("Expected results = ", JSON.stringify(expected, null, 2));
const actual = CensusUtils.countToBucketBreakdown(breakdown);
dumpn("Actual results = ", JSON.stringify(actual, null, 2));
assertStructurallyEquivalent(actual, expected);
}
/**
* Create a mock path entry for the given predecessor and edge.
*/
function pathEntry(predecessor, edge) {
return { predecessor, edge };
}

View file

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
console.log("Initializing worker.");
self.onmessage = e => {
console.log("Starting test.");
try {
ok(typeof ChromeUtils === "undefined",
"Should not have access to ChromeUtils in a worker.");
ok(ThreadSafeChromeUtils,
"Should have access to ThreadSafeChromeUtils in a worker.");
ok(HeapSnapshot,
"Should have access to HeapSnapshot in a worker.");
const filePath = ThreadSafeChromeUtils.saveHeapSnapshot({ globals: [this] });
ok(true, "Should be able to save a snapshot.");
const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(filePath);
ok(snapshot, "Should be able to read a heap snapshot");
ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
} catch (e) {
ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
} finally {
done();
}
};
// Proxy assertions to the main thread.
function ok(val, msg) {
console.log("ok(" + !!val + ", \"" + msg + "\")");
self.postMessage({
type: "assertion",
passed: !!val,
msg,
stack: Error().stack
});
}
// Tell the main thread we are done with the tests.
function done() {
console.log("done()");
self.postMessage({
type: "done"
});
}

View file

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can generate label structures from node description reports.
const breakdown = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const description = {
objects: {
Function: { count: 1, bytes: 32 },
other: { count: 0, bytes: 0 }
},
strings: {},
scripts: {},
other: {}
};
const expected = [
"objects",
"Function"
];
const shallowSize = 32;
function run_test() {
assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
}

View file

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can generate label structures from node description reports.
const breakdown = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const description = {
objects: {
other: { count: 1, bytes: 10 }
},
strings: {},
scripts: {},
other: {}
};
const expected = [
"objects",
"other"
];
const shallowSize = 10;
function run_test() {
assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
}

View file

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can generate label structures from node description reports.
const breakdown = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const description = {
objects: {
other: { count: 0, bytes: 0 }
},
strings: {
"JSString": { count: 1, bytes: 42 },
},
scripts: {},
other: {}
};
const expected = [
"strings",
"JSString"
];
const shallowSize = 42;
function run_test() {
assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
}

View file

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can generate label structures from node description reports.
const breakdown = {
by: "coarseType",
objects: {
by: "objectClass",
then: {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true },
},
other: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const stack = saveStack();
const description = {
objects: {
Array: new Map([[stack, { count: 1, bytes: 512 }]]),
other: { count: 0, bytes: 0 }
},
strings: {},
scripts: {},
other: {}
};
const expected = [
"objects",
"Array",
stack
];
const shallowSize = 512;
function run_test() {
assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
}

View file

@ -0,0 +1,132 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the DominatorTreeNode.attachShortestPaths function can correctly
// attach the deduplicated shortest retaining paths for each node it is given.
const startNodeId = 9999;
const maxNumPaths = 2;
// Mock data mapping node id to shortest paths to that node id.
const shortestPaths = new Map([
[1000, [
[pathEntry(1100, "a"), pathEntry(1200, "b")],
[pathEntry(1100, "c"), pathEntry(1300, "d")],
]],
[2000, [
[pathEntry(2100, "e"), pathEntry(2200, "f"), pathEntry(2300, "g")]
]],
[3000, [
[pathEntry(3100, "h")],
[pathEntry(3100, "i")],
[pathEntry(3100, "j")],
[pathEntry(3200, "k")],
[pathEntry(3300, "l")],
[pathEntry(3400, "m")],
]],
]);
const actual = [
makeTestDominatorTreeNode({ nodeId: 1000 }),
makeTestDominatorTreeNode({ nodeId: 2000 }),
makeTestDominatorTreeNode({ nodeId: 3000 }),
];
const expected = [
makeTestDominatorTreeNode({
nodeId: 1000,
shortestPaths: {
nodes: [
{ id: 1000, label: ["SomeType-1000"] },
{ id: 1100, label: ["SomeType-1100"] },
{ id: 1200, label: ["SomeType-1200"] },
{ id: 1300, label: ["SomeType-1300"] },
],
edges: [
{ from: 1100, to: 1200, name: "a" },
{ from: 1100, to: 1300, name: "c" },
{ from: 1200, to: 1000, name: "b" },
{ from: 1300, to: 1000, name: "d" },
]
}
}),
makeTestDominatorTreeNode({
nodeId: 2000,
shortestPaths: {
nodes: [
{ id: 2000, label: ["SomeType-2000"] },
{ id: 2100, label: ["SomeType-2100"] },
{ id: 2200, label: ["SomeType-2200"] },
{ id: 2300, label: ["SomeType-2300"] },
],
edges: [
{ from: 2100, to: 2200, name: "e" },
{ from: 2200, to: 2300, name: "f" },
{ from: 2300, to: 2000, name: "g" },
]
}
}),
makeTestDominatorTreeNode({ nodeId: 3000,
shortestPaths: {
nodes: [
{ id: 3000, label: ["SomeType-3000"] },
{ id: 3100, label: ["SomeType-3100"] },
{ id: 3200, label: ["SomeType-3200"] },
{ id: 3300, label: ["SomeType-3300"] },
{ id: 3400, label: ["SomeType-3400"] },
],
edges: [
{ from: 3100, to: 3000, name: "h" },
{ from: 3100, to: 3000, name: "i" },
{ from: 3100, to: 3000, name: "j" },
{ from: 3200, to: 3000, name: "k" },
{ from: 3300, to: 3000, name: "l" },
{ from: 3400, to: 3000, name: "m" },
]
}
}),
];
const breakdown = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
const mockSnapshot = {
computeShortestPaths: (start, nodeIds, max) => {
equal(start, startNodeId);
equal(max, maxNumPaths);
return new Map(nodeIds.map(nodeId => {
const paths = shortestPaths.get(nodeId);
ok(paths, "Expected computeShortestPaths call for node id = " + nodeId);
return [nodeId, paths];
}));
},
describeNode: (bd, nodeId) => {
equal(bd, breakdown);
return {
["SomeType-" + nodeId]: {
count: 1,
bytes: 10,
}
};
},
};
function run_test() {
DominatorTreeNode.attachShortestPaths(mockSnapshot,
breakdown,
startNodeId,
actual,
maxNumPaths);
dumpn("Expected = " + JSON.stringify(expected, null, 2));
dumpn("Actual = " + JSON.stringify(actual, null, 2));
assertStructurallyEquivalent(expected, actual);
}

View file

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can find the node with the given id along the specified path.
const node3000 = makeTestDominatorTreeNode({ nodeId: 3000 });
const node2000 = makeTestDominatorTreeNode({ nodeId: 2000 }, [
makeTestDominatorTreeNode({}),
node3000,
makeTestDominatorTreeNode({}),
]);
const node1000 = makeTestDominatorTreeNode({ nodeId: 1000 }, [
makeTestDominatorTreeNode({}),
node2000,
makeTestDominatorTreeNode({}),
]);
const tree = node1000;
const path = [1000, 2000, 3000];
const tests = [
{ id: 1000, expected: node1000 },
{ id: 2000, expected: node2000 },
{ id: 3000, expected: node3000 },
];
function run_test() {
for (let { id, expected } of tests) {
const actual = DominatorTreeNode.getNodeByIdAlongPath(id, tree, path);
equal(actual, expected, `We should have got the node with id = ${id}`);
}
equal(null,
DominatorTreeNode.getNodeByIdAlongPath(999999999999, tree, path),
"null is returned for nodes that are not even in the tree");
const lastNodeId = tree.children[tree.children.length - 1].nodeId;
equal(null,
DominatorTreeNode.getNodeByIdAlongPath(lastNodeId, tree, path),
"null is returned for nodes that are not along the path");
}

View file

@ -0,0 +1,112 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can insert new children into an existing DominatorTreeNode tree.
const tree = makeTestDominatorTreeNode({ nodeId: 1000 }, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({ nodeId: 2000 }, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({ nodeId: 3000 }),
makeTestDominatorTreeNode({}),
]),
makeTestDominatorTreeNode({}),
]);
const path = [1000, 2000, 3000];
const newChildren = [
makeTestDominatorTreeNode({ parentId: 3000 }),
makeTestDominatorTreeNode({ parentId: 3000 }),
];
const moreChildrenAvailable = false;
const expected = {
nodeId: 1000,
parentId: undefined,
label: undefined,
shallowSize: 1,
retainedSize: 7,
children: [
{
nodeId: 0,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 1000,
moreChildrenAvailable: true,
children: undefined,
},
{
nodeId: 2000,
label: undefined,
shallowSize: 1,
retainedSize: 4,
parentId: 1000,
children: [
{
nodeId: 1,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 2000,
moreChildrenAvailable: true,
children: undefined,
},
{
nodeId: 3000,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 2000,
children: [
{
nodeId: 7,
parentId: 3000,
label: undefined,
shallowSize: 1,
retainedSize: 1,
moreChildrenAvailable: true,
children: undefined,
},
{
nodeId: 8,
parentId: 3000,
label: undefined,
shallowSize: 1,
retainedSize: 1,
moreChildrenAvailable: true,
children: undefined,
},
],
moreChildrenAvailable: false
},
{
nodeId: 3,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 2000,
moreChildrenAvailable: true,
children: undefined,
},
],
moreChildrenAvailable: true
},
{
nodeId: 5,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 1000,
moreChildrenAvailable: true,
children: undefined,
}
],
moreChildrenAvailable: true
};
function run_test() {
assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
}

View file

@ -0,0 +1,30 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test attempting to insert new children into an existing DominatorTreeNode
// tree with a bad path.
const tree = makeTestDominatorTreeNode({}, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
]),
makeTestDominatorTreeNode({}),
]);
const path = [111111, 222222, 333333];
const newChildren = [
makeTestDominatorTreeNode({ parentId: 333333 }),
makeTestDominatorTreeNode({ parentId: 333333 }),
];
const moreChildrenAvailable = false;
const expected = tree;
function run_test() {
assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
}

View file

@ -0,0 +1,117 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test inserting new children into an existing DominatorTreeNode at the root.
const tree = makeTestDominatorTreeNode({ nodeId: 666 }, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}, [
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
makeTestDominatorTreeNode({}),
]),
makeTestDominatorTreeNode({}),
]);
const path = [666];
const newChildren = [
makeTestDominatorTreeNode({
nodeId: 777,
parentId: 666
}),
makeTestDominatorTreeNode({
nodeId: 888,
parentId: 666
}),
];
const moreChildrenAvailable = false;
const expected = {
nodeId: 666,
label: undefined,
parentId: undefined,
shallowSize: 1,
retainedSize: 7,
children: [
{
nodeId: 0,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 4,
label: undefined,
shallowSize: 1,
retainedSize: 4,
parentId: 666,
children: [
{
nodeId: 1,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 4,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 2,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 4,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 3,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 4,
moreChildrenAvailable: true,
children: undefined
}
],
moreChildrenAvailable: true
},
{
nodeId: 5,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 777,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
},
{
nodeId: 888,
label: undefined,
shallowSize: 1,
retainedSize: 1,
parentId: 666,
moreChildrenAvailable: true,
children: undefined
}
],
moreChildrenAvailable: false
};
function run_test() {
assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
}

View file

@ -0,0 +1,164 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we correctly set `moreChildrenAvailable` when doing a partial
// traversal of a dominator tree to create the initial incrementally loaded
// `DominatorTreeNode` tree.
// `tree` maps parent to children:
//
// 100
// |- 200
// | |- 500
// | |- 600
// | `- 700
// |- 300
// | |- 800
// | |- 900
// `- 400
// |- 1000
// |- 1100
// `- 1200
const tree = new Map([
[100, [200, 300, 400]],
[200, [500, 600, 700]],
[300, [800, 900]],
[400, [1000, 1100, 1200]]
]);
const mockDominatorTree = {
root: 100,
getRetainedSize: _ => 10,
getImmediatelyDominated: id => (tree.get(id) || []).slice()
};
const mockSnapshot = {
describeNode: _ => ({
objects: { count: 0, bytes: 0 },
strings: { count: 0, bytes: 0 },
scripts: { count: 0, bytes: 0 },
other: { SomeType: { count: 1, bytes: 10 } }
})
};
const breakdown = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true }
},
};
const expected = {
nodeId: 100,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
shortestPaths: undefined,
children: [
{
nodeId: 200,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
parentId: 100,
shortestPaths: undefined,
children: [
{
nodeId: 500,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
parentId: 200,
moreChildrenAvailable: false,
shortestPaths: undefined,
children: undefined
},
{
nodeId: 600,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
parentId: 200,
moreChildrenAvailable: false,
shortestPaths: undefined,
children: undefined
}
],
moreChildrenAvailable: true
},
{
nodeId: 300,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
parentId: 100,
shortestPaths: undefined,
children: [
{
nodeId: 800,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
parentId: 300,
moreChildrenAvailable: false,
shortestPaths: undefined,
children: undefined
},
{
nodeId: 900,
label: [
"other",
"SomeType"
],
shallowSize: 10,
retainedSize: 10,
parentId: 300,
moreChildrenAvailable: false,
shortestPaths: undefined,
children: undefined
}
],
moreChildrenAvailable: false
}
],
moreChildrenAvailable: true,
parentId: undefined,
};
function run_test() {
// Traverse the whole depth of the test tree, but one short of the number of
// siblings. This will exercise the moreChildrenAvailable handling for
// siblings.
const actual = DominatorTreeNode.partialTraversal(mockDominatorTree,
mockSnapshot,
breakdown,
/* maxDepth = */ 4,
/* siblings = */ 2);
dumpn("Expected = " + JSON.stringify(expected, null, 2));
dumpn("Actual = " + JSON.stringify(actual, null, 2));
assertStructurallyEquivalent(expected, actual);
}

View file

@ -0,0 +1,23 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can compute dominator trees.
function run_test() {
const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
ok(dominatorTree);
ok(dominatorTree instanceof DominatorTree);
let threw = false;
try {
new DominatorTree();
} catch (e) {
threw = true;
}
ok(threw, "Constructor shouldn't be usable");
do_test_finished();
}

View file

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can compute dominator trees from a snapshot in a worker.
add_task(function* () {
const worker = new ChromeWorker("resource://test/dominator-tree-worker.js");
worker.postMessage({});
let assertionCount = 0;
worker.onmessage = e => {
if (e.data.type !== "assertion") {
return;
}
ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
assertionCount++;
};
yield waitForDone(worker);
ok(assertionCount > 0);
worker.terminate();
});
function waitForDone(w) {
return new Promise((resolve, reject) => {
w.onerror = e => {
reject();
ok(false, "Error in worker: " + e);
};
w.addEventListener("message", function listener(e) {
if (e.data.type === "done") {
w.removeEventListener("message", listener, false);
resolve();
}
}, false);
});
}

View file

@ -0,0 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can get the root of dominator trees.
function run_test() {
const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
equal(typeof dominatorTree.root, "number", "root should be a number");
equal(Math.floor(dominatorTree.root), dominatorTree.root, "root should be an integer");
ok(dominatorTree.root >= 0, "root should be positive");
ok(dominatorTree.root <= Math.pow(2, 48), "root should be less than or equal to 2^48");
do_test_finished();
}

View file

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can get the retained sizes of dominator trees.
function run_test() {
const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
equal(typeof dominatorTree.getRetainedSize, "function",
"getRetainedSize should be a function");
const size = dominatorTree.getRetainedSize(dominatorTree.root);
ok(size, "should get a size for the root");
equal(typeof size, "number", "retained sizes should be a number");
equal(Math.floor(size), size, "size should be an integer");
ok(size > 0, "size should be positive");
ok(size <= Math.pow(2, 64), "size should be less than or equal to 2^64");
const bad = dominatorTree.getRetainedSize(1);
equal(bad, null, "null is returned for unknown node ids");
do_test_finished();
}

View file

@ -0,0 +1,75 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can get the set of immediately dominated nodes for any given
// node and that this forms a tree.
function run_test() {
var dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
equal(typeof dominatorTree.getImmediatelyDominated, "function",
"getImmediatelyDominated should be a function");
// Do a traversal of the dominator tree.
//
// Note that we don't assert directly, only if we get an unexpected
// value. There are just way too many nodes in the heap graph to assert for
// every one. This test would constantly time out and assertion messages would
// overflow the log size.
var root = dominatorTree.root;
equal(dominatorTree.getImmediateDominator(root), null,
"The root should not have a parent");
var seen = new Set();
var stack = [root];
while (stack.length > 0) {
var top = stack.pop();
if (seen.has(top)) {
ok(false,
"This is a tree, not a graph: we shouldn't have multiple edges to the same node");
}
seen.add(top);
if (seen.size % 1000 === 0) {
dumpn("Progress update: seen size = " + seen.size);
}
var newNodes = dominatorTree.getImmediatelyDominated(top);
if (Object.prototype.toString.call(newNodes) !== "[object Array]") {
ok(false, "getImmediatelyDominated should return an array for known node ids");
}
var topSize = dominatorTree.getRetainedSize(top);
var lastSize = Infinity;
for (var i = 0; i < newNodes.length; i++) {
if (typeof newNodes[i] !== "number") {
ok(false, "Every dominated id should be a number");
}
if (dominatorTree.getImmediateDominator(newNodes[i]) !== top) {
ok(false, "child's parent should be the expected parent");
}
var thisSize = dominatorTree.getRetainedSize(newNodes[i]);
if (thisSize >= topSize) {
ok(false, "the size of children in the dominator tree should always be less than that of their parent");
}
if (thisSize > lastSize) {
ok(false,
"children should be sorted by greatest to least retained size, "
+ "lastSize = " + lastSize + ", thisSize = " + thisSize);
}
lastSize = thisSize;
stack.push(newNodes[i]);
}
}
ok(true, "Successfully walked the tree");
dumpn("Walked " + seen.size + " nodes");
do_test_finished();
}

View file

@ -0,0 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the retained size of a node is the sum of its children retained
// sizes plus its shallow size.
// Note that we don't assert directly, only if we get an unexpected
// value. There are just way too many nodes in the heap graph to assert for
// every one. This test would constantly time out and assertion messages would
// overflow the log size.
function fastAssert(cond, msg) {
if (!cond) {
ok(false, msg);
}
}
var COUNT = { by: "count", count: false, bytes: true };
function run_test() {
var path = saveNewHeapSnapshot();
var snapshot = ChromeUtils.readHeapSnapshot(path);
var dominatorTree = snapshot.computeDominatorTree();
// Do a traversal of the dominator tree and assert the relationship between
// retained size, shallow size, and children's retained sizes.
var root = dominatorTree.root;
var stack = [root];
while (stack.length > 0) {
var top = stack.pop();
var children = dominatorTree.getImmediatelyDominated(top);
var topRetainedSize = dominatorTree.getRetainedSize(top);
var topShallowSize = snapshot.describeNode(COUNT, top).bytes;
fastAssert(topShallowSize <= topRetainedSize,
"The shallow size should be less than or equal to the " +
"retained size");
var sumOfChildrensRetainedSizes = 0;
for (var i = 0; i < children.length; i++) {
sumOfChildrensRetainedSizes += dominatorTree.getRetainedSize(children[i]);
stack.push(children[i]);
}
fastAssert(sumOfChildrensRetainedSizes <= topRetainedSize,
"The sum of the children's retained sizes should be less than " +
"or equal to the retained size");
fastAssert(sumOfChildrensRetainedSizes + topShallowSize === topRetainedSize,
"The sum of the children's retained sizes plus the shallow " +
"size should be equal to the retained size");
}
ok(true, "Successfully walked the tree");
do_test_finished();
}

View file

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
equal(typeof dominatorTreeId, "number",
"should get a dominator tree id, and it should be a number");
client.destroy();
});

View file

@ -0,0 +1,23 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request with bad
// file paths.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
let threw = false;
try {
yield client.computeDominatorTree("/etc/passwd");
} catch (_) {
threw = true;
}
ok(threw, "should throw when given a bad path");
client.destroy();
});

View file

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can delete heap snapshots.
function run_test() {
run_next_test();
}
const breakdown = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
let dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
ok(true, "Should have computed the dominator tree");
yield client.deleteHeapSnapshot(snapshotFilePath);
ok(true, "Should have deleted the snapshot");
let threw = false;
try {
yield client.getDominatorTree({
dominatorTreeId: dominatorTreeId,
breakdown
});
} catch (_) {
threw = true;
}
ok(threw, "getDominatorTree on deleted tree should throw an error");
threw = false;
try {
yield client.computeDominatorTree(snapshotFilePath);
} catch (_) {
threw = true;
}
ok(threw, "computeDominatorTree on deleted snapshot should throw an error");
threw = false;
try {
yield client.takeCensus(snapshotFilePath);
} catch (_) {
threw = true;
}
ok(threw, "takeCensus on deleted tree should throw an error");
client.destroy();
});

View file

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test deleteHeapSnapshot is a noop if the provided path matches no snapshot
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
let threw = false;
try {
yield client.deleteHeapSnapshot("path-does-not-exist");
} catch (_) {
threw = true;
}
ok(threw, "deleteHeapSnapshot on non-existant path should throw an error");
client.destroy();
});

View file

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test other dominatorTrees can still be retrieved after deleting a snapshot
function run_test() {
run_next_test();
}
const breakdown = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
function* createSnapshotAndDominatorTree(client) {
let snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
let dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
return { dominatorTreeId, snapshotFilePath };
}
add_task(function* () {
const client = new HeapAnalysesClient();
let savedSnapshots = [
yield createSnapshotAndDominatorTree(client),
yield createSnapshotAndDominatorTree(client),
yield createSnapshotAndDominatorTree(client)
];
ok(true, "Create 3 snapshots and dominator trees");
yield client.deleteHeapSnapshot(savedSnapshots[1].snapshotFilePath);
ok(true, "Snapshot deleted");
let tree = yield client.getDominatorTree({
dominatorTreeId: savedSnapshots[0].dominatorTreeId,
breakdown
});
ok(tree, "Should get a valid tree for first snapshot");
let threw = false;
try {
yield client.getDominatorTree({
dominatorTreeId: savedSnapshots[1].dominatorTreeId,
breakdown
});
} catch (_) {
threw = true;
}
ok(threw, "getDominatorTree on a deleted snapshot should throw an error");
tree = yield client.getDominatorTree({
dominatorTreeId: savedSnapshots[2].dominatorTreeId,
breakdown
});
ok(tree, "Should get a valid tree for third snapshot");
client.destroy();
});

View file

@ -0,0 +1,89 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can get census individuals.
function run_test() {
run_next_test();
}
const COUNT = { by: "count", count: true, bytes: true };
const CENSUS_BREAKDOWN = {
by: "coarseType",
objects: COUNT,
strings: COUNT,
scripts: COUNT,
other: COUNT,
};
const LABEL_BREAKDOWN = {
by: "internalType",
then: COUNT,
};
const MAX_INDIVIDUALS = 10;
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
ok(true, "Should have computed dominator tree");
const { report } = yield client.takeCensus(snapshotFilePath,
{ breakdown: CENSUS_BREAKDOWN },
{ asTreeNode: true });
ok(report, "Should get a report");
let nodesWithLeafIndicesFound = 0;
yield* (function* assertCanGetIndividuals(censusNode) {
if (censusNode.reportLeafIndex !== undefined) {
nodesWithLeafIndicesFound++;
const response = yield client.getCensusIndividuals({
dominatorTreeId,
indices: DevToolsUtils.isSet(censusNode.reportLeafIndex)
? censusNode.reportLeafIndex
: new Set([censusNode.reportLeafIndex]),
censusBreakdown: CENSUS_BREAKDOWN,
labelBreakdown: LABEL_BREAKDOWN,
maxRetainingPaths: 1,
maxIndividuals: MAX_INDIVIDUALS,
});
dumpn(`response = ${JSON.stringify(response, null, 4)}`);
equal(response.nodes.length, Math.min(MAX_INDIVIDUALS, censusNode.count),
"response.nodes.length === Math.min(MAX_INDIVIDUALS, censusNode.count)");
let lastRetainedSize = Infinity;
for (let individual of response.nodes) {
equal(typeof individual.nodeId, "number",
"individual.nodeId should be a number");
ok(individual.retainedSize <= lastRetainedSize,
"individual.retainedSize <= lastRetainedSize");
lastRetainedSize = individual.retainedSize;
ok(individual.shallowSize, "individual.shallowSize should exist and be non-zero");
ok(individual.shortestPaths, "individual.shortestPaths should exist");
ok(individual.shortestPaths.nodes, "individual.shortestPaths.nodes should exist");
ok(individual.shortestPaths.edges, "individual.shortestPaths.edges should exist");
ok(individual.label, "individual.label should exist");
}
}
if (censusNode.children) {
for (let child of censusNode.children) {
yield* assertCanGetIndividuals(child);
}
}
}(report));
equal(nodesWithLeafIndicesFound, 4, "Should have found a leaf for each coarse type");
client.destroy();
});

View file

@ -0,0 +1,58 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can get a HeapSnapshot's
// creation time.
function waitForThirtyMilliseconds() {
const start = Date.now();
while (Date.now() - start < 30) ;
}
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
add_task(function* () {
const client = new HeapAnalysesClient();
const start = Date.now() * 1000;
// Because Date.now() is less precise than the snapshot's time stamp, give it
// a little bit of head room. Additionally, WinXP's timers have a granularity
// of only +/-15 ms.
waitForThirtyMilliseconds();
const snapshotFilePath = saveNewHeapSnapshot();
waitForThirtyMilliseconds();
const end = Date.now() * 1000;
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
let threw = false;
try {
yield client.getCreationTime("/not/a/real/path", {
breakdown: BREAKDOWN
});
} catch (_) {
threw = true;
}
ok(threw, "getCreationTime should throw when snapshot does not exist");
let time = yield client.getCreationTime(snapshotFilePath, {
breakdown: BREAKDOWN
});
dumpn("Start = " + start);
dumpn("End = " + end);
dumpn("Time = " + time);
ok(time >= start, "creation time occurred after start");
ok(time <= end, "creation time occurred before end");
client.destroy();
});

View file

@ -0,0 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request.
function run_test() {
run_next_test();
}
const breakdown = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
equal(typeof dominatorTreeId, "number",
"should get a dominator tree id, and it should be a number");
const partialTree = yield client.getDominatorTree({
dominatorTreeId,
breakdown
});
ok(partialTree, "Should get a partial tree");
equal(typeof partialTree, "object",
"partialTree should be an object");
function checkTree(node) {
equal(typeof node.nodeId, "number", "each node should have an id");
if (node === partialTree) {
equal(node.parentId, undefined, "the root has no parent");
} else {
equal(typeof node.parentId, "number", "each node should have a parent id");
}
equal(typeof node.retainedSize, "number",
"each node should have a retained size");
ok(node.children === undefined || Array.isArray(node.children),
"each node either has a list of children, or undefined meaning no children loaded");
equal(typeof node.moreChildrenAvailable, "boolean",
"each node should indicate if there are more children available or not");
equal(typeof node.shortestPaths, "object",
"Should have shortest paths");
equal(typeof node.shortestPaths.nodes, "object",
"Should have shortest paths' nodes");
equal(typeof node.shortestPaths.edges, "object",
"Should have shortest paths' edges");
if (node.children) {
node.children.forEach(checkTree);
}
}
checkTree(partialTree);
client.destroy();
});

View file

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request with bad
// dominator tree ids.
function run_test() {
run_next_test();
}
const breakdown = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
add_task(function* () {
const client = new HeapAnalysesClient();
let threw = false;
try {
yield client.getDominatorTree({ dominatorTreeId: 42, breakdown });
} catch (_) {
threw = true;
}
ok(threw, "should throw when given a bad id");
client.destroy();
});

View file

@ -0,0 +1,81 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the HeapAnalyses{Client,Worker} "getImmediatelyDominated" request.
function run_test() {
run_next_test();
}
const breakdown = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
const partialTree = yield client.getDominatorTree({
dominatorTreeId,
breakdown
});
ok(partialTree.children.length > 0,
"root should immediately dominate some nodes");
// First, test getting a subset of children available.
const response = yield client.getImmediatelyDominated({
dominatorTreeId,
breakdown,
nodeId: partialTree.nodeId,
startIndex: 0,
maxCount: partialTree.children.length - 1
});
ok(Array.isArray(response.nodes));
ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
ok(response.moreChildrenAvailable);
equal(response.path.length, 1);
equal(response.path[0], partialTree.nodeId);
for (let node of response.nodes) {
equal(typeof node.shortestPaths, "object",
"Should have shortest paths");
equal(typeof node.shortestPaths.nodes, "object",
"Should have shortest paths' nodes");
equal(typeof node.shortestPaths.edges, "object",
"Should have shortest paths' edges");
}
// Next, test getting a subset of children available.
const secondResponse = yield client.getImmediatelyDominated({
dominatorTreeId,
breakdown,
nodeId: partialTree.nodeId,
startIndex: 0,
maxCount: Infinity
});
ok(Array.isArray(secondResponse.nodes));
ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
ok(!secondResponse.moreChildrenAvailable);
equal(secondResponse.path.length, 1);
equal(secondResponse.path[0], partialTree.nodeId);
for (let node of secondResponse.nodes) {
equal(typeof node.shortestPaths, "object",
"Should have shortest paths");
equal(typeof node.shortestPaths.nodes, "object",
"Should have shortest paths' nodes");
equal(typeof node.shortestPaths.edges, "object",
"Should have shortest paths' edges");
}
client.destroy();
});

View file

@ -0,0 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can read heap snapshots.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
client.destroy();
});

View file

@ -0,0 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses.
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "objectClass",
then: { by: "count", count: true, bytes: false },
other: { by: "count", count: true, bytes: false },
};
add_task(function* () {
const client = new HeapAnalysesClient();
const markers = [allocationMarker()];
const firstSnapshotFilePath = saveNewHeapSnapshot();
// Allocate and hold an additional AllocationMarker object so we can see it in
// the next heap snapshot.
markers.push(allocationMarker());
const secondSnapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(firstSnapshotFilePath);
yield client.readHeapSnapshot(secondSnapshotFilePath);
ok(true, "Should have read both heap snapshot files");
const { delta } = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN });
equal(delta.AllocationMarker.count, 1,
"There exists one new AllocationMarker in the second heap snapshot");
const { delta: deltaTreeNode } = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN },
{ asTreeNode: true });
// Have to manually set these because symbol properties aren't structured
// cloned.
delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
compareCensusViewData(BREAKDOWN, delta, deltaTreeNode,
"Returning delta-census as a tree node represents same data as the report");
client.destroy();
});

View file

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses as
// inverted trees.
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
add_task(function* () {
const firstSnapshotFilePath = saveNewHeapSnapshot();
const secondSnapshotFilePath = saveNewHeapSnapshot();
const client = new HeapAnalysesClient();
yield client.readHeapSnapshot(firstSnapshotFilePath);
yield client.readHeapSnapshot(secondSnapshotFilePath);
ok(true, "Should have read both heap snapshot files");
const { delta } = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN });
const { delta: deltaTreeNode } = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN },
{ asInvertedTreeNode: true });
// Have to manually set these because symbol properties aren't structured
// cloned.
delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
compareCensusViewData(BREAKDOWN, delta, deltaTreeNode, { invert: true });
client.destroy();
});

View file

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const { report } = yield client.takeCensus(snapshotFilePath);
ok(report, "Should get a report");
equal(typeof report, "object", "report should be an object");
ok(report.objects);
ok(report.scripts);
ok(report.strings);
ok(report.other);
client.destroy();
});

View file

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses with breakdown
// options.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const { report } = yield client.takeCensus(snapshotFilePath, {
breakdown: { by: "count", count: true, bytes: true }
});
ok(report, "Should get a report");
equal(typeof report, "object", "report should be an object");
ok(report.count);
ok(report.bytes);
client.destroy();
});

View file

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} bubbles errors properly when things
// go wrong.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
// Snapshot file path to a file that doesn't exist.
let failed = false;
try {
yield client.readHeapSnapshot(getFilePath("foo-bar-baz" + Math.random(), true));
} catch (e) {
failed = true;
}
ok(failed, "should not read heap snapshots that do not exist");
// Snapshot file path to a file that is not a heap snapshot.
failed = false;
try {
yield client.readHeapSnapshot(getFilePath("test_HeapAnalyses_takeCensus_03.js"));
} catch (e) {
failed = true;
}
ok(failed, "should not be able to read a file that is not a heap snapshot as a heap snapshot");
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
// Bad census breakdown options.
failed = false;
try {
yield client.takeCensus(snapshotFilePath, {
breakdown: { by: "some classification that we do not have" }
});
} catch (e) {
failed = true;
}
ok(failed, "should not be able to breakdown by an unknown classification");
client.destroy();
});

View file

@ -0,0 +1,118 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can send SavedFrame stacks from
// by-allocation-stack reports from the worker.
function run_test() {
run_next_test();
}
add_task(function* test() {
const client = new HeapAnalysesClient();
// Track some allocation stacks.
const g = newGlobal();
const dbg = new Debugger(g);
g.eval(` // 1
this.log = []; // 2
function f() { this.log.push(allocationMarker()); } // 3
function g() { this.log.push(allocationMarker()); } // 4
function h() { this.log.push(allocationMarker()); } // 5
`); // 6
// Create one allocationMarker with tracking turned off,
// so it will have no associated stack.
g.f();
dbg.memory.allocationSamplingProbability = 1;
for (let [func, n] of [ [g.f, 20],
[g.g, 10],
[g.h, 5] ]) {
for (let i = 0; i < n; i++) {
dbg.memory.trackingAllocationSites = true;
// All allocations of allocationMarker occur with this line as the oldest
// stack frame.
func();
dbg.memory.trackingAllocationSites = false;
}
}
// Take a heap snapshot.
const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
// Run a census broken down by class name -> allocation stack so we can grab
// only the AllocationMarker objects we have complete control over.
const { report } = yield client.takeCensus(snapshotFilePath, {
breakdown: { by: "objectClass",
then: { by: "allocationStack",
then: { by: "count",
bytes: true,
count: true
},
noStack: { by: "count",
bytes: true,
count: true
}
}
}
});
// Test the generated report.
ok(report, "Should get a report");
const map = report.AllocationMarker;
ok(map, "Should get AllocationMarkers in the report.");
// From a module with a different global, and therefore a different Map
// constructor, so we can't use instanceof.
equal(map.__proto__.constructor.name, "Map");
equal(map.size, 4, "Should have 4 allocation stacks (including the lack of a stack)");
// Gather the stacks we are expecting to appear as keys, and
// check that there are no unexpected keys.
let stacks = {};
map.forEach((v, k) => {
if (k === "noStack") {
// No need to save this key.
} else if (k.functionDisplayName === "f" &&
k.parent.functionDisplayName === "test") {
stacks.f = k;
} else if (k.functionDisplayName === "g" &&
k.parent.functionDisplayName === "test") {
stacks.g = k;
} else if (k.functionDisplayName === "h" &&
k.parent.functionDisplayName === "test") {
stacks.h = k;
} else {
dumpn("Unexpected allocation stack:");
k.toString().split(/\n/g).forEach(s => dumpn(s));
ok(false);
}
});
ok(map.get("noStack"));
equal(map.get("noStack").count, 1);
ok(stacks.f);
ok(map.get(stacks.f));
equal(map.get(stacks.f).count, 20);
ok(stacks.g);
ok(map.get(stacks.g));
equal(map.get(stacks.g).count, 10);
ok(stacks.h);
ok(map.get(stacks.h));
equal(map.get(stacks.h).count, 5);
client.destroy();
});

View file

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses and return
// a CensusTreeNode.
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const { report } = yield client.takeCensus(snapshotFilePath, {
breakdown: BREAKDOWN
});
const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
breakdown: BREAKDOWN
}, {
asTreeNode: true
});
ok(treeNode.children.length > 0, "treeNode has children");
ok(treeNode.children.every(type => {
return "name" in type &&
"bytes" in type &&
"count" in type;
}), "all of tree node's children have name, bytes, count");
compareCensusViewData(BREAKDOWN, report, treeNode,
"Returning census as a tree node represents same data as the report");
client.destroy();
});

View file

@ -0,0 +1,109 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses by
// "allocationStack" and return a CensusTreeNode.
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "objectClass",
then: {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true }
},
other: { by: "count", count: true, bytes: true }
};
add_task(function* () {
const g = newGlobal();
const dbg = new Debugger(g);
// 5 allocation markers with no stack.
g.eval(`
this.markers = [];
for (var i = 0; i < 5; i++) {
markers.push(allocationMarker());
}
`);
dbg.memory.allocationSamplingProbability = 1;
dbg.memory.trackingAllocationSites = true;
// 5 allocation markers at 5 stacks.
g.eval(`
(function shouldHaveCountOfOne() {
markers.push(allocationMarker());
markers.push(allocationMarker());
markers.push(allocationMarker());
markers.push(allocationMarker());
markers.push(allocationMarker());
}());
`);
// 5 allocation markers at 1 stack.
g.eval(`
(function shouldHaveCountOfFive() {
for (var i = 0; i < 5; i++) {
markers.push(allocationMarker());
}
}());
`);
const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
const client = new HeapAnalysesClient();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const { report } = yield client.takeCensus(snapshotFilePath, {
breakdown: BREAKDOWN
});
const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
breakdown: BREAKDOWN
}, {
asTreeNode: true
});
const markers = treeNode.children.find(c => c.name === "AllocationMarker");
ok(markers);
const noStack = markers.children.find(c => c.name === "noStack");
equal(noStack.count, 5);
let numShouldHaveFiveFound = 0;
let numShouldHaveOneFound = 0;
function walk(node) {
if (node.children) {
node.children.forEach(walk);
}
if (!isSavedFrame(node.name)) {
return;
}
if (node.name.functionDisplayName === "shouldHaveCountOfFive") {
equal(node.count, 5, "shouldHaveCountOfFive should have count of five");
numShouldHaveFiveFound++;
}
if (node.name.functionDisplayName === "shouldHaveCountOfOne") {
equal(node.count, 1, "shouldHaveCountOfOne should have count of one");
numShouldHaveOneFound++;
}
}
markers.children.forEach(walk);
equal(numShouldHaveFiveFound, 1);
equal(numShouldHaveOneFound, 5);
compareCensusViewData(BREAKDOWN, report, treeNode,
"Returning census as a tree node represents same data as the report");
client.destroy();
});

View file

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses and return
// an inverted CensusTreeNode.
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const { report } = yield client.takeCensus(snapshotFilePath, {
breakdown: BREAKDOWN
});
const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
breakdown: BREAKDOWN
}, {
asInvertedTreeNode: true
});
compareCensusViewData(BREAKDOWN, report, treeNode, { invert: true });
client.destroy();
});

View file

@ -0,0 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can compute shortest paths.
//
// Because the actual heap graph is too unpredictable and likely to drastically
// change as various implementation bits change, we don't test exact paths
// here. See js/src/jsapi-tests/testUbiNode.cpp for such tests, where we can
// control the specific graph shape and structure and so testing exact paths is
// reliable.
function run_test() {
const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
const dominatedByRoot = dominatorTree.getImmediatelyDominated(dominatorTree.root)
.slice(0, 10);
ok(dominatedByRoot);
ok(dominatedByRoot.length);
const targetSet = new Set(dominatedByRoot);
const shortestPaths = snapshot.computeShortestPaths(dominatorTree.root, dominatedByRoot, 2);
ok(shortestPaths);
ok(shortestPaths instanceof Map);
ok(shortestPaths.size === targetSet.size);
for (let [target, paths] of shortestPaths) {
ok(targetSet.has(target),
"We should only get paths for our targets");
targetSet.delete(target);
ok(paths.length > 0,
"We must have at least one path, since the target is dominated by the root");
ok(paths.length <= 2,
"Should not have recorded more paths than the max requested");
dumpn("---------------------");
dumpn("Shortest paths for 0x" + target.toString(16) + ":");
for (let path of paths) {
dumpn(" path =");
for (let part of path) {
dumpn(" predecessor: 0x" + part.predecessor.toString(16) +
"; edge: " + part.edge);
}
}
dumpn("---------------------");
for (let path of paths) {
ok(path.length > 0, "Cannot have zero length paths");
ok(path[0].predecessor === dominatorTree.root,
"The first predecessor is always our start node");
for (let part of path) {
ok(part.predecessor, "Each part of a path has a predecessor");
ok(!!snapshot.describeNode({ by: "count", count: true, bytes: true},
part.predecessor),
"The predecessor is in the heap snapshot");
ok("edge" in part, "Each part has an (potentially null) edge property");
}
}
}
ok(targetSet.size === 0,
"We found paths for all of our targets");
do_test_finished();
}

View file

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test computing shortest paths with invalid arguments.
function run_test() {
const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
const target = dominatorTree.getImmediatelyDominated(dominatorTree.root).pop();
ok(target);
let threw = false;
try {
snapshot.computeShortestPaths(0, [target], 2);
} catch (_) {
threw = true;
}
ok(threw, "invalid start node should throw");
threw = false;
try {
snapshot.computeShortestPaths(dominatorTree.root, [0], 2);
} catch (_) {
threw = true;
}
ok(threw, "invalid target nodes should throw");
threw = false;
try {
snapshot.computeShortestPaths(dominatorTree.root, [], 2);
} catch (_) {
threw = true;
}
ok(threw, "empty target nodes should throw");
threw = false;
try {
snapshot.computeShortestPaths(dominatorTree.root, [target], 0);
} catch (_) {
threw = true;
}
ok(threw, "0 max paths should throw");
do_test_finished();
}

View file

@ -0,0 +1,30 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.creationTime returns the expected time.
function waitForThirtyMilliseconds() {
const start = Date.now();
while (Date.now() - start < 30) ;
}
function run_test() {
const start = Date.now() * 1000;
do_print("start = " + start);
// Because Date.now() is less precise than the snapshot's time stamp, give it
// a little bit of head room. Additionally, WinXP's timer only has granularity
// of +/- 15ms.
waitForThirtyMilliseconds();
const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
waitForThirtyMilliseconds();
const end = Date.now() * 1000;
do_print("end = " + end);
const snapshot = ChromeUtils.readHeapSnapshot(path);
do_print("snapshot.creationTime = " + snapshot.creationTime);
ok(snapshot.creationTime >= start);
ok(snapshot.creationTime <= end);
}

View file

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can save a core dump with very deep allocation stacks and read
// it back into a HeapSnapshot.
function stackDepth(stack) {
return stack ? 1 + stackDepth(stack.parent) : 0;
}
function run_test() {
// Create a Debugger observing a debuggee's allocations.
const debuggee = new Cu.Sandbox(null);
const dbg = new Debugger(debuggee);
dbg.memory.trackingAllocationSites = true;
// Allocate some objects in the debuggee that will have their allocation
// stacks recorded by the Debugger.
debuggee.eval("this.objects = []");
debuggee.eval(
(function recursiveAllocate(n) {
if (n <= 0)
return;
// Make sure to recurse before pushing the object so that when TCO is
// implemented sometime in the future, it doesn't invalidate this test.
recursiveAllocate(n - 1);
this.objects.push({});
}).toString()
);
debuggee.eval("recursiveAllocate = recursiveAllocate.bind(this);");
debuggee.eval("recursiveAllocate(200);");
// Now save a snapshot that will include the allocation stacks and read it
// back again.
const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
ok(true, "Should be able to save a snapshot.");
const snapshot = ChromeUtils.readHeapSnapshot(filePath);
ok(snapshot, "Should be able to read a heap snapshot");
ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
const report = snapshot.takeCensus({
breakdown: { by: "allocationStack",
then: { by: "count", bytes: true, count: true },
noStack: { by: "count", bytes: true, count: true }
}
});
// Keep this synchronized with `HeapSnapshot::MAX_STACK_DEPTH`!
const MAX_STACK_DEPTH = 60;
let foundStacks = false;
report.forEach((v, k) => {
if (k === "noStack") {
return;
}
foundStacks = true;
const depth = stackDepth(k);
dumpn("Stack depth is " + depth);
ok(depth <= MAX_STACK_DEPTH,
"Every stack should have depth less than or equal to the maximum stack depth");
});
ok(foundStacks);
do_test_finished();
}

View file

@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can describe nodes with a breakdown.
function run_test() {
const path = saveNewHeapSnapshot();
const snapshot = ChromeUtils.readHeapSnapshot(path);
ok(snapshot.describeNode);
equal(typeof snapshot.describeNode, "function");
const dt = snapshot.computeDominatorTree();
let threw = false;
try {
snapshot.describeNode(undefined, dt.root);
} catch (_) {
threw = true;
}
ok(threw, "Should require a breakdown");
const breakdown = {
by: "coarseType",
objects: { by: "objectClass" },
scripts: { by: "internalType" },
strings: { by: "internalType" },
other: { by: "internalType" }
};
threw = false;
try {
snapshot.describeNode(breakdown, 0);
} catch (_) {
threw = true;
}
ok(threw, "Should throw when given an invalid node id");
const description = snapshot.describeNode(breakdown, dt.root);
ok(description);
ok(description.other);
ok(description.other["JS::ubi::RootList"]);
}

View file

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.takeCensus returns a value of an appropriate
// shape. Ported from js/src/jit-tests/debug/Memory-takeCensus-01.js
function run_test() {
var dbg = new Debugger;
function checkProperties(census) {
equal(typeof census, "object");
for (prop of Object.getOwnPropertyNames(census)) {
var desc = Object.getOwnPropertyDescriptor(census, prop);
equal(desc.enumerable, true);
equal(desc.configurable, true);
equal(desc.writable, true);
if (typeof desc.value === "object")
checkProperties(desc.value);
else
equal(typeof desc.value, "number");
}
}
checkProperties(saveHeapSnapshotAndTakeCensus(dbg));
var g = newGlobal();
dbg.addDebuggee(g);
checkProperties(saveHeapSnapshotAndTakeCensus(dbg));
do_test_finished();
}

View file

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.takeCensus behaves plausibly as we allocate objects.
//
// Exact object counts vary in ways we can't predict. For example,
// BaselineScripts can hold onto "template objects", which exist only to hold
// the shape and type for newly created objects. When BaselineScripts are
// discarded, these template objects go with them.
//
// So instead of expecting precise counts, we expect counts that are at least as
// many as we would expect given the object graph we've built.
//
// Ported from js/src/jit-tests/debug/Memory-takeCensus-02.js
function run_test() {
// A Debugger with no debuggees had better not find anything.
var dbg = new Debugger;
var census0 = saveHeapSnapshotAndTakeCensus(dbg);
Census.walkCensus(census0, "census0", Census.assertAllZeros);
function newGlobalWithDefs() {
var g = newGlobal();
g.eval(`
function times(n, fn) {
var a=[];
for (var i = 0; i<n; i++)
a.push(fn());
return a;
}
`);
return g;
}
// Allocate a large number of various types of objects, and check that census
// finds them.
var g = newGlobalWithDefs();
dbg.addDebuggee(g);
g.eval("var objs = times(100, () => ({}));");
g.eval("var rxs = times(200, () => /foo/);");
g.eval("var ars = times(400, () => []);");
g.eval("var fns = times(800, () => () => {});");
var census1 = dbg.memory.takeCensus(dbg);
Census.walkCensus(census1, "census1",
Census.assertAllNotLessThan(
{ "objects":
{ "Object": { count: 100 },
"RegExp": { count: 200 },
"Array": { count: 400 },
"Function": { count: 800 }
}
}));
do_test_finished();
}

View file

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.takeCensus behaves plausibly as we add and remove
// debuggees.
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-03.js
function run_test() {
var dbg = new Debugger;
var census0 = saveHeapSnapshotAndTakeCensus(dbg);
Census.walkCensus(census0, "census0", Census.assertAllZeros);
var g1 = newGlobal();
dbg.addDebuggee(g1);
var census1 = saveHeapSnapshotAndTakeCensus(dbg);
Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0));
var g2 = newGlobal();
dbg.addDebuggee(g2);
var census2 = saveHeapSnapshotAndTakeCensus(dbg);
Census.walkCensus(census2, "census2", Census.assertAllNotLessThan(census1));
dbg.removeDebuggee(g2);
var census3 = saveHeapSnapshotAndTakeCensus(dbg);
Census.walkCensus(census3, "census3", Census.assertAllEqual(census1));
dbg.removeDebuggee(g1);
var census4 = saveHeapSnapshotAndTakeCensus(dbg);
Census.walkCensus(census4, "census4", Census.assertAllEqual(census0));
do_test_finished();
}

View file

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that HeapSnapshot.prototype.takeCensus finds GC roots that are on the
// stack.
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-04.js
function run_test() {
var g = newGlobal();
var dbg = new Debugger(g);
g.eval(`
function withAllocationMarkerOnStack(f) {
(function () {
var onStack = allocationMarker();
f();
}());
}
`);
equal("AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects, false,
"There shouldn't exist any allocation markers in the census.");
var allocationMarkerCount;
g.withAllocationMarkerOnStack(() => {
const census = saveHeapSnapshotAndTakeCensus(dbg);
allocationMarkerCount = census.objects.AllocationMarker.count;
});
equal(allocationMarkerCount, 1,
"Should have one allocation marker in the census, because there " +
"was one on the stack.");
do_test_finished();
}

View file

@ -0,0 +1,24 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that HeapSnapshot.prototype.takeCensus finds cross compartment
// wrapper GC roots.
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-05.js
function run_test() {
var g = newGlobal();
var dbg = new Debugger(g);
equal("AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects, false,
"No allocation markers should exist in the census.");
this.ccw = g.allocationMarker();
const census = saveHeapSnapshotAndTakeCensus(dbg);
equal(census.objects.AllocationMarker.count, 1,
"Should have one allocation marker in the census, because there " +
"is one cross-compartment wrapper referring to it.");
do_test_finished();
}

View file

@ -0,0 +1,125 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Check HeapSnapshot.prototype.takeCensus handling of 'breakdown' argument.
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-06.js
function run_test() {
var Pattern = Match.Pattern;
var g = newGlobal();
var dbg = new Debugger(g);
Pattern({ count: Pattern.NATURAL,
bytes: Pattern.NATURAL })
.assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count" } }));
let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: false, bytes: false } });
equal("count" in census, false);
equal("bytes" in census, false);
census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: true, bytes: false } });
equal("count" in census, true);
equal("bytes" in census, false);
census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: false, bytes: true } });
equal("count" in census, false);
equal("bytes" in census, true);
census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: true, bytes: true } });
equal("count" in census, true);
equal("bytes" in census, true);
// Pattern doesn't mind objects with extra properties, so we'll restrict this
// list to the object classes we're pretty sure are going to stick around for
// the forseeable future.
Pattern({
Function: { count: Pattern.NATURAL },
Object: { count: Pattern.NATURAL },
Debugger: { count: Pattern.NATURAL },
Sandbox: { count: Pattern.NATURAL },
// The below are all Debugger prototype objects.
Source: { count: Pattern.NATURAL },
Environment: { count: Pattern.NATURAL },
Script: { count: Pattern.NATURAL },
Memory: { count: Pattern.NATURAL },
Frame: { count: Pattern.NATURAL }
})
.assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass" } }));
Pattern({
objects: { count: Pattern.NATURAL },
scripts: { count: Pattern.NATURAL },
strings: { count: Pattern.NATURAL },
other: { count: Pattern.NATURAL }
})
.assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "coarseType" } }));
// As for { by: 'objectClass' }, restrict our pattern to the types
// we predict will stick around for a long time.
Pattern({
JSString: { count: Pattern.NATURAL },
"js::Shape": { count: Pattern.NATURAL },
JSObject: { count: Pattern.NATURAL },
JSScript: { count: Pattern.NATURAL }
})
.assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "internalType" } }));
// Nested breakdowns.
let coarseTypePattern = {
objects: { count: Pattern.NATURAL },
scripts: { count: Pattern.NATURAL },
strings: { count: Pattern.NATURAL },
other: { count: Pattern.NATURAL }
};
Pattern({
JSString: coarseTypePattern,
"js::Shape": coarseTypePattern,
JSObject: coarseTypePattern,
JSScript: coarseTypePattern,
})
.assert(saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "internalType",
then: { by: "coarseType" }
}
}));
Pattern({
Function: { count: Pattern.NATURAL },
Object: { count: Pattern.NATURAL },
Debugger: { count: Pattern.NATURAL },
Sandbox: { count: Pattern.NATURAL },
other: coarseTypePattern
})
.assert(saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: {
by: "objectClass",
then: { by: "count" },
other: { by: "coarseType" }
}
}));
Pattern({
objects: { count: Pattern.NATURAL, label: "object" },
scripts: { count: Pattern.NATURAL, label: "scripts" },
strings: { count: Pattern.NATURAL, label: "strings" },
other: { count: Pattern.NATURAL, label: "other" }
})
.assert(saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: {
by: "coarseType",
objects: { by: "count", label: "object" },
scripts: { by: "count", label: "scripts" },
strings: { by: "count", label: "strings" },
other: { by: "count", label: "other" }
}
}));
do_test_finished();
}

View file

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.takeCensus breakdown: check error handling on property
// gets.
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-07.js
function run_test() {
var g = newGlobal();
var dbg = new Debugger(g);
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { get by() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "count", get count() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "count", get bytes() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "objectClass", get then() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "objectClass", get other() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "coarseType", get objects() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "coarseType", get scripts() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "coarseType", get strings() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "coarseType", get other() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "internalType", get then() { throw "ಠ_ಠ"; } }
});
}, "ಠ_ಠ");
do_test_finished();
}

View file

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.takeCensus: test by: 'count' breakdown
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-08.js
function run_test() {
let g = newGlobal();
let dbg = new Debugger(g);
g.eval(`
var stuff = [];
function add(n, c) {
for (let i = 0; i < n; i++)
stuff.push(c());
}
let count = 0;
function obj() { return { count: count++ }; }
obj.factor = 1;
// This creates a closure (a function JSObject) that has captured
// a Call object. So each call creates two items.
function fun() { let v = count; return () => { return v; } }
fun.factor = 2;
function str() { return 'perambulator' + count++; }
str.factor = 1;
// Eval a fresh text each time, allocating:
// - a fresh ScriptSourceObject
// - a new JSScripts, not an eval cache hits
// - a fresh prototype object
// - a fresh Call object, since the eval makes 'ev' heavyweight
// - the new function itself
function ev() {
return eval(\`(function () { return \${ count++ } })\`);
}
ev.factor = 5;
// A new object (1) with a new shape (2) with a new atom (3)
function shape() { return { [ 'theobroma' + count++ ]: count }; }
shape.factor = 3;
`);
let baseline = 0;
function countIncreasedByAtLeast(n) {
let oldBaseline = baseline;
// Since a census counts only reachable objects, one might assume that calling
// GC here would have no effect on the census results. But GC also throws away
// JIT code and any objects it might be holding (template objects, say);
// takeCensus reaches those. Shake everything loose that we can, to make the
// census approximate reachability a bit more closely, and make our results a
// bit more predictable.
gc(g, "shrinking");
baseline = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count" } }).count;
return baseline >= oldBaseline + n;
}
countIncreasedByAtLeast(0);
g.add(100, g.obj);
ok(countIncreasedByAtLeast(g.obj.factor * 100));
g.add(100, g.fun);
ok(countIncreasedByAtLeast(g.fun.factor * 100));
g.add(100, g.str);
ok(countIncreasedByAtLeast(g.str.factor * 100));
g.add(100, g.ev);
ok(countIncreasedByAtLeast(g.ev.factor * 100));
g.add(100, g.shape);
ok(countIncreasedByAtLeast(g.shape.factor * 100));
do_test_finished();
}

View file

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// HeapSnapshot.prototype.takeCensus: by: allocationStack breakdown
//
// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-09.js
function run_test() {
var g = newGlobal();
var dbg = new Debugger(g);
g.eval(` // 1
var log = []; // 2
function f() { log.push(allocationMarker()); } // 3
function g() { f(); } // 4
function h() { f(); } // 5
`); // 6
// Create one allocationMarker with tracking turned off,
// so it will have no associated stack.
g.f();
dbg.memory.allocationSamplingProbability = 1;
for ([func, n] of [[g.f, 20], [g.g, 10], [g.h, 5]]) {
for (let i = 0; i < n; i++) {
dbg.memory.trackingAllocationSites = true;
// All allocations of allocationMarker occur with this line as the oldest
// stack frame.
func();
dbg.memory.trackingAllocationSites = false;
}
}
let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass",
then: { by: "allocationStack",
then: { by: "count",
label: "haz stack"
},
noStack: { by: "count",
label: "no haz stack"
}
}
}
});
let map = census.AllocationMarker;
ok(map instanceof Map, "Should be a Map instance");
equal(map.size, 4, "Should have 4 allocation stacks (including the lack of a stack)");
// Gather the stacks we are expecting to appear as keys, and
// check that there are no unexpected keys.
let stacks = { };
map.forEach((v, k) => {
if (k === "noStack") {
// No need to save this key.
} else if (k.functionDisplayName === "f" &&
k.parent.functionDisplayName === "run_test") {
stacks.f = k;
} else if (k.functionDisplayName === "f" &&
k.parent.functionDisplayName === "g" &&
k.parent.parent.functionDisplayName === "run_test") {
stacks.fg = k;
} else if (k.functionDisplayName === "f" &&
k.parent.functionDisplayName === "h" &&
k.parent.parent.functionDisplayName === "run_test") {
stacks.fh = k;
} else {
dumpn("Unexpected allocation stack:");
k.toString().split(/\n/g).forEach(s => dumpn(s));
ok(false);
}
});
equal(map.get("noStack").label, "no haz stack");
equal(map.get("noStack").count, 1);
ok(stacks.f);
equal(map.get(stacks.f).label, "haz stack");
equal(map.get(stacks.f).count, 20);
ok(stacks.fg);
equal(map.get(stacks.fg).label, "haz stack");
equal(map.get(stacks.fg).count, 10);
ok(stacks.fh);
equal(map.get(stacks.fh).label, "haz stack");
equal(map.get(stacks.fh).count, 5);
do_test_finished();
}

View file

@ -0,0 +1,68 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Check byte counts produced by takeCensus.
//
// Ported from js/src/jit-test/tests/debug/Memory-take Census-10.js
function run_test() {
let g = newGlobal();
let dbg = new Debugger(g);
let sizeOfAM = byteSize(allocationMarker());
// Allocate a single allocation marker, and check that we can find it.
g.eval("var hold = allocationMarker();");
let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass" } });
equal(census.AllocationMarker.count, 1);
equal(census.AllocationMarker.bytes, sizeOfAM);
g.hold = null;
g.eval(` // 1
var objs = []; // 2
function fnerd() { // 3
objs.push(allocationMarker()); // 4
for (let i = 0; i < 10; i++) // 5
objs.push(allocationMarker()); // 6
} // 7
`); // 8
dbg.memory.allocationSamplingProbability = 1;
dbg.memory.trackingAllocationSites = true;
g.fnerd();
dbg.memory.trackingAllocationSites = false;
census = saveHeapSnapshotAndTakeCensus(dbg, {
breakdown: { by: "objectClass",
then: { by: "allocationStack" }
}
});
let seen = 0;
census.AllocationMarker.forEach((v, k) => {
equal(k.functionDisplayName, "fnerd");
switch (k.line) {
case 4:
equal(v.count, 1);
equal(v.bytes, sizeOfAM);
seen++;
break;
case 6:
equal(v.count, 10);
equal(v.bytes, 10 * sizeOfAM);
seen++;
break;
default:
dumpn("Unexpected stack:");
k.toString().split(/\n/g).forEach(s => dumpn(s));
ok(false);
break;
}
});
equal(seen, 2);
do_test_finished();
}

View file

@ -0,0 +1,116 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that Debugger.Memory.prototype.takeCensus and
// HeapSnapshot.prototype.takeCensus return the same data for the same heap
// graph.
function doLiveAndOfflineCensus(g, dbg, opts) {
dbg.memory.allocationSamplingProbability = 1;
dbg.memory.trackingAllocationSites = true;
g.eval(` // 1
(function unsafeAtAnySpeed() { // 2
for (var i = 0; i < 100; i++) { // 3
this.markers.push(allocationMarker()); // 4
} // 5
}()); // 6
`); // 7
dbg.memory.trackingAllocationSites = false;
return {
live: dbg.memory.takeCensus(opts),
offline: saveHeapSnapshotAndTakeCensus(dbg, opts)
};
}
function run_test() {
var g = newGlobal();
var dbg = new Debugger(g);
g.eval("this.markers = []");
const markerSize = byteSize(allocationMarker());
// First, test that we get the same counts and sizes as we allocate and retain
// more things.
let prevCount = 0;
let prevBytes = 0;
for (var i = 0; i < 10; i++) {
const { live, offline } = doLiveAndOfflineCensus(g, dbg, {
breakdown: { by: "objectClass",
then: { by: "count"} }
});
equal(live.AllocationMarker.count, offline.AllocationMarker.count);
equal(live.AllocationMarker.bytes, offline.AllocationMarker.bytes);
equal(live.AllocationMarker.count, prevCount + 100);
equal(live.AllocationMarker.bytes, prevBytes + 100 * markerSize);
prevCount = live.AllocationMarker.count;
prevBytes = live.AllocationMarker.bytes;
}
// Second, test that the reported allocation stacks and counts and sizes at
// those allocation stacks match up.
const { live, offline } = doLiveAndOfflineCensus(g, dbg, {
breakdown: { by: "objectClass",
then: { by: "allocationStack"} }
});
equal(live.AllocationMarker.size, offline.AllocationMarker.size);
// One stack with the loop further above, and another stack featuring the call
// right above.
equal(live.AllocationMarker.size, 2);
// Note that because SavedFrame stacks reconstructed from an offline heap
// snapshot don't have the same principals as SavedFrame stacks captured from
// a live stack, the live and offline allocation stacks won't be identity
// equal, but should be structurally the same.
const liveEntries = [];
live.AllocationMarker.forEach((v, k) => {
dumpn("Allocation stack:");
k.toString().split(/\n/g).forEach(s => dumpn(s));
equal(k.functionDisplayName, "unsafeAtAnySpeed");
equal(k.line, 4);
liveEntries.push([k.toString(), v]);
});
const offlineEntries = [];
offline.AllocationMarker.forEach((v, k) => {
dumpn("Allocation stack:");
k.toString().split(/\n/g).forEach(s => dumpn(s));
equal(k.functionDisplayName, "unsafeAtAnySpeed");
equal(k.line, 4);
offlineEntries.push([k.toString(), v]);
});
const sortEntries = (a, b) => {
if (a[0] < b[0]) {
return -1;
} else if (a[0] > b[0]) {
return 1;
} else {
return 0;
}
};
liveEntries.sort(sortEntries);
offlineEntries.sort(sortEntries);
equal(liveEntries.length, live.AllocationMarker.size);
equal(liveEntries.length, offlineEntries.length);
for (let i = 0; i < liveEntries.length; i++) {
equal(liveEntries[i][0], offlineEntries[i][0]);
equal(liveEntries[i][1].count, offlineEntries[i][1].count);
equal(liveEntries[i][1].bytes, offlineEntries[i][1].bytes);
}
do_test_finished();
}

View file

@ -0,0 +1,50 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that when we take a census and get a bucket list of ids that matched the
// given category, that the returned ids are all in the snapshot and their
// reported category.
function run_test() {
const g = newGlobal();
const dbg = new Debugger(g);
const path = saveNewHeapSnapshot({ debugger: dbg });
const snapshot = readHeapSnapshot(path);
const bucket = { by: "bucket" };
const count = { by: "count", count: true, bytes: false };
const objectClassCount = { by: "objectClass", then: count, other: count };
const byClassName = snapshot.takeCensus({
breakdown: {
by: "objectClass",
then: bucket,
other: bucket,
}
});
const byClassNameCount = snapshot.takeCensus({
breakdown: objectClassCount
});
const keys = new Set(Object.keys(byClassName));
equal(keys.size, Object.keys(byClassNameCount).length,
"Should have the same number of keys.");
for (let k of Object.keys(byClassNameCount)) {
ok(keys.has(k), "Should not have any unexpected class names");
}
for (let key of Object.keys(byClassName)) {
equal(byClassNameCount[key].count, byClassName[key].length,
"Length of the bucket and count should be equal");
for (let id of byClassName[key]) {
const desc = snapshot.describeNode(objectClassCount, id);
equal(desc[key].count, 1,
"Describing the bucketed node confirms that it belongs to the category");
}
}
do_test_finished();
}

View file

@ -0,0 +1,20 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can read core dumps into HeapSnapshot instances.
if (typeof Debugger != "function") {
const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
addDebuggerToGlobal(this);
}
function run_test() {
const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
ok(true, "Should be able to save a snapshot.");
const snapshot = ChromeUtils.readHeapSnapshot(filePath);
ok(snapshot, "Should be able to read a heap snapshot");
ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
do_test_finished();
}

View file

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can save a core dump with allocation stacks and read it back
// into a HeapSnapshot.
if (typeof Debugger != "function") {
const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
addDebuggerToGlobal(this);
}
function run_test() {
// Create a Debugger observing a debuggee's allocations.
const debuggee = new Cu.Sandbox(null);
const dbg = new Debugger(debuggee);
dbg.memory.trackingAllocationSites = true;
// Allocate some objects in the debuggee that will have their allocation
// stacks recorded by the Debugger.
debuggee.eval("this.objects = []");
for (let i = 0; i < 100; i++) {
debuggee.eval("this.objects.push({})");
}
// Now save a snapshot that will include the allocation stacks and read it
// back again.
const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
ok(true, "Should be able to save a snapshot.");
const snapshot = ChromeUtils.readHeapSnapshot(filePath);
ok(snapshot, "Should be able to read a heap snapshot");
ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
do_test_finished();
}

View file

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can read core dumps into HeapSnapshot instances in a worker.
add_task(function* () {
const worker = new ChromeWorker("resource://test/heap-snapshot-worker.js");
worker.postMessage({});
let assertionCount = 0;
worker.onmessage = e => {
if (e.data.type !== "assertion") {
return;
}
ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
assertionCount++;
};
yield waitForDone(worker);
ok(assertionCount > 0);
worker.terminate();
});
function waitForDone(w) {
return new Promise((resolve, reject) => {
w.onerror = e => {
reject();
ok(false, "Error in worker: " + e);
};
w.addEventListener("message", function listener(e) {
if (e.data.type === "done") {
w.removeEventListener("message", listener, false);
resolve();
}
}, false);
});
}

View file

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the ChromeUtils interface.
if (typeof Debugger != "function") {
const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
addDebuggerToGlobal(this);
}
function run_test() {
ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
testBadParameters();
testGoodParameters();
do_test_finished();
}
function testBadParameters() {
throws(() => ChromeUtils.saveHeapSnapshot(),
"Should throw if arguments aren't passed in.");
throws(() => ChromeUtils.saveHeapSnapshot(null),
"Should throw if boundaries isn't an object.");
throws(() => ChromeUtils.saveHeapSnapshot({}),
"Should throw if the boundaries object doesn't have any properties.");
throws(() => ChromeUtils.saveHeapSnapshot({ runtime: true,
globals: [this] }),
"Should throw if the boundaries object has more than one property.");
throws(() => ChromeUtils.saveHeapSnapshot({ debugger: {} }),
"Should throw if the debuggees object is not a Debugger object");
throws(() => ChromeUtils.saveHeapSnapshot({ globals: [{}] }),
"Should throw if the globals array contains non-global objects.");
throws(() => ChromeUtils.saveHeapSnapshot({ runtime: false }),
"Should throw if runtime is supplied and is not true.");
throws(() => ChromeUtils.saveHeapSnapshot({ globals: null }),
"Should throw if globals is not an object.");
throws(() => ChromeUtils.saveHeapSnapshot({ globals: {} }),
"Should throw if globals is not an array.");
throws(() => ChromeUtils.saveHeapSnapshot({ debugger: Debugger.prototype }),
"Should throw if debugger is the Debugger.prototype object.");
throws(() => ChromeUtils.saveHeapSnapshot({ get globals() { return [this]; } }),
"Should throw if boundaries property is a getter.");
}
const makeNewSandbox = () =>
Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
function testGoodParameters() {
let sandbox = makeNewSandbox();
let dbg = new Debugger(sandbox);
ChromeUtils.saveHeapSnapshot({ debugger: dbg });
ok(true, "Should be able to save a snapshot for a debuggee global.");
dbg = new Debugger;
let sandboxes = Array(10).fill(null).map(makeNewSandbox);
sandboxes.forEach(sb => dbg.addDebuggee(sb));
ChromeUtils.saveHeapSnapshot({ debugger: dbg });
ok(true, "Should be able to save a snapshot for many debuggee globals.");
dbg = new Debugger;
ChromeUtils.saveHeapSnapshot({ debugger: dbg });
ok(true, "Should be able to save a snapshot with no debuggee globals.");
ChromeUtils.saveHeapSnapshot({ globals: [this] });
ok(true, "Should be able to save a snapshot for a specific global.");
ChromeUtils.saveHeapSnapshot({ runtime: true });
ok(true, "Should be able to save a snapshot of the full runtime.");
}

View file

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests CensusTreeNode with `internalType` breakdown.
*/
const BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
const REPORT = {
"JSObject": {
"bytes": 100,
"count": 10,
},
"js::Shape": {
"bytes": 500,
"count": 50,
},
"JSString": {
"bytes": 10,
"count": 1,
},
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 610,
count: 0,
totalCount: 61,
children: [
{
name: "js::Shape",
bytes: 500,
totalBytes: 500,
count: 50,
totalCount: 50,
children: undefined,
id: 3,
parent: 1,
reportLeafIndex: 2,
},
{
name: "JSObject",
bytes: 100,
totalBytes: 100,
count: 10,
totalCount: 10,
children: undefined,
id: 2,
parent: 1,
reportLeafIndex: 1,
},
{
name: "JSString",
bytes: 10,
totalBytes: 10,
count: 1,
totalCount: 1,
children: undefined,
id: 4,
parent: 1,
reportLeafIndex: 3,
},
],
id: 1,
parent: undefined,
reportLeafIndex: undefined,
};
function run_test() {
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
}

View file

@ -0,0 +1,136 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests CensusTreeNode with `coarseType` breakdown.
*/
const countBreakdown = { by: "count", count: true, bytes: true };
const BREAKDOWN = {
by: "coarseType",
objects: { by: "objectClass", then: countBreakdown },
strings: countBreakdown,
scripts: countBreakdown,
other: { by: "internalType", then: countBreakdown },
};
const REPORT = {
"objects": {
"Function": { bytes: 10, count: 1 },
"Array": { bytes: 20, count: 2 },
},
"strings": { bytes: 10, count: 1 },
"scripts": { bytes: 1, count: 1 },
"other": {
"js::Shape": { bytes: 30, count: 3 },
"js::Shape2": { bytes: 40, count: 4 }
},
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 111,
count: 0,
totalCount: 12,
children: [
{
name: "other",
count: 0,
totalCount: 7,
bytes: 0,
totalBytes: 70,
children: [
{
name: "js::Shape2",
bytes: 40,
totalBytes: 40,
count: 4,
totalCount: 4,
children: undefined,
id: 9,
parent: 7,
reportLeafIndex: 8,
},
{
name: "js::Shape",
bytes: 30,
totalBytes: 30,
count: 3,
totalCount: 3,
children: undefined,
id: 8,
parent: 7,
reportLeafIndex: 7,
},
],
id: 7,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "objects",
count: 0,
totalCount: 3,
bytes: 0,
totalBytes: 30,
children: [
{
name: "Array",
bytes: 20,
totalBytes: 20,
count: 2,
totalCount: 2,
children: undefined,
id: 4,
parent: 2,
reportLeafIndex: 3,
},
{
name: "Function",
bytes: 10,
totalBytes: 10,
count: 1,
totalCount: 1,
children: undefined,
id: 3,
parent: 2,
reportLeafIndex: 2,
},
],
id: 2,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "strings",
count: 1,
totalCount: 1,
bytes: 10,
totalBytes: 10,
children: undefined,
id: 6,
parent: 1,
reportLeafIndex: 5,
},
{
name: "scripts",
count: 1,
totalCount: 1,
bytes: 1,
totalBytes: 1,
children: undefined,
id: 5,
parent: 1,
reportLeafIndex: 4,
},
],
id: 1,
parent: undefined,
reportLeafIndex: undefined,
};
function run_test() {
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
}

View file

@ -0,0 +1,96 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests CensusTreeNode with `objectClass` breakdown.
*/
const countBreakdown = { by: "count", count: true, bytes: true };
const BREAKDOWN = {
by: "objectClass",
then: countBreakdown,
other: { by: "internalType", then: countBreakdown }
};
const REPORT = {
"Function": { bytes: 10, count: 10 },
"Array": { bytes: 100, count: 1 },
"other": {
"JIT::CODE::NOW!!!": { bytes: 20, count: 2 },
"JIT::CODE::LATER!!!": { bytes: 40, count: 4 }
}
};
const EXPECTED = {
name: null,
count: 0,
totalCount: 17,
bytes: 0,
totalBytes: 170,
children: [
{
name: "Array",
bytes: 100,
totalBytes: 100,
count: 1,
totalCount: 1,
children: undefined,
id: 3,
parent: 1,
reportLeafIndex: 2,
},
{
name: "other",
count: 0,
totalCount: 6,
bytes: 0,
totalBytes: 60,
children: [
{
name: "JIT::CODE::LATER!!!",
bytes: 40,
totalBytes: 40,
count: 4,
totalCount: 4,
children: undefined,
id: 6,
parent: 4,
reportLeafIndex: 5,
},
{
name: "JIT::CODE::NOW!!!",
bytes: 20,
totalBytes: 20,
count: 2,
totalCount: 2,
children: undefined,
id: 5,
parent: 4,
reportLeafIndex: 4,
},
],
id: 4,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "Function",
bytes: 10,
totalBytes: 10,
count: 10,
totalCount: 10,
children: undefined,
id: 2,
parent: 1,
reportLeafIndex: 1,
},
],
id: 1,
parent: undefined,
reportLeafIndex: undefined,
};
function run_test() {
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
}

View file

@ -0,0 +1,159 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests CensusTreeNode with `allocationStack` breakdown.
*/
function run_test() {
const countBreakdown = { by: "count", count: true, bytes: true };
const BREAKDOWN = {
by: "allocationStack",
then: countBreakdown,
noStack: countBreakdown,
};
let stack1, stack2, stack3, stack4, stack5;
(function a() {
(function b() {
(function c() {
stack1 = saveStack(3);
}());
(function d() {
stack2 = saveStack(3);
stack3 = saveStack(3);
}());
stack4 = saveStack(2);
}());
}());
stack5 = saveStack(1);
const REPORT = new Map([
[stack1, { bytes: 10, count: 1 }],
[stack2, { bytes: 20, count: 2 }],
[stack3, { bytes: 30, count: 3 }],
[stack4, { bytes: 40, count: 4 }],
[stack5, { bytes: 50, count: 5 }],
["noStack", { bytes: 60, count: 6 }],
]);
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 210,
count: 0,
totalCount: 21,
children: [
{
name: stack4.parent,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: [
{
name: stack3.parent,
bytes: 0,
totalBytes: 50,
count: 0,
totalCount: 5,
children: [
{
name: stack3,
bytes: 30,
totalBytes: 30,
count: 3,
totalCount: 3,
children: undefined,
id: 7,
parent: 5,
reportLeafIndex: 3,
},
{
name: stack2,
bytes: 20,
totalBytes: 20,
count: 2,
totalCount: 2,
children: undefined,
id: 6,
parent: 5,
reportLeafIndex: 2,
}
],
id: 5,
parent: 2,
reportLeafIndex: undefined,
},
{
name: stack4,
bytes: 40,
totalBytes: 40,
count: 4,
totalCount: 4,
children: undefined,
id: 8,
parent: 2,
reportLeafIndex: 4,
},
{
name: stack1.parent,
bytes: 0,
totalBytes: 10,
count: 0,
totalCount: 1,
children: [
{
name: stack1,
bytes: 10,
totalBytes: 10,
count: 1,
totalCount: 1,
children: undefined,
id: 4,
parent: 3,
reportLeafIndex: 1,
},
],
id: 3,
parent: 2,
reportLeafIndex: undefined,
},
],
id: 2,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "noStack",
bytes: 60,
totalBytes: 60,
count: 6,
totalCount: 6,
children: undefined,
id: 10,
parent: 1,
reportLeafIndex: 6,
},
{
name: stack5,
bytes: 50,
totalBytes: 50,
count: 5,
totalCount: 5,
children: undefined,
id: 9,
parent: 1,
reportLeafIndex: 5
},
],
id: 1,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
}

View file

@ -0,0 +1,145 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests CensusTreeNode with `allocationStack` => `objectClass` breakdown.
*/
function run_test() {
const countBreakdown = { by: "count", count: true, bytes: true };
const BREAKDOWN = {
by: "allocationStack",
then: {
by: "objectClass",
then: countBreakdown,
other: countBreakdown
},
noStack: countBreakdown,
};
let stack;
(function a() {
(function b() {
(function c() {
stack = saveStack(3);
}());
}());
}());
const REPORT = new Map([
[stack, { Foo: { bytes: 10, count: 1 },
Bar: { bytes: 20, count: 2 },
Baz: { bytes: 30, count: 3 },
other: { bytes: 40, count: 4 }
}],
["noStack", { bytes: 50, count: 5 }],
]);
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 150,
count: 0,
totalCount: 15,
children: [
{
name: stack.parent.parent,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: [
{
name: stack.parent,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: [
{
name: stack,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: [
{
name: "other",
bytes: 40,
totalBytes: 40,
count: 4,
totalCount: 4,
children: undefined,
id: 8,
parent: 4,
reportLeafIndex: 5,
},
{
name: "Baz",
bytes: 30,
totalBytes: 30,
count: 3,
totalCount: 3,
children: undefined,
id: 7,
parent: 4,
reportLeafIndex: 4,
},
{
name: "Bar",
bytes: 20,
totalBytes: 20,
count: 2,
totalCount: 2,
children: undefined,
id: 6,
parent: 4,
reportLeafIndex: 3,
},
{
name: "Foo",
bytes: 10,
totalBytes: 10,
count: 1,
totalCount: 1,
children: undefined,
id: 5,
parent: 4,
reportLeafIndex: 2,
},
],
id: 4,
parent: 3,
reportLeafIndex: undefined,
}
],
id: 3,
parent: 2,
reportLeafIndex: undefined,
}
],
id: 2,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "noStack",
bytes: 50,
totalBytes: 50,
count: 5,
totalCount: 5,
children: undefined,
id: 9,
parent: 1,
reportLeafIndex: 6,
},
],
id: 1,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
}

View file

@ -0,0 +1,200 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test inverting CensusTreeNode with a by alloaction stack breakdown.
*/
function run_test() {
const BREAKDOWN = {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true },
};
let stack1, stack2, stack3, stack4;
function a(n) {
return b(n);
}
function b(n) {
return c(n);
}
function c(n) {
return saveStack(n);
}
function d(n) {
return b(n);
}
function e(n) {
return c(n);
}
const abc_Stack = a(3);
const bc_Stack = b(2);
const c_Stack = c(1);
const dbc_Stack = d(3);
const ec_Stack = e(2);
const REPORT = new Map([
[abc_Stack, { bytes: 10, count: 1 }],
[ bc_Stack, { bytes: 10, count: 1 }],
[ c_Stack, { bytes: 10, count: 1 }],
[dbc_Stack, { bytes: 10, count: 1 }],
[ ec_Stack, { bytes: 10, count: 1 }],
["noStack", { bytes: 50, count: 5 }],
]);
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: [
{
name: "noStack",
bytes: 50,
totalBytes: 50,
count: 5,
totalCount: 5,
children: [
{
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: undefined,
id: 16,
parent: 15,
reportLeafIndex: undefined,
}
],
id: 15,
parent: 14,
reportLeafIndex: 6,
},
{
name: abc_Stack,
bytes: 50,
totalBytes: 10,
count: 5,
totalCount: 1,
children: [
{
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: undefined,
id: 18,
parent: 17,
reportLeafIndex: undefined,
},
{
name: abc_Stack.parent,
bytes: 0,
totalBytes: 10,
count: 0,
totalCount: 1,
children: [
{
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: undefined,
id: 22,
parent: 19,
reportLeafIndex: undefined,
},
{
name: abc_Stack.parent.parent,
bytes: 0,
totalBytes: 10,
count: 0,
totalCount: 1,
children: [
{
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: undefined,
id: 21,
parent: 20,
reportLeafIndex: undefined,
}
],
id: 20,
parent: 19,
reportLeafIndex: undefined,
},
{
name: dbc_Stack.parent.parent,
bytes: 0,
totalBytes: 10,
count: 0,
totalCount: 1,
children: [
{
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: undefined,
id: 24,
parent: 23,
reportLeafIndex: undefined,
}
],
id: 23,
parent: 19,
reportLeafIndex: undefined,
}
],
id: 19,
parent: 17,
reportLeafIndex: undefined,
},
{
name: ec_Stack.parent,
bytes: 0,
totalBytes: 10,
count: 0,
totalCount: 1,
children: [
{
name: null,
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: undefined,
id: 26,
parent: 25,
reportLeafIndex: undefined,
},
],
id: 25,
parent: 17,
reportLeafIndex: undefined,
},
],
id: 17,
parent: 14,
reportLeafIndex: new Set([1, 2, 3, 4, 5]),
}
],
id: 14,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
}

View file

@ -0,0 +1,200 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test inverting CensusTreeNode with a non-allocation stack breakdown.
*/
function run_test() {
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other:{
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const REPORT = {
objects: {
Array: { bytes: 50, count: 5 },
other: { bytes: 0, count: 0 },
},
scripts: {
"js::jit::JitScript": { bytes: 30, count: 3 },
},
strings: {
JSAtom: { bytes: 60, count: 6 },
},
other: {
"js::Shape": { bytes: 80, count: 8 },
}
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 220,
count: 0,
totalCount: 22,
children: [
{
name: "js::Shape",
bytes: 80,
totalBytes: 80,
count: 8,
totalCount: 8,
children: [
{
name: "other",
bytes: 0,
totalBytes: 80,
count: 0,
totalCount: 8,
children: [
{
name: null,
bytes: 0,
totalBytes: 220,
count: 0,
totalCount: 22,
children: undefined,
id: 14,
parent: 13,
reportLeafIndex: undefined,
}
],
id: 13,
parent: 12,
reportLeafIndex: undefined,
}
],
id: 12,
parent: 11,
reportLeafIndex: 9,
},
{
name: "JSAtom",
bytes: 60,
totalBytes: 60,
count: 6,
totalCount: 6,
children: [
{
name: "strings",
bytes: 0,
totalBytes: 60,
count: 0,
totalCount: 6,
children: [
{
name: null,
bytes: 0,
totalBytes: 220,
count: 0,
totalCount: 22,
children: undefined,
id: 17,
parent: 16,
reportLeafIndex: undefined,
}
],
id: 16,
parent: 15,
reportLeafIndex: undefined,
}
],
id: 15,
parent: 11,
reportLeafIndex: 7,
},
{
name: "Array",
bytes: 50,
totalBytes: 50,
count: 5,
totalCount: 5,
children: [
{
name: "objects",
bytes: 0,
totalBytes: 50,
count: 0,
totalCount: 5,
children: [
{
name: null,
bytes: 0,
totalBytes: 220,
count: 0,
totalCount: 22,
children: undefined,
id: 20,
parent: 19,
reportLeafIndex: undefined,
}
],
id: 19,
parent: 18,
reportLeafIndex: undefined,
}
],
id: 18,
parent: 11,
reportLeafIndex: 2,
},
{
name: "js::jit::JitScript",
bytes: 30,
totalBytes: 30,
count: 3,
totalCount: 3,
children: [
{
name: "scripts",
bytes: 0,
totalBytes: 30,
count: 0,
totalCount: 3,
children: [
{
name: null,
bytes: 0,
totalBytes: 220,
count: 0,
totalCount: 22,
children: undefined,
id: 23,
parent: 22,
reportLeafIndex: undefined,
}
],
id: 22,
parent: 21,
reportLeafIndex: undefined,
}
],
id: 21,
parent: 11,
reportLeafIndex: 5,
},
],
id: 11,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
}

View file

@ -0,0 +1,142 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test inverting CensusTreeNode with a non-allocation stack breakdown.
*/
function run_test() {
const BREAKDOWN = {
by: "filename",
then: {
by: "internalType",
then: { by: "count", count: true, bytes: true }
},
noFilename: {
by: "internalType",
then: { by: "count", count: true, bytes: true }
},
};
const REPORT = {
"http://example.com/app.js": {
JSScript: { count: 10, bytes: 100 }
},
"http://example.com/ads.js": {
"js::LazyScript": { count: 20, bytes: 200 }
},
"http://example.com/trackers.js": {
JSScript: { count: 30, bytes: 300 }
},
noFilename: {
"js::jit::JitCode": { count: 40, bytes: 400 }
}
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 1000,
count: 0,
totalCount: 100,
children: [
{
name: "noFilename",
bytes: 0,
totalBytes: 400,
count: 0,
totalCount: 40,
children: [
{
name: "js::jit::JitCode",
bytes: 400,
totalBytes: 400,
count: 40,
totalCount: 40,
children: undefined,
id: 9,
parent: 8,
reportLeafIndex: 8,
}
],
id: 8,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "http://example.com/trackers.js",
bytes: 0,
totalBytes: 300,
count: 0,
totalCount: 30,
children: [
{
name: "JSScript",
bytes: 300,
totalBytes: 300,
count: 30,
totalCount: 30,
children: undefined,
id: 7,
parent: 6,
reportLeafIndex: 6,
}
],
id: 6,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "http://example.com/ads.js",
bytes: 0,
totalBytes: 200,
count: 0,
totalCount: 20,
children: [
{
name: "js::LazyScript",
bytes: 200,
totalBytes: 200,
count: 20,
totalCount: 20,
children: undefined,
id: 5,
parent: 4,
reportLeafIndex: 4,
}
],
id: 4,
parent: 1,
reportLeafIndex: undefined,
},
{
name: "http://example.com/app.js",
bytes: 0,
totalBytes: 100,
count: 0,
totalCount: 10,
children: [
{
name: "JSScript",
bytes: 100,
totalBytes: 100,
count: 10,
totalCount: 10,
children: undefined,
id: 3,
parent: 2,
reportLeafIndex: 2,
}
],
id: 2,
parent: 1,
reportLeafIndex: undefined,
}
],
id: 1,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
}

View file

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test that repeatedly converting the same census report to a CensusTreeNode
* tree results in the same CensusTreeNode tree.
*/
function run_test() {
const BREAKDOWN = {
by: "filename",
then: {
by: "internalType",
then: { by: "count", count: true, bytes: true }
},
noFilename: {
by: "internalType",
then: { by: "count", count: true, bytes: true }
},
};
const REPORT = {
"http://example.com/app.js": {
JSScript: { count: 10, bytes: 100 }
},
"http://example.com/ads.js": {
"js::LazyScript": { count: 20, bytes: 200 }
},
"http://example.com/trackers.js": {
JSScript: { count: 30, bytes: 300 }
},
noFilename: {
"js::jit::JitCode": { count: 40, bytes: 400 }
}
};
const first = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
const second = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
const third = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
assertStructurallyEquivalent(first, second);
assertStructurallyEquivalent(second, third);
}

View file

@ -0,0 +1,43 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test when multiple leaves in the census report map to the same node in an
* inverted CensusReportTree.
*/
function run_test() {
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
strings: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
};
const REPORT = {
objects: {
Array: { count: 1, bytes: 10 },
},
other: {
Array: { count: 1, bytes: 10 },
},
strings: { count: 0, bytes: 0 },
scripts: { count: 0, bytes: 0 },
};
const node = censusReportToCensusTreeNode(BREAKDOWN, REPORT, { invert: true });
equal(node.children[0].name, "Array");
equal(node.children[0].reportLeafIndex.size, 2);
dumpn(`node.children[0].reportLeafIndex = ${[...node.children[0].reportLeafIndex]}`);
ok(node.children[0].reportLeafIndex.has(2));
ok(node.children[0].reportLeafIndex.has(6));
}

View file

@ -0,0 +1,74 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "internalType".
const BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
const REPORT1 = {
"JSObject": {
"count": 10,
"bytes": 100,
},
"js::Shape": {
"count": 50,
"bytes": 500,
},
"JSString": {
"count": 0,
"bytes": 0,
},
"js::LazyScript": {
"count": 1,
"bytes": 10,
},
};
const REPORT2 = {
"JSObject": {
"count": 11,
"bytes": 110,
},
"js::Shape": {
"count": 51,
"bytes": 510,
},
"JSString": {
"count": 1,
"bytes": 1,
},
"js::BaseShape": {
"count": 1,
"bytes": 42,
},
};
const EXPECTED = {
"JSObject": {
"count": 1,
"bytes": 10,
},
"js::Shape": {
"count": 1,
"bytes": 10,
},
"JSString": {
"count": 1,
"bytes": 1,
},
"js::LazyScript": {
"count": -1,
"bytes": -10,
},
"js::BaseShape": {
"count": 1,
"bytes": 42,
},
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View file

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "count".
const BREAKDOWN = { by: "count", count: true, bytes: true };
const REPORT1 = {
"count": 10,
"bytes": 100,
};
const REPORT2 = {
"count": 11,
"bytes": 110,
};
const EXPECTED = {
"count": 1,
"bytes": 10,
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View file

@ -0,0 +1,73 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "coarseType".
const BREAKDOWN = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
const REPORT1 = {
objects: {
count: 1,
bytes: 10,
},
scripts: {
count: 1,
bytes: 10,
},
strings: {
count: 1,
bytes: 10,
},
other: {
count: 3,
bytes: 30,
},
};
const REPORT2 = {
objects: {
count: 1,
bytes: 10,
},
scripts: {
count: 0,
bytes: 0,
},
strings: {
count: 2,
bytes: 20,
},
other: {
count: 4,
bytes: 40,
},
};
const EXPECTED = {
objects: {
count: 0,
bytes: 0,
},
scripts: {
count: -1,
bytes: -10,
},
strings: {
count: 1,
bytes: 10,
},
other: {
count: 1,
bytes: 10,
},
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View file

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "objectClass".
const BREAKDOWN = {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
const REPORT1 = {
"Array": {
count: 1,
bytes: 100,
},
"Function": {
count: 10,
bytes: 10,
},
"other": {
count: 10,
bytes: 100,
}
};
const REPORT2 = {
"Object": {
count: 1,
bytes: 100,
},
"Function": {
count: 20,
bytes: 20,
},
"other": {
count: 10,
bytes: 100,
}
};
const EXPECTED = {
"Array": {
count: -1,
bytes: -100,
},
"Function": {
count: 10,
bytes: 10,
},
"other": {
count: 0,
bytes: 0,
},
"Object": {
count: 1,
bytes: 100,
},
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View file

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "allocationStack".
const BREAKDOWN = {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true },
};
const stack1 = saveStack();
const stack2 = saveStack();
const stack3 = saveStack();
const REPORT1 = new Map([
[stack1, { "count": 10, "bytes": 100 }],
[stack2, { "count": 1, "bytes": 10 }],
]);
const REPORT2 = new Map([
[stack2, { "count": 10, "bytes": 100 }],
[stack3, { "count": 1, "bytes": 10 }],
]);
const EXPECTED = new Map([
[stack1, { "count": -10, "bytes": -100 }],
[stack2, { "count": 9, "bytes": 90 }],
[stack3, { "count": 1, "bytes": 10 }],
]);
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View file

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of a "complex" and "realistic" breakdown.
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "allocationStack",
then: {
by: "objectClass",
then: { by: "count", count: false, bytes: true },
other: { by: "count", count: false, bytes: true }
},
noStack: {
by: "objectClass",
then: { by: "count", count: false, bytes: true },
other: { by: "count", count: false, bytes: true }
}
},
strings: {
by: "internalType",
then: { by: "count", count: false, bytes: true }
},
scripts: {
by: "internalType",
then: { by: "count", count: false, bytes: true }
},
other: {
by: "internalType",
then: { by: "count", count: false, bytes: true }
},
};
const stack1 = saveStack();
const stack2 = saveStack();
const stack3 = saveStack();
const REPORT1 = {
objects: new Map([
[stack1, { Function: { bytes: 1 },
Object: { bytes: 2 },
other: { bytes: 0 },
}],
[stack2, { Array: { bytes: 3 },
Date: { bytes: 4 },
other: { bytes: 0 },
}],
["noStack", { Object: { bytes: 3 }}],
]),
strings: {
JSAtom: { bytes: 10 },
JSLinearString: { bytes: 5 },
},
scripts: {
JSScript: { bytes: 1 },
"js::jit::JitCode": { bytes: 2 },
},
other: {
"mozilla::dom::Thing": { bytes: 1 },
}
};
const REPORT2 = {
objects: new Map([
[stack2, { Array: { bytes: 1 },
Date: { bytes: 2 },
other: { bytes: 3 },
}],
[stack3, { Function: { bytes: 1 },
Object: { bytes: 2 },
other: { bytes: 0 },
}],
["noStack", { Object: { bytes: 3 }}],
]),
strings: {
JSAtom: { bytes: 5 },
JSLinearString: { bytes: 10 },
},
scripts: {
JSScript: { bytes: 2 },
"js::LazyScript": { bytes: 42 },
"js::jit::JitCode": { bytes: 1 },
},
other: {
"mozilla::dom::OtherThing": { bytes: 1 },
}
};
const EXPECTED = {
"objects": new Map([
[stack1, { Function: { bytes: -1 },
Object: { bytes: -2 },
other: { bytes: 0 },
}],
[stack2, { Array: { bytes: -2 },
Date: { bytes: -2 },
other: { bytes: 3 },
}],
[stack3, { Function: { bytes: 1 },
Object: { bytes: 2 },
other: { bytes: 0 },
}],
["noStack", { Object: { bytes: 0 }}],
]),
"scripts": {
"JSScript": {
"bytes": 1
},
"js::jit::JitCode": {
"bytes": -1
},
"js::LazyScript": {
"bytes": 42
}
},
"strings": {
"JSAtom": {
"bytes": -5
},
"JSLinearString": {
"bytes": 5
}
},
"other": {
"mozilla::dom::Thing": {
"bytes": -1
},
"mozilla::dom::OtherThing": {
"bytes": 1
}
}
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View file

@ -0,0 +1,105 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test filtering basic CensusTreeNode trees.
function run_test() {
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other:{
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const REPORT = {
objects: {
Array: { bytes: 50, count: 5 },
UInt8Array: { bytes: 80, count: 8 },
Int32Array: { bytes: 320, count: 32 },
other: { bytes: 0, count: 0 },
},
scripts: {
"js::jit::JitScript": { bytes: 30, count: 3 },
},
strings: {
JSAtom: { bytes: 60, count: 6 },
},
other: {
"js::Shape": { bytes: 80, count: 8 },
}
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 620,
count: 0,
totalCount: 62,
children: [
{
name: "objects",
bytes: 0,
totalBytes: 450,
count: 0,
totalCount: 45,
children: [
{
name: "Int32Array",
bytes: 320,
totalBytes: 320,
count: 32,
totalCount: 32,
children: undefined,
id: 15,
parent: 14,
reportLeafIndex: 4,
},
{
name: "UInt8Array",
bytes: 80,
totalBytes: 80,
count: 8,
totalCount: 8,
children: undefined,
id: 16,
parent: 14,
reportLeafIndex: 3,
},
{
name: "Array",
bytes: 50,
totalBytes: 50,
count: 5,
totalCount: 5,
children: undefined,
id: 17,
parent: 14,
reportLeafIndex: 2,
}
],
id: 14,
parent: 13,
reportLeafIndex: undefined,
}
],
id: 13,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "Array" });
}

View file

@ -0,0 +1,124 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test filtering CensusTreeNode trees with an `allocationStack` breakdown.
function run_test() {
const countBreakdown = { by: "count", count: true, bytes: true };
const BREAKDOWN = {
by: "allocationStack",
then: countBreakdown,
noStack: countBreakdown,
};
let stack1, stack2, stack3, stack4, stack5;
(function foo() {
(function bar() {
(function baz() {
stack1 = saveStack(3);
}());
(function quux() {
stack2 = saveStack(3);
stack3 = saveStack(3);
}());
}());
stack4 = saveStack(2);
}());
stack5 = saveStack(1);
const REPORT = new Map([
[stack1, { bytes: 10, count: 1 }],
[stack2, { bytes: 20, count: 2 }],
[stack3, { bytes: 30, count: 3 }],
[stack4, { bytes: 40, count: 4 }],
[stack5, { bytes: 50, count: 5 }],
["noStack", { bytes: 60, count: 6 }],
]);
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 210,
count: 0,
totalCount: 21,
children: [
{
name: stack1.parent.parent,
bytes: 0,
totalBytes: 60,
count: 0,
totalCount: 6,
children: [
{
name: stack2.parent,
bytes: 0,
totalBytes: 50,
count: 0,
totalCount: 5,
children: [
{
name: stack3,
bytes: 30,
totalBytes: 30,
count: 3,
totalCount: 3,
children: undefined,
id: 15,
parent: 14,
reportLeafIndex: 3,
},
{
name: stack2,
bytes: 20,
totalBytes: 20,
count: 2,
totalCount: 2,
children: undefined,
id: 16,
parent: 14,
reportLeafIndex: 2,
}
],
id: 14,
parent: 13,
reportLeafIndex: undefined,
},
{
name: stack1.parent,
bytes: 0,
totalBytes: 10,
count: 0,
totalCount: 1,
children: [
{
name: stack1,
bytes: 10,
totalBytes: 10,
count: 1,
totalCount: 1,
children: undefined,
id: 18,
parent: 17,
reportLeafIndex: 1,
}
],
id: 17,
parent: 13,
reportLeafIndex: undefined,
}
],
id: 13,
parent: 12,
reportLeafIndex: undefined,
}
],
id: 12,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "bar" });
}

View file

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test filtering with no matches.
function run_test() {
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
scripts: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
strings: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
other:{
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const REPORT = {
objects: {
Array: { bytes: 50, count: 5 },
UInt8Array: { bytes: 80, count: 8 },
Int32Array: { bytes: 320, count: 32 },
other: { bytes: 0, count: 0 },
},
scripts: {
"js::jit::JitScript": { bytes: 30, count: 3 },
},
strings: {
JSAtom: { bytes: 60, count: 6 },
},
other: {
"js::Shape": { bytes: 80, count: 8 },
}
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 620,
count: 0,
totalCount: 62,
children: undefined,
id: 13,
parent: undefined,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "zzzzzzzzzzzzzzzzzzzz" });
}

View file

@ -0,0 +1,102 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the filtered nodes' counts and bytes are the same as they were when
// unfiltered.
function run_test() {
const COUNT = { by: "count", count: true, bytes: true };
const INTERNAL_TYPE = { by: "internalType", then: COUNT };
const BREAKDOWN = {
by: "coarseType",
objects: { by: "objectClass", then: COUNT, other: COUNT },
strings: COUNT,
scripts: {
by: "filename",
then: INTERNAL_TYPE,
noFilename: INTERNAL_TYPE
},
other: INTERNAL_TYPE,
};
const REPORT = {
objects: {
Function: {
count: 7,
bytes: 70
},
Array: {
count: 6,
bytes: 60
}
},
scripts: {
"http://mozilla.github.io/pdf.js/build/pdf.js": {
"js::LazyScript": {
count: 4,
bytes: 40
},
}
},
strings: {
count: 2,
bytes: 20
},
other: {
"js::Shape": {
count: 1,
bytes: 10
}
}
};
const EXPECTED = {
name: null,
bytes: 0,
totalBytes: 200,
count: 0,
totalCount: 20,
parent: undefined,
children: [
{
name: "objects",
bytes: 0,
totalBytes: 130,
count: 0,
totalCount: 13,
children: [
{
name: "Function",
bytes: 70,
totalBytes: 70,
count: 7,
totalCount: 7,
id: 13,
parent: 12,
children: undefined,
reportLeafIndex: 2,
},
{
name: "Array",
bytes: 60,
totalBytes: 60,
count: 6,
totalCount: 6,
id: 14,
parent: 12,
children: undefined,
reportLeafIndex: 3,
},
],
id: 12,
parent: 11,
reportLeafIndex: undefined,
}
],
id: 11,
reportLeafIndex: undefined,
};
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "objects" });
}

View file

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that filtered and inverted allocation stack census trees are sorted
// properly.
function run_test() {
const countBreakdown = { by: "count", count: true, bytes: true };
const BREAKDOWN = {
by: "allocationStack",
then: countBreakdown,
noStack: countBreakdown,
};
const stacks = [];
function foo(depth = 1) {
stacks.push(saveStack(depth));
bar(depth + 1);
baz(depth + 1);
stacks.push(saveStack(depth));
}
function bar(depth = 1) {
stacks.push(saveStack(depth));
stacks.push(saveStack(depth));
}
function baz(depth = 1) {
stacks.push(saveStack(depth));
bang(depth + 1);
stacks.push(saveStack(depth));
}
function bang(depth = 1) {
stacks.push(saveStack(depth));
stacks.push(saveStack(depth));
stacks.push(saveStack(depth));
}
foo();
bar();
baz();
bang();
const REPORT = new Map(stacks.map((s, i) => {
return [s, {
count: i + 1,
bytes: (i + 1) * 10
}];
}));
const tree = censusReportToCensusTreeNode(BREAKDOWN, REPORT, {
filter: "baz",
invert: true
});
dumpn("tree = " + JSON.stringify(tree, savedFrameReplacer, 4));
(function assertSortedBySelf(node) {
if (node.children) {
let lastSelfBytes = Infinity;
for (let child of node.children) {
ok(child.bytes <= lastSelfBytes, `${child.bytes} <= ${lastSelfBytes}`);
lastSelfBytes = child.bytes;
assertSortedBySelf(child);
}
}
}(tree));
}

View file

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that we can turn a breakdown with { by: "count" } leaves into a
// breakdown with { by: "bucket" } leaves.
const COUNT = { by: "count", count: true, bytes: true };
const BUCKET = { by: "bucket" };
const BREAKDOWN = {
by: "coarseType",
objects: { by: "objectClass", then: COUNT, other: COUNT },
strings: COUNT,
scripts: {
by: "filename",
then: { by: "internalType", then: COUNT },
noFilename: { by: "internalType", then: COUNT },
},
other: { by: "internalType", then: COUNT },
};
const EXPECTED = {
by: "coarseType",
objects: { by: "objectClass", then: BUCKET, other: BUCKET },
strings: BUCKET,
scripts: {
by: "filename",
then: { by: "internalType", then: BUCKET },
noFilename: { by: "internalType", then: BUCKET },
},
other: { by: "internalType", then: BUCKET },
};
function run_test() {
assertCountToBucketBreakdown(BREAKDOWN, EXPECTED);
}

View file

@ -0,0 +1,113 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test the behavior of the deduplicatePaths utility function.
function edge(from, to, name) {
return { from, to, name };
}
function run_test() {
const a = 1;
const b = 2;
const c = 3;
const d = 4;
const e = 5;
const f = 6;
const g = 7;
dumpn("Single long path");
assertDeduplicatedPaths({
target: g,
paths: [
[
pathEntry(a, "e1"),
pathEntry(b, "e2"),
pathEntry(c, "e3"),
pathEntry(d, "e4"),
pathEntry(e, "e5"),
pathEntry(f, "e6"),
]
],
expectedNodes: [a, b, c, d, e, f, g],
expectedEdges: [
edge(a, b, "e1"),
edge(b, c, "e2"),
edge(c, d, "e3"),
edge(d, e, "e4"),
edge(e, f, "e5"),
edge(f, g, "e6"),
]
});
dumpn("Multiple edges from and to the same nodes");
assertDeduplicatedPaths({
target: a,
paths: [
[pathEntry(b, "x")],
[pathEntry(b, "y")],
[pathEntry(b, "z")],
],
expectedNodes: [a, b],
expectedEdges: [
edge(b, a, "x"),
edge(b, a, "y"),
edge(b, a, "z"),
]
});
dumpn("Multiple paths sharing some nodes and edges");
assertDeduplicatedPaths({
target: g,
paths: [
[
pathEntry(a, "a->b"),
pathEntry(b, "b->c"),
pathEntry(c, "foo"),
],
[
pathEntry(a, "a->b"),
pathEntry(b, "b->d"),
pathEntry(d, "bar"),
],
[
pathEntry(a, "a->b"),
pathEntry(b, "b->e"),
pathEntry(e, "baz"),
],
],
expectedNodes: [a, b, c, d, e, g],
expectedEdges: [
edge(a, b, "a->b"),
edge(b, c, "b->c"),
edge(b, d, "b->d"),
edge(b, e, "b->e"),
edge(c, g, "foo"),
edge(d, g, "bar"),
edge(e, g, "baz"),
]
});
dumpn("Second shortest path contains target itself");
assertDeduplicatedPaths({
target: g,
paths: [
[
pathEntry(a, "a->b"),
pathEntry(b, "b->g"),
],
[
pathEntry(a, "a->b"),
pathEntry(b, "b->g"),
pathEntry(g, "g->f"),
pathEntry(f, "f->g"),
],
],
expectedNodes: [a, b, g],
expectedEdges: [
edge(a, b, "a->b"),
edge(b, g, "b->g"),
]
});
}

View file

@ -0,0 +1,60 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test basic functionality of `CensusUtils.getCensusIndividuals`.
function run_test() {
const stack1 = saveStack(1);
const stack2 = saveStack(1);
const stack3 = saveStack(1);
const COUNT = { by: "count", count: true, bytes: true };
const INTERNAL_TYPE = { by: "internalType", then: COUNT };
const BREAKDOWN = {
by: "allocationStack",
then: INTERNAL_TYPE,
noStack: INTERNAL_TYPE,
};
const MOCK_SNAPSHOT = {
takeCensus: ({ breakdown }) => {
assertStructurallyEquivalent(
breakdown,
CensusUtils.countToBucketBreakdown(BREAKDOWN));
// DFS Index
return new Map([ // 0
[stack1, { // 1
JSObject: [101, 102, 103], // 2
JSString: [111, 112, 113], // 3
}],
[stack2, { // 4
JSObject: [201, 202, 203], // 5
JSString: [211, 212, 213], // 6
}],
[stack3, { // 7
JSObject: [301, 302, 303], // 8
JSString: [311, 312, 313], // 9
}],
["noStack", { // 10
JSObject: [401, 402, 403], // 11
JSString: [411, 412, 413], // 12
}],
]);
}
};
const INDICES = new Set([3, 5, 9]);
const EXPECTED = new Set([111, 112, 113,
201, 202, 203,
311, 312, 313]);
const actual = new Set(CensusUtils.getCensusIndividuals(INDICES,
BREAKDOWN,
MOCK_SNAPSHOT));
assertStructurallyEquivalent(EXPECTED, actual);
}

View file

@ -0,0 +1,114 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test basic functionality of `CensusUtils.getReportLeaves`.
function run_test() {
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
},
strings: { by: "count", count: true, bytes: true },
scripts: {
by: "filename",
then: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
noFilename: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
},
other: {
by: "internalType",
then: { by: "count", count: true, bytes: true },
},
};
const REPORT = {
objects: {
Array: { count: 6, bytes: 60 },
Function: { count: 1, bytes: 10 },
Object: { count: 1, bytes: 10 },
RegExp: { count: 1, bytes: 10 },
other: { count: 0, bytes: 0 },
},
strings: { count: 1, bytes: 10 },
scripts: {
"foo.js": {
JSScript: { count: 1, bytes: 10 },
"js::jit::IonScript": { count: 1, bytes: 10 },
},
noFilename: {
JSScript: { count: 1, bytes: 10 },
"js::jit::IonScript": { count: 1, bytes: 10 },
},
},
other: {
"js::Shape": { count: 7, bytes: 70 },
"js::BaseShape": { count: 1, bytes: 10 },
},
};
const root = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
dumpn("CensusTreeNode tree = " + JSON.stringify(root, null, 4));
(function assertEveryNodeCanFindItsLeaf(node) {
if (node.reportLeafIndex) {
const [ leaf ] = CensusUtils.getReportLeaves(new Set([node.reportLeafIndex]),
BREAKDOWN,
REPORT);
ok(leaf, "Should be able to find leaf for a node with a reportLeafIndex = " + node.reportLeafIndex);
}
if (node.children) {
for (let child of node.children) {
assertEveryNodeCanFindItsLeaf(child);
}
}
}(root));
// Test finding multiple leaves at a time.
function find(name, node) {
if (node.name === name) {
return node;
}
if (node.children) {
for (let child of node.children) {
const found = find(name, child);
if (found) {
return found;
}
}
}
}
const arrayNode = find("Array", root);
ok(arrayNode);
equal(typeof arrayNode.reportLeafIndex, "number");
const shapeNode = find("js::Shape", root);
ok(shapeNode);
equal(typeof shapeNode.reportLeafIndex, "number");
const indices = new Set([arrayNode.reportLeafIndex, shapeNode.reportLeafIndex]);
const leaves = CensusUtils.getReportLeaves(indices, BREAKDOWN, REPORT);
equal(leaves.length, 2);
// `getReportLeaves` does not guarantee order of the results, so handle both
// cases.
ok(leaves.some(l => l === REPORT.objects.Array));
ok(leaves.some(l => l === REPORT.other["js::Shape"]));
// Test that bad indices do not yield results.
const none = CensusUtils.getReportLeaves(new Set([999999999999]), BREAKDOWN, REPORT);
equal(none.length, 0);
}

View file

@ -0,0 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test saving a heap snapshot in the sandboxed e10s child process.
function run_test() {
run_test_in_child("../unit/test_SaveHeapSnapshot.js");
}

View file

@ -0,0 +1,98 @@
[DEFAULT]
tags = devtools heapsnapshot devtools-memory
head = head_heapsnapshot.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android'
support-files =
Census.jsm
dominator-tree-worker.js
heap-snapshot-worker.js
Match.jsm
[test_census_diff_01.js]
[test_census_diff_02.js]
[test_census_diff_03.js]
[test_census_diff_04.js]
[test_census_diff_05.js]
[test_census_diff_06.js]
[test_census_filtering_01.js]
[test_census_filtering_02.js]
[test_census_filtering_03.js]
[test_census_filtering_04.js]
[test_census_filtering_05.js]
[test_census-tree-node-01.js]
[test_census-tree-node-02.js]
[test_census-tree-node-03.js]
[test_census-tree-node-04.js]
[test_census-tree-node-05.js]
[test_census-tree-node-06.js]
[test_census-tree-node-07.js]
[test_census-tree-node-08.js]
[test_census-tree-node-09.js]
[test_census-tree-node-10.js]
[test_countToBucketBreakdown_01.js]
[test_deduplicatePaths_01.js]
[test_DominatorTree_01.js]
[test_DominatorTree_02.js]
[test_DominatorTree_03.js]
[test_DominatorTree_04.js]
[test_DominatorTree_05.js]
[test_DominatorTree_06.js]
[test_DominatorTreeNode_attachShortestPaths_01.js]
[test_DominatorTreeNode_getNodeByIdAlongPath_01.js]
[test_DominatorTreeNode_insert_01.js]
[test_DominatorTreeNode_insert_02.js]
[test_DominatorTreeNode_insert_03.js]
[test_DominatorTreeNode_LabelAndShallowSize_01.js]
[test_DominatorTreeNode_LabelAndShallowSize_02.js]
[test_DominatorTreeNode_LabelAndShallowSize_03.js]
[test_DominatorTreeNode_LabelAndShallowSize_04.js]
[test_DominatorTreeNode_partialTraversal_01.js]
[test_getCensusIndividuals_01.js]
[test_getReportLeaves_01.js]
[test_HeapAnalyses_computeDominatorTree_01.js]
[test_HeapAnalyses_computeDominatorTree_02.js]
[test_HeapAnalyses_deleteHeapSnapshot_01.js]
[test_HeapAnalyses_deleteHeapSnapshot_02.js]
[test_HeapAnalyses_deleteHeapSnapshot_03.js]
[test_HeapAnalyses_getCensusIndividuals_01.js]
[test_HeapAnalyses_getCreationTime_01.js]
[test_HeapAnalyses_getDominatorTree_01.js]
[test_HeapAnalyses_getDominatorTree_02.js]
[test_HeapAnalyses_getImmediatelyDominated_01.js]
[test_HeapAnalyses_readHeapSnapshot_01.js]
[test_HeapAnalyses_takeCensusDiff_01.js]
[test_HeapAnalyses_takeCensusDiff_02.js]
[test_HeapAnalyses_takeCensus_01.js]
[test_HeapAnalyses_takeCensus_02.js]
[test_HeapAnalyses_takeCensus_03.js]
[test_HeapAnalyses_takeCensus_04.js]
[test_HeapAnalyses_takeCensus_05.js]
[test_HeapAnalyses_takeCensus_06.js]
[test_HeapAnalyses_takeCensus_07.js]
[test_HeapSnapshot_creationTime_01.js]
[test_HeapSnapshot_deepStack_01.js]
[test_HeapSnapshot_describeNode_01.js]
[test_HeapSnapshot_computeShortestPaths_01.js]
[test_HeapSnapshot_computeShortestPaths_02.js]
[test_HeapSnapshot_takeCensus_01.js]
[test_HeapSnapshot_takeCensus_02.js]
[test_HeapSnapshot_takeCensus_03.js]
[test_HeapSnapshot_takeCensus_04.js]
[test_HeapSnapshot_takeCensus_05.js]
[test_HeapSnapshot_takeCensus_06.js]
[test_HeapSnapshot_takeCensus_07.js]
[test_HeapSnapshot_takeCensus_08.js]
[test_HeapSnapshot_takeCensus_09.js]
[test_HeapSnapshot_takeCensus_10.js]
[test_HeapSnapshot_takeCensus_11.js]
[test_HeapSnapshot_takeCensus_12.js]
[test_ReadHeapSnapshot.js]
[test_ReadHeapSnapshot_with_allocations.js]
skip-if = os == 'linux' # Bug 1176173
[test_ReadHeapSnapshot_worker.js]
skip-if = os == 'linux' # Bug 1176173
[test_SaveHeapSnapshot.js]
[test_saveHeapSnapshot_e10s_01.js]