Bug 1214070 - Add support for diffing census reports to HeapAnalysesWorker; r=jsantell

This commit is contained in:
Nick Fitzgerald 2015-10-15 08:23:00 +02:00
parent 1019f0f2cb
commit cc099550eb
13 changed files with 949 additions and 2 deletions

View File

@ -0,0 +1,324 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*** Visitor ****************************************************************/
/**
* A Visitor visits each node and edge of a census report tree as the census
* report is being traversed by `walk`.
*/
function Visitor() { };
exports.Visitor = Visitor;
/**
* The `enter` method is called when a new sub-report is entered in traversal.
*
* @param {Object} breakdown
* The breakdown for the sub-report that is being entered by traversal.
*
* @param {any} edge
* The edge leading to this sub-report. The edge is null if (but not iff!
* eg, null allocation stack edges) we are entering the root report.
*/
Visitor.prototype.enter = function (breakdown, edge) { };
/**
* The `exit` method is called when traversal of a sub-report has finished.
*
* @param {Object} breakdown
* The breakdown for the sub-report whose traversal has finished.
*/
Visitor.prototype.exit = function (breakdown) { };
/**
* The `count` method is called when leaf nodes (reports whose breakdown is
* by: "count") in the report tree are encountered.
*
* @param {Object} breakdown
* The count breakdown for this report.
*
* @param {Object} report
* The report generated by a breakdown by "count".
*
* @param {any|null} edge
* The edge leading to this count report. The edge is null if we are
* entering the root report.
*/
Visitor.prototype.count = function (breakdown, report, edge) { }
/*** getReportEdges *********************************************************/
const EDGES = Object.create(null);
EDGES.count = function (breakdown, report) {
return [];
};
EDGES.internalType = function (breakdown, report) {
return Object.keys(report).map(key => ({
edge: key,
referent: report[key],
breakdown: breakdown.then
}));
};
EDGES.objectClass = function (breakdown, report) {
return Object.keys(report).map(key => ({
edge: key,
referent: report[key],
breakdown: key === "other" ? breakdown.other : breakdown.then
}));
};
EDGES.coarseType = function (breakdown, report) {
return [
{ edge: "objects", referent: report.objects, breakdown: breakdown.objects },
{ edge: "scripts", referent: report.scripts, breakdown: breakdown.scripts },
{ edge: "strings", referent: report.strings, breakdown: breakdown.strings },
{ edge: "other", referent: report.other, breakdown: breakdown.other },
];
};
EDGES.allocationStack = function (breakdown, report) {
const edges = [];
report.forEach((value, key) => {
edges.push({
edge: key,
referent: value,
breakdown: key === "noStack" ? breakdown.noStack : breakdown.then
})
});
return edges;
};
/**
* Get the set of outgoing edges from `report` as specified by the given
* breakdown.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} report
* The census report.
*/
function getReportEdges(breakdown, report) {
return EDGES[breakdown.by](breakdown, report);
}
exports.getReportEdges = getReportEdges;
/*** walk *******************************************************************/
function recursiveWalk(breakdown, edge, report, visitor) {
if (breakdown.by === "count") {
visitor.enter(breakdown, edge);
visitor.count(breakdown, report, edge);
visitor.exit(breakdown, edge);
} else {
visitor.enter(breakdown, edge);
for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) {
recursiveWalk(breakdown, edge, referent, visitor);
}
visitor.exit(breakdown, edge);
}
};
/**
* Walk the given `report` that was generated by taking a census with the
* specified `breakdown`.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} report
* The census report.
*
* @param {Visitor} visitor
* The Visitor instance to call into while traversing.
*/
function walk(breakdown, report, visitor) {
recursiveWalk(breakdown, null, report, visitor);
};
exports.walk = walk;
/*** diff *******************************************************************/
/**
* Return true if the object is a Map, false otherwise. Works with Map objects
* from other globals, unlike `instanceof`.
*
* @returns {Boolean}
*/
function isMap(obj) {
return Object.prototype.toString.call(obj) === "[object Map]";
}
/**
* A Visitor for computing the difference between the census report being
* traversed and the given other census.
*
* @param {Object} otherCensus
* The other census report.
*/
function DiffVisitor(otherCensus) {
// The other census we are comparing against.
this._otherCensus = otherCensus;
// Stack maintaining the current corresponding sub-report for the other
// census we are comparing against.
this._otherCensusStack = [];
// Stack maintaining the set of edges visited at each sub-report.
this._edgesVisited = [new Set()];
// The final delta census. Valid only after traversal.
this._results = null;
// Stack maintaining the results corresponding to each sub-report we are
// currently traversing.
this._resultsStack = [];
}
DiffVisitor.prototype = Object.create(Visitor.prototype);
/**
* Given a report and an outgoing edge, get the edge's referent.
*/
DiffVisitor.prototype._get = function (report, edge) {
if (!report) {
return undefined;
}
return isMap(report) ? report.get(edge) : report[edge];
};
/**
* Given a report, an outgoing edge, and a value, set the edge's referent to
* the given value.
*/
DiffVisitor.prototype._set = function (report, edge, val) {
if (isMap(report)) {
report.set(edge, val);
} else {
report[edge] = val;
}
};
/**
* @overrides Visitor.prototype.enter
*/
DiffVisitor.prototype.enter = function (breakdown, edge) {
const isFirstTimeEntering = this._results === null;
const newResults = breakdown.by === "allocationStack" ? new Map() : {};
let newOther;
if (!this._results) {
// This is the first time we have entered a sub-report.
this._results = newResults;
newOther = this._otherCensus;
} else {
const topResults = this._resultsStack[this._resultsStack.length - 1];
this._set(topResults, edge, newResults);
const topOther = this._otherCensusStack[this._otherCensusStack.length - 1];
newOther = this._get(topOther, edge);
}
this._resultsStack.push(newResults);
this._otherCensusStack.push(newOther);
const visited = this._edgesVisited[this._edgesVisited.length - 1];
visited.add(edge);
this._edgesVisited.push(new Set());
};
/**
* @overrides Visitor.prototype.exit
*/
DiffVisitor.prototype.exit = function (breakdown) {
// Find all the edges in the other census report that were not traversed and
// add them to the results directly.
const other = this._otherCensusStack[this._otherCensusStack.length - 1];
if (other) {
const visited = this._edgesVisited[this._edgesVisited.length - 1];
const unvisited = getReportEdges(breakdown, other)
.map(e => e.edge)
.filter(e => !visited.has(e));
const results = this._resultsStack[this._resultsStack.length - 1];
for (let edge of unvisited) {
this._set(results, edge, this._get(other, edge));
}
}
this._otherCensusStack.pop();
this._resultsStack.pop();
this._edgesVisited.pop();
};
/**
* @overrides Visitor.prototype.count
*/
DiffVisitor.prototype.count = function (breakdown, report, edge) {
const other = this._otherCensusStack[this._otherCensusStack.length - 1];
const results = this._resultsStack[this._resultsStack.length - 1];
if (other) {
if (breakdown.count) {
results.count = other.count - report.count;
}
if (breakdown.bytes) {
results.bytes = other.bytes - report.bytes;
}
} else {
if (breakdown.count) {
results.count = -report.count;
}
if (breakdown.bytes) {
results.bytes = -report.bytes;
}
}
};
/**
* Get the resulting report of the difference between the traversed census
* report and the other census report.
*
* @returns {Object}
* The delta census report.
*/
DiffVisitor.prototype.results = function () {
if (!this._results) {
throw new Error("Attempt to get results before computing diff!");
}
if (this._resultsStack.length) {
throw new Error("Attempt to get results while still computing diff!");
}
return this._results;
};
/**
* Take the difference between two censuses. The resulting delta report
* contains the number/size of things that are in the `endCensus` that are not
* in the `startCensus`.
*
* @param {Object} breakdown
* The breakdown used to generate both census reports.
*
* @param {Object} startCensus
* The first census report.
*
* @param {Object} endCensus
* The second census report.
*
* @returns {Object}
* A delta report mirroring the structure of the two census reports
* (as specified by the given breakdown).
*/
function diff(breakdown, startCensus, endCensus) {
const visitor = new DiffVisitor(endCensus);
walk(breakdown, startCensus, visitor);
return visitor.results();
};
exports.diff = diff

View File

@ -76,7 +76,7 @@ HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
* if `asTreeNode` is true.
*/
HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
censusOptions={},
censusOptions,
requestOptions={}) {
return this._worker.performTask("takeCensus", {
snapshotFilePath,
@ -84,3 +84,42 @@ HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
requestOptions,
});
};
/**
* Request that the worker take a census on the heap snapshots with the given
* paths and then return the difference between them. Both heap snapshots must
* have already been read into memory by the worker (see `readHeapSnapshot`).
*
* @param {String} firstSnapshotFilePath
* The first snapshot file path.
*
* @param {String} secondSnapshotFilePath
* The second snapshot file path.
*
* @param {Object} censusOptions
* A structured-cloneable object specifying the requested census's
* breakdown. See the "takeCensus" section of
* `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
*
* @param {Object} requestOptions
* An object specifying options for this request.
* - {Boolean} asTreeNode
* Whether the resulting delta report should be converted to a census
* tree node before returned. Defaults to false.
*
* @returns Promise<delta report|CensusTreeNode>
* The delta report generated by diffing the two census reports, or a
* CensusTreeNode generated from the delta report if
* `requestOptions.asTreeNode` was true.
*/
HeapAnalysesClient.prototype.takeCensusDiff = function (firstSnapshotFilePath,
secondSnapshotFilePath,
censusOptions,
requestOptions = {}) {
return this._worker.performTask("takeCensusDiff", {
firstSnapshotFilePath,
secondSnapshotFilePath,
censusOptions,
requestOptions
});
}

View File

@ -13,6 +13,7 @@
importScripts("resource://gre/modules/workers/require.js");
importScripts("resource://gre/modules/devtools/shared/worker/helper.js");
const { CensusTreeNode } = require("resource://gre/modules/devtools/shared/heapsnapshot/census-tree-node.js");
const CensusUtils = require("resource://gre/modules/devtools/shared/heapsnapshot/CensusUtils.js");
// The set of HeapSnapshot instances this worker has read into memory. Keyed by
// snapshot file path.
@ -36,5 +37,35 @@ workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions,
}
let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
return requestOptions.asTreeNode ? new CensusTreeNode(censusOptions.breakdown, report) : report;
return requestOptions.asTreeNode
? new CensusTreeNode(censusOptions.breakdown, report)
: report;
});
/**
* @see HeapAnalysesClient.prototype.takeCensusDiff
*/
workerHelper.createTask(self, "takeCensusDiff", request => {
const {
firstSnapshotFilePath,
secondSnapshotFilePath,
censusOptions,
requestOptions
} = request;
if (!snapshots[firstSnapshotFilePath]) {
throw new Error(`No known heap snapshot for '${firstSnapshotFilePath}'`);
}
if (!snapshots[secondSnapshotFilePath]) {
throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
}
const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
const delta = CensusUtils.diff(censusOptions.breakdown, first, second);
return requestOptions.asTreeNode
? new CensusTreeNode(censusOptions.breakdown, delta)
: delta;
});

View File

@ -45,6 +45,7 @@ FINAL_LIBRARY = 'xul'
DevToolsModules(
'census-tree-node.js',
'CensusUtils.js',
'HeapAnalysesClient.js',
'HeapAnalysesWorker.js',
'HeapSnapshotFileUtils.js',

View File

@ -21,6 +21,7 @@ const HeapAnalysesClient =
require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const Services = require("Services");
const { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
// Always log packets when running tests. runxpcshelltests.py will throw
// the output away anyway, unless you give it the --verbose flag.
@ -149,7 +150,96 @@ function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) {
return snapshot.takeCensus(censusOptions);
}
/**
* 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 {String} assertion
* The assertion message.
*/
function compareCensusViewData (breakdown, report, expected, assertion) {
let data = new CensusTreeNode(breakdown, report);
equal(JSON.stringify(data), JSON.stringify(expected), assertion);
}
// Deep structural equivalence that can handle Map objects in addition to plain
// objects.
function assertStructurallyEquivalent(actual, expected, path="root") {
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`);
} 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`);
}
} 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);
}

View File

@ -0,0 +1,49 @@
/* 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 deltaTreeNode = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN },
{ asTreeNode: true });
compareCensusViewData(BREAKDOWN, delta, deltaTreeNode,
"Returning delta-census as a tree node represents same data as the report");
client.destroy();
});

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

@ -9,10 +9,17 @@ support-files =
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-tree-node-01.js]
[test_census-tree-node-02.js]
[test_census-tree-node-03.js]
[test_HeapAnalyses_readHeapSnapshot_01.js]
[test_HeapAnalyses_takeCensusDiff_01.js]
[test_HeapAnalyses_takeCensus_01.js]
[test_HeapAnalyses_takeCensus_02.js]
[test_HeapAnalyses_takeCensus_03.js]