mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1214070
- Add support for diffing census reports to HeapAnalysesWorker; r=jsantell
This commit is contained in:
parent
1019f0f2cb
commit
cc099550eb
324
devtools/shared/heapsnapshot/CensusUtils.js
Normal file
324
devtools/shared/heapsnapshot/CensusUtils.js
Normal 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
|
@ -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
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -45,6 +45,7 @@ FINAL_LIBRARY = 'xul'
|
||||
|
||||
DevToolsModules(
|
||||
'census-tree-node.js',
|
||||
'CensusUtils.js',
|
||||
'HeapAnalysesClient.js',
|
||||
'HeapAnalysesWorker.js',
|
||||
'HeapSnapshotFileUtils.js',
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
137
devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js
Normal file
137
devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js
Normal 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);
|
||||
}
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user