/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict";
// 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.
/** *ThevalueofasingleentrystoredinaCensusTreeNodeCache.Itisapairof *theCensusTreeNodeforthiscachevalue,andthesubsequent *CensusTreeNodeCacheforthisnode'schildren.
*/ class CensusTreeNodeCacheValue {
constructor() { // The CensusTreeNode for this cache value. this.node = undefined; // The CensusTreeNodeCache for this frame's children. this.children = undefined;
}
}
const frames = getArrayOfFrames(edge);
let currentCache = cache;
let prevNode; for (let i = 0, length = frames.length; i < length; i++) { const frame = frames[i];
// Get or create the CensusTreeNodeCacheValue for this frame. If we already // have a CensusTreeNodeCacheValue (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 CensusTreeNodeCacheValue // 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 = CensusTreeNodeCache.lookupFrame(currentCache, frame); if (!val) {
isNewNode = true;
val = new CensusTreeNodeCacheValue();
val.node = new CensusTreeNode(frame);
CensusTreeNodeCache.insertFrame(currentCache, 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 CensusTreeNodeCache();
}
currentCache = val.children;
}
}
/** *AVisitorthatwalksacensusreportandcreatesthecorresponding *CensusTreeNodetree.
*/ class CensusTreeNodeVisitor extends Visitor {
constructor() { super(); // 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 `CensusTreeNodeCache`s that we use to aggregate many // SavedFrame stacks into a single CensusTreeNode tree. this._cacheStack = [new CensusTreeNodeCache()];
// The current index in the DFS of the census report tree. this._index = -1;
}
/** *WehavefinishedaddingchildrentotheCensusTreeNodesubtreeforthe *currentsub-report.Makesurethatthechildrenaresortedforeverynodein *thesubtree. * *@override
*/
exit() { // Ensure all children are sorted and have their counts/bytes aggregated. We // only need to consider cache children here, because other children // correspond to other sub-reports and we already fixed them up in an earlier // invocation of `exit`.
function dfs(node, childrenCache) { if (childrenCache) { const childValues = values(childrenCache); for (let i = 0, length = childValues.length; i < length; i++) {
dfs(childValues[i].node, childValues[i].children);
}
}
if (breakdown.count) {
node.count = report.count;
}
if (breakdown.bytes) {
node.bytes = report.bytes;
}
}
/** *GettherootoftheresultingCensusTreeNodetree. * *@returns{CensusTreeNode}
*/
root() { if (!this._root) { thrownew Error( "Attempt to get the root before walking the census report!"
);
}
if (this._nodeStack.length) { thrownew Error( "Attempt to get the root while walking the census report!"
);
}
returnthis._root;
}
}
function values(cache) { return Object.keys(cache).map(k => cache[k]);
}
/** *Createasingle,uninitializedCensusTreeNode.
*/ class CensusTreeNode { /** * *@param{null|string|SavedFrame}name
*/
constructor(name) { // Display name for this CensusTreeNode. Either null, a string, or a // SavedFrame. this.name = name;
// The number of bytes occupied by matching things in the heap snapshot. this.bytes = 0;
// The sum of `this.bytes` and `child.totalBytes` for each child in // `this.children`. this.totalBytes = 0;
// The number of things in the heap snapshot that match this node in the // census tree. this.count = 0;
// The sum of `this.count` and `child.totalCount` for each child in // `this.children`. this.totalCount = 0;
// An array of this node's children, or undefined if it has no children. this.children = undefined;
// The unique ID of this node. this.id = ++censusTreeNodeIdCounter;
// If present, the unique ID of this node's parent. If this node does not have // a parent, then undefined. this.parent = undefined;
// The `reportLeafIndex` property allows mapping a CensusTreeNode node back to // a leaf in the census report it was generated from. It is always one of the // following variants: // // * A `Number` index pointing a leaf report in a pre-order DFS traversal of // this CensusTreeNode's census report. // // * A `Set` object containing such indices, when this is part of an inverted // CensusTreeNode tree and multiple leaves in the report map onto this node. // // * Finally, `undefined` when no leaves in the census report correspond with // this node. // // The first and third cases are the common cases. The second case is rather // uncommon, and to avoid doubling the number of allocations when creating // CensusTreeNode trees, and objects that get structured cloned when sending // such trees from the HeapAnalysesWorker to the main thread, we only allocate // a Set object once a node actually does have multiple leaves it corresponds // to. this.reportLeafIndex = undefined;
}
}
/** *Givenaparentcachevaluefromatreewearebuildingandachildnodefrom *atreewearebasingthenewtreeoffof,ifwealreadyhaveacorresponding *nodeintheparent'schildrencache,mergethisnode'scountswith *it.Otherwise,createthecorrespondingnode,addittotheparent'schildren *cache,andcreatetheparent->childedge. * *@param{CensusTreeNodeCacheValue}parentCachevalue *@param{CensusTreeNode}node * *@returns{CensusTreeNodeCacheValue} *Theneworextantchildnode'scorrespondingcachevalue.
*/ function insertOrMergeNode(parentCacheValue, node) { if (!parentCacheValue.children) {
parentCacheValue.children = new CensusTreeNodeCache();
}
let val = CensusTreeNodeCache.lookupNode(parentCacheValue.children, node);
if (val) { // When inverting, it is possible that multiple leaves in the census report // get merged into a single CensusTreeNode node. When this occurs, switch // from a single index to a set of indices. if (
val.node.reportLeafIndex !== undefined &&
val.node.reportLeafIndex !== node.reportLeafIndex
) { if (typeof val.node.reportLeafIndex === "number") { const oldIndex = val.node.reportLeafIndex;
val.node.reportLeafIndex = new Set();
val.node.reportLeafIndex.add(oldIndex);
val.node.reportLeafIndex.add(node.reportLeafIndex);
} else {
val.node.reportLeafIndex.add(node.reportLeafIndex);
}
}
val.node.count += node.count;
val.node.bytes += node.bytes;
} else {
val = new CensusTreeNodeCacheValue();
/** *Givenanun-invertedCensusTreeNodetree,returnthecorrespondinginverted *CensusTreeNodetree.Theinputtreeisnotmodified.Theresultinginverted *treeissortedbyselfbytesratherthanbytotalbytes. * *@param{CensusTreeNode}tree *Theun-invertedtree. * *@returns{CensusTreeNode} *Thecorrespondinginvertedtree.
*/ function invert(tree) { const inverted = new CensusTreeNodeCacheValue();
inverted.node = new CensusTreeNode(null);
// Do a depth-first search of the un-inverted tree. As we reach each leaf, // take the path from the old root to the leaf, reverse that path, and add it // to the new, inverted tree's root.
if (node.children) { for (let i = 0, length = node.children.length; i < length; i++) {
addInvertedPaths(node.children[i]);
}
} else { // We found a leaf node, add the reverse path to the inverted tree.
let currentCacheValue = inverted; for (let i = path.length - 1; i >= 0; i--) {
currentCacheValue = insertOrMergeNode(currentCacheValue, path[i]);
}
}
path.pop();
})(tree);
// Ensure that the root node always has the totals.
inverted.node.totalBytes = tree.totalBytes;
inverted.node.totalCount = tree.totalCount;
return inverted.node;
}
/** *GivenaCensusTreeNodetreeandpredicatefunction,createthetree *containingonlythenodesinanypath`(node_0,node_1,...,node_n-1)`in *thegiventreewhere`predicate(node_j)`istruefor`0<=j<n`,`node_0` *isthegiventree'sroot,and`node_n-1`isaleafinthegiventree.The *giventreeisleftunmodified. * *@param{CensusTreeNode}tree *@param{Function}predicate * *@returns{CensusTreeNode}
*/ function filter(tree, predicate) { const filtered = new CensusTreeNodeCacheValue();
filtered.node = new CensusTreeNode(null);
// Do a DFS over the given tree. If the predicate returns true for any node, // add that node and its whole subtree to the filtered tree.
const path = [];
let match = false;
function addMatchingNodes(node) {
path.push(node);
const oldMatch = match; if (!match && predicate(node)) {
match = true;
}
if (node.children) { for (let i = 0, length = node.children.length; i < length; i++) {
addMatchingNodes(node.children[i]);
}
} elseif (match) { // We found a matching leaf node, add it to the filtered tree.
let currentCacheValue = filtered; for (let i = 0, length = path.length; i < length; i++) {
currentCacheValue = insertOrMergeNode(currentCacheValue, path[i]);
}
}
match = oldMatch;
path.pop();
}
if (tree.children) { for (let i = 0, length = tree.children.length; i < length; i++) {
addMatchingNodes(tree.children[i]);
}
}
/** *Takesareportfromacensus(`dbg.memory.takeCensus()`)andthebreakdown *usedtogeneratethecensusandreturnsastructureusedtorender *atreetodisplaythedata. * *Returnsarecursive"CensusTreeNode"object,lookinglike: * *CensusTreeNode={ *// `children` if it exists, is sorted by `bytes`, if they are leaf nodes. *children:?[<CensusTreeNode...>], *name:<?String> *count:<?Number> *bytes:<?Number> *id:<?Number> *parent:<?Number> *} * *@param{object}breakdown *Thebreakdownusedtogeneratethecensusreport. * *@param{object}report *Thecensusreportgeneratedwiththespecifiedbreakdown. * *@param{object}options *Configurationoptions. *-invert:Whethertoinverttheresultingtreeornot.Defaultsto *false,ieuninverted. * *@returns{CensusTreeNode}
*/
exports.censusReportToCensusTreeNode = function (
breakdown,
report,
options = {
invert: false,
filter: null,
}
) { // Reset the counter so that turning the same census report into a // CensusTreeNode tree repeatedly is idempotent.
censusTreeNodeIdCounter = 0;
const visitor = new CensusTreeNodeVisitor();
walk(breakdown, report, visitor);
let result = visitor.root();
if (options.invert) {
result = invert(result);
}
if (typeof options.filter === "string") {
result = filter(result, makeFilterPredicate(options.filter));
}
// If the report is a delta report that was generated by diffing two other // reports, make sure to use the basis totals rather than the totals of the // difference. if (typeof report[basisTotalBytes] === "number") {
result.totalBytes = report[basisTotalBytes];
result.totalCount = report[basisTotalCount];
}
// Inverting and filtering could have messed up the sort order, so do a // depth-first search of the tree and ensure that siblings are sorted. const comparator = options.invert ? compareBySelf : compareByTotal;
(function ensureSorted(node) { if (node.children) {
node.children.sort(comparator); for (let i = 0, length = node.children.length; i < length; i++) {
ensureSorted(node.children[i]);
}
}
})(result);
return result;
};
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.