mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 01:05:45 +00:00
Bug 1201542 - Implement allocationSite
breakdown for CensusTreeNode; r=jsantell
This commit is contained in:
parent
56e65abbc0
commit
1995b160e9
@ -19,7 +19,7 @@ Bug 1067491 - Test taking a census over the RDP.
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
var { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
|
||||
var { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
|
||||
var { INDENTATION, CensusView } = require("devtools/client/memory/modules/census-view");
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
@ -52,7 +52,7 @@ window.onload = function() {
|
||||
{ level: 1, name: "js::Shape2", bytes: 40, count: 4, },
|
||||
{ level: 1, name: "js::Shape", bytes: 30, count: 3, },
|
||||
];
|
||||
var censusTreeNode = new CensusTreeNode(BREAKDOWN, REPORT);
|
||||
var censusTreeNode = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
|
||||
|
||||
var view = new CensusView({
|
||||
censusTreeNode: censusTreeNode,
|
||||
|
@ -17,19 +17,29 @@ exports.Visitor = Visitor;
|
||||
* @param {Object} breakdown
|
||||
* The breakdown for the sub-report that is being entered by traversal.
|
||||
*
|
||||
* @param {Object} report
|
||||
* The report generated by the given breakdown.
|
||||
*
|
||||
* @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) { };
|
||||
Visitor.prototype.enter = function (breakdown, report, 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.
|
||||
*
|
||||
* @param {Object} report
|
||||
* The report generated by the given breakdown.
|
||||
*
|
||||
* @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.exit = function (breakdown) { };
|
||||
Visitor.prototype.exit = function (breakdown, report, edge) { };
|
||||
|
||||
/**
|
||||
* The `count` method is called when leaf nodes (reports whose breakdown is
|
||||
@ -111,15 +121,15 @@ exports.getReportEdges = getReportEdges;
|
||||
|
||||
function recursiveWalk(breakdown, edge, report, visitor) {
|
||||
if (breakdown.by === "count") {
|
||||
visitor.enter(breakdown, edge);
|
||||
visitor.enter(breakdown, report, edge);
|
||||
visitor.count(breakdown, report, edge);
|
||||
visitor.exit(breakdown, edge);
|
||||
visitor.exit(breakdown, report, edge);
|
||||
} else {
|
||||
visitor.enter(breakdown, edge);
|
||||
visitor.enter(breakdown, report, edge);
|
||||
for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) {
|
||||
recursiveWalk(breakdown, edge, referent, visitor);
|
||||
}
|
||||
visitor.exit(breakdown, edge);
|
||||
visitor.exit(breakdown, report, edge);
|
||||
}
|
||||
};
|
||||
|
||||
@ -206,7 +216,7 @@ DiffVisitor.prototype._set = function (report, edge, val) {
|
||||
/**
|
||||
* @overrides Visitor.prototype.enter
|
||||
*/
|
||||
DiffVisitor.prototype.enter = function (breakdown, edge) {
|
||||
DiffVisitor.prototype.enter = function (breakdown, report, edge) {
|
||||
const isFirstTimeEntering = this._results === null;
|
||||
|
||||
const newResults = breakdown.by === "allocationStack" ? new Map() : {};
|
||||
@ -235,7 +245,7 @@ DiffVisitor.prototype.enter = function (breakdown, edge) {
|
||||
/**
|
||||
* @overrides Visitor.prototype.exit
|
||||
*/
|
||||
DiffVisitor.prototype.exit = function (breakdown) {
|
||||
DiffVisitor.prototype.exit = function (breakdown, report, edge) {
|
||||
// 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];
|
||||
|
@ -12,7 +12,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 { censusReportToCensusTreeNode } = 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
|
||||
@ -38,7 +38,7 @@ workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions,
|
||||
|
||||
let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
|
||||
return requestOptions.asTreeNode
|
||||
? new CensusTreeNode(censusOptions.breakdown, report)
|
||||
? censusReportToCensusTreeNode(censusOptions.breakdown, report)
|
||||
: report;
|
||||
});
|
||||
|
||||
@ -66,6 +66,6 @@ workerHelper.createTask(self, "takeCensusDiff", request => {
|
||||
const delta = CensusUtils.diff(censusOptions.breakdown, first, second);
|
||||
|
||||
return requestOptions.asTreeNode
|
||||
? new CensusTreeNode(censusOptions.breakdown, delta)
|
||||
? censusReportToCensusTreeNode(censusOptions.breakdown, delta)
|
||||
: delta;
|
||||
});
|
||||
|
@ -3,11 +3,347 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Utilities for interfacing with census reports from dbg.memory.takeCensus().
|
||||
*/
|
||||
// CensusTreeNode is an intermediate representation of a census report that
|
||||
// exists between after a report is generated by taking a census and before the
|
||||
// report is rendered in the DOM. It must be dead simple to render, with no
|
||||
// further data processing or massaging needed before rendering DOM nodes. Our
|
||||
// goal is to do the census report to CensusTreeNode transformation in the
|
||||
// HeapAnalysesWorker, and ensure that the **only** work that the main thread
|
||||
// has to do is strictly DOM rendering work.
|
||||
|
||||
const COARSE_TYPES = new Set(["objects", "scripts", "strings", "other"]);
|
||||
const { Visitor, walk } = require("resource://gre/modules/devtools/shared/heapsnapshot/CensusUtils.js");
|
||||
|
||||
/**
|
||||
* Return true if the given object is a SavedFrame stack object, false otherwise.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isSavedFrame(obj) {
|
||||
return Object.prototype.toString.call(obj) === "[object SavedFrame]";
|
||||
}
|
||||
|
||||
/**
|
||||
* A FrameCache maps from SavedFrames to CensusTreeNodes. It is used when
|
||||
* aggregating multiple SavedFrame allocation stack keys into a tree of many
|
||||
* CensusTreeNodes. Each stack may share older frames, and we want to preserve
|
||||
* this sharing when converting to CensusTreeNode, so before creating a new
|
||||
* CensusTreeNode, we look for an existing one in one of our FrameCaches.
|
||||
*/
|
||||
function FrameCache() {}
|
||||
FrameCache.prototype = null;
|
||||
|
||||
/**
|
||||
* The value of a single entry stored in a FrameCache. It is a pair of the
|
||||
* CensusTreeNode for this frame, and the subsequent FrameCache for this node's
|
||||
* children.
|
||||
*
|
||||
* @param {SavedFrame} frame
|
||||
* The frame being cached.
|
||||
*/
|
||||
function FrameCacheValue(frame) {
|
||||
// The CensusTreeNode for this frame.
|
||||
this.node = new CensusTreeNode(frame);
|
||||
// The FrameCache for this frame's children.
|
||||
this.children = undefined;
|
||||
}
|
||||
|
||||
FrameCacheValue.prototype = null;
|
||||
|
||||
/**
|
||||
* Create a unique string for the given SavedFrame (ignoring the frame's parent
|
||||
* chain) that can be used as a hash to key this frame within a FrameCache.
|
||||
*
|
||||
* @param {SavedFrame} frame
|
||||
* The SavedFrame object we would like to lookup in or insert into a
|
||||
* FrameCache.
|
||||
*
|
||||
* @returns {String}
|
||||
* The unique string that can be used as a key in a FrameCache.
|
||||
*/
|
||||
FrameCache.hash = function (frame) {
|
||||
return `${frame.functionDisplayName},${frame.source},${frame.line},${frame.column},${frame.asyncCause}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Associate `frame` with `value` in the given `cache`.
|
||||
*
|
||||
* @param {FrameCache} cache
|
||||
* @param {SavedFrame} frame
|
||||
* @param {FrameCacheValue} value
|
||||
*/
|
||||
FrameCache.insert = function (cache, frame, value) {
|
||||
cache[FrameCache.hash(frame)] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup `frame` in `cache` and return its value if it exists.
|
||||
*
|
||||
* @param {FrameCache} cache
|
||||
* @param {SavedFrame} frame
|
||||
*
|
||||
* @returns {undefined|FrameCacheValue}
|
||||
*/
|
||||
FrameCache.lookup = function (cache, frame) {
|
||||
return cache[FrameCache.hash(frame)];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add `child` to `parent`'s set of children.
|
||||
*
|
||||
* @param {CensusTreeNode} parent
|
||||
* @param {CensusTreeNode} child
|
||||
*/
|
||||
function addChild(parent, child) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of each frame in the provided stack.
|
||||
*
|
||||
* @param {SavedFrame} stack
|
||||
* @returns {Array<SavedFrame>}
|
||||
*/
|
||||
function getArrayOfFrames(stack) {
|
||||
const frames = [];
|
||||
let frame = stack;
|
||||
while (frame) {
|
||||
frames.push(frame);
|
||||
frame = frame.parent;
|
||||
}
|
||||
frames.reverse();
|
||||
return frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an `edge` to a sub-`report` whose structure is described by
|
||||
* `breakdown`, create a CensusTreeNode tree.
|
||||
*
|
||||
* @param {Object} breakdown
|
||||
* The breakdown specifying the structure of the given report.
|
||||
*
|
||||
* @param {Object} report
|
||||
* The census report.
|
||||
*
|
||||
* @param {null|String|SavedFrame} edge
|
||||
* The edge leading to this report from the parent report.
|
||||
*
|
||||
* @param {FrameCache} frameCache
|
||||
* The cache of CensusTreeNodes we have already made for the siblings of
|
||||
* the node being created. The existing nodes are reused when possible.
|
||||
*
|
||||
* @param {Object} outParams
|
||||
* The return values are attached to this object after this function
|
||||
* returns. Because we create a CensusTreeNode for each frame in a
|
||||
* SavedFrame stack edge, there may multiple nodes per sub-report.
|
||||
*
|
||||
* - top: The deepest node in the CensusTreeNode subtree created.
|
||||
*
|
||||
* - bottom: The shallowest node in the CensusTreeNode subtree created.
|
||||
* This is null if the shallowest node in the subtree was
|
||||
* found in the `frameCache` and reused.
|
||||
*
|
||||
* Note that top and bottom are not necessarily different. In the case
|
||||
* where there is a 1:1 correspondence between an edge in the report and
|
||||
* a CensusTreeNode, top and bottom refer to the same node.
|
||||
*/
|
||||
function makeCensusTreeNodeSubTree(breakdown, report, edge, frameCache, outParams) {
|
||||
if (!isSavedFrame(edge)) {
|
||||
const node = new CensusTreeNode(edge);
|
||||
outParams.top = outParams.bottom = node;
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through each frame in the stack and get or create a CensusTreeNode for
|
||||
// the frame.
|
||||
|
||||
const frames = getArrayOfFrames(edge);
|
||||
let cache = frameCache;
|
||||
let prevNode;
|
||||
for (let i = 0, length = frames.length; i < length; i++) {
|
||||
const frame = frames[i];
|
||||
|
||||
// Get or create the FrameCacheValue for this frame. If we already have a
|
||||
// FrameCacheValue (and hence a CensusTreeNode) for this frame, we don't
|
||||
// need to add the node to the previous node's children as we have already
|
||||
// done that. If we don't have a FrameCacheValue and CensusTreeNode for
|
||||
// this frame, then create one and make sure to hook it up as a child of
|
||||
// the previous node.
|
||||
let isNewNode = false;
|
||||
let val = FrameCache.lookup(cache, frame);
|
||||
if (!val) {
|
||||
isNewNode = true;
|
||||
val = new FrameCacheValue(frame);
|
||||
|
||||
FrameCache.insert(cache, frame, val);
|
||||
if (prevNode) {
|
||||
addChild(prevNode, val.node);
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
outParams.bottom = isNewNode ? val.node : null;
|
||||
}
|
||||
if (i === length - 1) {
|
||||
outParams.top = val.node;
|
||||
}
|
||||
|
||||
prevNode = val.node;
|
||||
|
||||
if (i !== length - 1 && !val.children) {
|
||||
// This is not the last frame and therefore this node will have
|
||||
// children, which we must cache.
|
||||
val.children = new FrameCache();
|
||||
}
|
||||
|
||||
cache = val.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Visitor that walks a census report and creates the corresponding
|
||||
* CensusTreeNode tree.
|
||||
*/
|
||||
function CensusTreeNodeVisitor() {
|
||||
// The root of the resulting CensusTreeNode tree.
|
||||
this._root = null;
|
||||
|
||||
// The stack of CensusTreeNodes that we are in the process of building while
|
||||
// walking the census report.
|
||||
this._nodeStack = [];
|
||||
|
||||
// To avoid unnecessary allocations, we reuse the same out parameter object
|
||||
// passed to `makeCensusTreeNodeSubTree` every time we call it.
|
||||
this._outParams = {
|
||||
top: null,
|
||||
bottom: null,
|
||||
};
|
||||
|
||||
// The stack of `FrameCache`s that we use to aggregate many SavedFrame stacks
|
||||
// into a single CensusTreeNode tree.
|
||||
this._frameCacheStack = [new FrameCache()];
|
||||
}
|
||||
|
||||
CensusTreeNodeVisitor.prototype = Object.create(Visitor);
|
||||
|
||||
/**
|
||||
* Create the CensusTreeNode subtree for this sub-report and link it to the
|
||||
* parent CensusTreeNode.
|
||||
*
|
||||
* @overrides Visitor.prototype.enter
|
||||
*/
|
||||
CensusTreeNodeVisitor.prototype.enter = function (breakdown, report, edge) {
|
||||
const cache = this._frameCacheStack[this._frameCacheStack.length - 1];
|
||||
makeCensusTreeNodeSubTree(breakdown, report, edge, cache, this._outParams);
|
||||
const { top, bottom } = this._outParams;
|
||||
|
||||
if (!this._root) {
|
||||
this._root = bottom;
|
||||
} else {
|
||||
if (bottom) {
|
||||
addChild(this._nodeStack[this._nodeStack.length - 1], bottom);
|
||||
}
|
||||
}
|
||||
|
||||
this._frameCacheStack.push(new FrameCache);
|
||||
this._nodeStack.push(top);
|
||||
};
|
||||
|
||||
function values(cache) {
|
||||
return Object.keys(cache).map(k => cache[k]);
|
||||
}
|
||||
|
||||
/**
|
||||
* We have finished adding children to the CensusTreeNode subtree for the
|
||||
* current sub-report. Make sure that the children are sorted for every node in
|
||||
* the subtree.
|
||||
*
|
||||
* @overrides Visitor.prototype.exit
|
||||
*/
|
||||
CensusTreeNodeVisitor.prototype.exit = function (breakdown, report, edge) {
|
||||
const top = this._nodeStack.pop();
|
||||
if (top.children) {
|
||||
top.children.sort(compareByBytes);
|
||||
}
|
||||
|
||||
const cache = this._frameCacheStack.pop();
|
||||
const toSort = values(cache);
|
||||
while (toSort.length) {
|
||||
const { node, children } = toSort.pop();
|
||||
if (!node.children) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node !== top) {
|
||||
node.children.sort(compareByBytes);
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newlyNeedSorting = values(children);
|
||||
for (let i = 0, length = newlyNeedSorting.length; i < length; i++) {
|
||||
toSort.push(newlyNeedSorting[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @overrides Visitor.prototype.count
|
||||
*/
|
||||
CensusTreeNodeVisitor.prototype.count = function (breakdown, report, edge) {
|
||||
const node = this._nodeStack[this._nodeStack.length - 1];
|
||||
node.count = report.count;
|
||||
node.bytes = report.bytes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the root of the resulting CensusTreeNode tree.
|
||||
*
|
||||
* @returns {CensusTreeNode}
|
||||
*/
|
||||
CensusTreeNodeVisitor.prototype.root = function () {
|
||||
if (!this._root) {
|
||||
throw new Error("Attempt to get the root before walking the census report!");
|
||||
}
|
||||
|
||||
if (this._nodeStack.length) {
|
||||
throw new Error("Attempt to get the root while walking the census report!");
|
||||
}
|
||||
|
||||
return this._root;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a single, uninitialized CensusTreeNode.
|
||||
*
|
||||
* @param {null|String|SavedFrame} name
|
||||
*/
|
||||
function CensusTreeNode (name) {
|
||||
this.name = name;
|
||||
this.bytes = undefined;
|
||||
this.count = undefined;
|
||||
this.children = undefined;
|
||||
}
|
||||
|
||||
CensusTreeNode.prototype = null;
|
||||
|
||||
/**
|
||||
* Compare the given nodes by their `bytes` properties.
|
||||
*
|
||||
* @param {CensusTreeNode} node1
|
||||
* @param {CensusTreeNode} node2
|
||||
*
|
||||
* @returns {Number}
|
||||
* A number suitable for using with Array.prototype.sort.
|
||||
*/
|
||||
function compareByBytes (node1, node2) {
|
||||
return (node2.bytes || 0) - (node1.bytes || 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a report from a census (`dbg.memory.takeCensus()`) and the breakdown
|
||||
@ -25,67 +361,15 @@ const COARSE_TYPES = new Set(["objects", "scripts", "strings", "other"]);
|
||||
* }
|
||||
*
|
||||
* @param {Object} breakdown
|
||||
* The breakdown used to generate the census report.
|
||||
*
|
||||
* @param {Object} report
|
||||
* @param {?String} name
|
||||
* @return {Object}
|
||||
* The census report generated with the specified breakdown.
|
||||
*
|
||||
* @returns {CensusTreeNode}
|
||||
*/
|
||||
function CensusTreeNode (breakdown, report, name) {
|
||||
this.name = name;
|
||||
this.bytes = void 0;
|
||||
this.count = void 0;
|
||||
this.children = void 0;
|
||||
|
||||
CensusTreeNodeBreakdowns[breakdown.by](this, breakdown, report);
|
||||
|
||||
if (this.children) {
|
||||
this.children.sort(sortByBytes);
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNode.prototype = null;
|
||||
|
||||
/**
|
||||
* A series of functions to handle different breakdowns used by CensusTreeNode
|
||||
*/
|
||||
const CensusTreeNodeBreakdowns = Object.create(null);
|
||||
|
||||
CensusTreeNodeBreakdowns.count = function (node, breakdown, report) {
|
||||
if (breakdown.bytes === true) {
|
||||
node.bytes = report.bytes;
|
||||
}
|
||||
if (breakdown.count === true) {
|
||||
node.count = report.count;
|
||||
}
|
||||
exports.censusReportToCensusTreeNode = function (breakdown, report) {
|
||||
const visitor = new CensusTreeNodeVisitor();
|
||||
walk(breakdown, report, visitor);
|
||||
return visitor.root();
|
||||
};
|
||||
|
||||
CensusTreeNodeBreakdowns.internalType = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let key of Object.keys(report)) {
|
||||
node.children.push(new CensusTreeNode(breakdown.then, report[key], key));
|
||||
}
|
||||
};
|
||||
|
||||
CensusTreeNodeBreakdowns.objectClass = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let key of Object.keys(report)) {
|
||||
let bd = key === "other" ? breakdown.other : breakdown.then;
|
||||
node.children.push(new CensusTreeNode(bd, report[key], key));
|
||||
}
|
||||
};
|
||||
|
||||
CensusTreeNodeBreakdowns.coarseType = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let type of Object.keys(breakdown).filter(type => COARSE_TYPES.has(type))) {
|
||||
node.children.push(new CensusTreeNode(breakdown[type], report[type], type));
|
||||
}
|
||||
};
|
||||
|
||||
CensusTreeNodeBreakdowns.allocationStack = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
};
|
||||
|
||||
function sortByBytes (a, b) {
|
||||
return (b.bytes || 0) - (a.bytes || 0);
|
||||
}
|
||||
|
||||
exports.CensusTreeNode = CensusTreeNode;
|
||||
|
@ -20,7 +20,7 @@ const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const HeapAnalysesClient =
|
||||
require("devtools/shared/heapsnapshot/HeapAnalysesClient");
|
||||
const Services = require("Services");
|
||||
const { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
|
||||
const { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
|
||||
const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
|
||||
|
||||
// Always log packets when running tests. runxpcshelltests.py will throw
|
||||
@ -150,6 +150,18 @@ function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) {
|
||||
return snapshot.takeCensus(censusOptions);
|
||||
}
|
||||
|
||||
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.
|
||||
@ -162,18 +174,27 @@ function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) {
|
||||
*
|
||||
* @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);
|
||||
function compareCensusViewData (breakdown, report, expected) {
|
||||
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);
|
||||
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") {
|
||||
@ -195,7 +216,7 @@ function assertStructurallyEquivalent(actual, expected, path="root") {
|
||||
}
|
||||
|
||||
equal(expectedKeys.size, 0,
|
||||
`${path}: every key in expected should also exist in actual`);
|
||||
`${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
|
||||
} else {
|
||||
const expectedKeys = new Set(Object.keys(expected));
|
||||
|
||||
@ -208,7 +229,7 @@ function assertStructurallyEquivalent(actual, expected, path="root") {
|
||||
}
|
||||
|
||||
equal(expectedKeys.size, 0,
|
||||
`${path}: every key in expected should also exist in actual`);
|
||||
`${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
|
||||
}
|
||||
} else {
|
||||
equal(actual, expected, `${path}: primitives should be equal`);
|
||||
|
@ -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 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();
|
||||
});
|
@ -4,9 +4,6 @@
|
||||
/**
|
||||
* Tests CensusTreeNode with `internalType` breakdown.
|
||||
*/
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "internalType",
|
||||
@ -29,9 +26,16 @@ const REPORT = {
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
name: null,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{ name: "js::Shape", bytes: 500, count: 50, },
|
||||
{ name: "JSObject", bytes: 100, count: 10, },
|
||||
{ name: "JSString", bytes: 0, count: 0, },
|
||||
{ name: "js::Shape", bytes: 500, count: 50, children: undefined },
|
||||
{ name: "JSObject", bytes: 100, count: 10, children: undefined },
|
||||
{ name: "JSString", bytes: 0, count: 0, children: undefined },
|
||||
],
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
|
||||
}
|
||||
|
@ -5,16 +5,13 @@
|
||||
* Tests CensusTreeNode with `coarseType` breakdown.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
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 },
|
||||
};
|
||||
|
||||
@ -24,6 +21,7 @@ const REPORT = {
|
||||
"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 }
|
||||
@ -31,15 +29,43 @@ const REPORT = {
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
name: null,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{ name: "strings", bytes: 10, count: 1, },
|
||||
{ name: "objects", children: [
|
||||
{ name: "Array", bytes: 20, count: 2, },
|
||||
{ name: "Function", bytes: 10, count: 1, },
|
||||
]},
|
||||
{ name: "other", children: [
|
||||
{ name: "js::Shape2", bytes: 40, count: 4, },
|
||||
{ name: "js::Shape", bytes: 30, count: 3, },
|
||||
]},
|
||||
{
|
||||
name: "strings",
|
||||
count: 1,
|
||||
bytes: 10,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: "scripts",
|
||||
count: 1,
|
||||
bytes: 1,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: "objects",
|
||||
count: undefined,
|
||||
bytes: undefined,
|
||||
children: [
|
||||
{ name: "Array", bytes: 20, count: 2, children: undefined },
|
||||
{ name: "Function", bytes: 10, count: 1, children: undefined },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "other",
|
||||
count: undefined,
|
||||
bytes: undefined,
|
||||
children: [
|
||||
{ name: "js::Shape2", bytes: 40, count: 4, children: undefined },
|
||||
{ name: "js::Shape", bytes: 30, count: 3, children: undefined },
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
|
||||
}
|
||||
|
@ -5,10 +5,6 @@
|
||||
* Tests CensusTreeNode with `objectClass` breakdown.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
|
||||
const BREAKDOWN = {
|
||||
@ -27,12 +23,24 @@ const REPORT = {
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
name: null,
|
||||
count: undefined,
|
||||
bytes: undefined,
|
||||
children: [
|
||||
{ name: "Array", bytes: 100, count: 1 },
|
||||
{ name: "Function", bytes: 10, count: 10 },
|
||||
{ name: "other", children: [
|
||||
{ name: "JIT::CODE::LATER!!!", bytes: 40, count: 4 },
|
||||
{ name: "JIT::CODE::NOW!!!", bytes: 20, count: 2 },
|
||||
]}
|
||||
{ name: "Array", bytes: 100, count: 1, children: undefined },
|
||||
{ name: "Function", bytes: 10, count: 10, children: undefined },
|
||||
{
|
||||
name: "other",
|
||||
count: undefined,
|
||||
bytes: undefined,
|
||||
children: [
|
||||
{ name: "JIT::CODE::LATER!!!", bytes: 40, count: 4, children: undefined },
|
||||
{ name: "JIT::CODE::NOW!!!", bytes: 20, count: 2, children: undefined },
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
/* 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: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: "noStack",
|
||||
bytes: 60,
|
||||
count: 6,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: stack5,
|
||||
bytes: 50,
|
||||
count: 5,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: stack4.parent,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: stack4,
|
||||
bytes: 40,
|
||||
count: 4,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: stack1.parent,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: stack1,
|
||||
bytes: 10,
|
||||
count: 1,
|
||||
children: undefined
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: stack3.parent,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: stack3,
|
||||
bytes: 30,
|
||||
count: 3,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: stack2,
|
||||
bytes: 20,
|
||||
count: 2,
|
||||
children: undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/* 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: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: "noStack",
|
||||
bytes: 50,
|
||||
count: 5,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: stack.parent.parent,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: stack.parent,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: stack,
|
||||
bytes: undefined,
|
||||
count: undefined,
|
||||
children: [
|
||||
{
|
||||
name: "other",
|
||||
bytes: 40,
|
||||
count: 4,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: "Baz",
|
||||
bytes: 30,
|
||||
count: 3,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: "Bar",
|
||||
bytes: 20,
|
||||
count: 2,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
name: "Foo",
|
||||
bytes: 10,
|
||||
count: 1,
|
||||
children: undefined
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
|
||||
}
|
@ -18,6 +18,8 @@ support-files =
|
||||
[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_HeapAnalyses_readHeapSnapshot_01.js]
|
||||
[test_HeapAnalyses_takeCensusDiff_01.js]
|
||||
[test_HeapAnalyses_takeCensus_01.js]
|
||||
@ -25,6 +27,7 @@ support-files =
|
||||
[test_HeapAnalyses_takeCensus_03.js]
|
||||
[test_HeapAnalyses_takeCensus_04.js]
|
||||
[test_HeapAnalyses_takeCensus_05.js]
|
||||
[test_HeapAnalyses_takeCensus_06.js]
|
||||
[test_HeapSnapshot_creationTime_01.js]
|
||||
[test_HeapSnapshot_takeCensus_01.js]
|
||||
[test_HeapSnapshot_takeCensus_02.js]
|
||||
|
Loading…
Reference in New Issue
Block a user