/* 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/. */ /* eslint-env mozilla/chrome-worker */ // This is a worker which reads offline heap snapshots into memory and performs // heavyweight analyses on them without blocking the main thread. A // HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient // instance. See HeapAnalysesClient.js. "use strict"; /* import-globals-from /toolkit/components/workerloader/require.js */ importScripts("resource://gre/modules/workers/require.js"); /* import-globals-from ../worker/helper.js */ importScripts("resource://devtools/shared/worker/helper.js"); const { censusReportToCensusTreeNode, } = require("resource://devtools/shared/heapsnapshot/census-tree-node.js"); const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js"); const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js"); const DEFAULT_START_INDEX = 0; const DEFAULT_MAX_COUNT = 50; /** * The set of HeapSnapshot instances this worker has read into memory. Keyed by * snapshot file path. */ const snapshots = Object.create(null); /** * The set of `DominatorTree`s that have been computed, mapped by their id (aka * the index into this array). * * @see /dom/webidl/DominatorTree.webidl */ const dominatorTrees = []; /** * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th * dominator tree in `dominatorTrees` above. This lets us map from a dominator * tree id to the snapshot it came from. */ const dominatorTreeSnapshots = []; /** * @see HeapAnalysesClient.prototype.readHeapSnapshot */ workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => { snapshots[snapshotFilePath] = ChromeUtils.readHeapSnapshot(snapshotFilePath); return true; }); /** * @see HeapAnalysesClient.prototype.deleteHeapSnapshot */ workerHelper.createTask(self, "deleteHeapSnapshot", ({ snapshotFilePath }) => { const snapshot = snapshots[snapshotFilePath]; if (!snapshot) { throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); } snapshots[snapshotFilePath] = undefined; const dominatorTreeId = dominatorTreeSnapshots.indexOf(snapshot); if (dominatorTreeId != -1) { dominatorTreeSnapshots[dominatorTreeId] = undefined; dominatorTrees[dominatorTreeId] = undefined; } }); /** * @see HeapAnalysesClient.prototype.takeCensus */ workerHelper.createTask( self, "takeCensus", ({ snapshotFilePath, censusOptions, requestOptions }) => { if (!snapshots[snapshotFilePath]) { throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); } let report = snapshots[snapshotFilePath].takeCensus(censusOptions); let parentMap; if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) { const opts = { filter: requestOptions.filter || null }; if (requestOptions.asInvertedTreeNode) { opts.invert = true; } report = censusReportToCensusTreeNode( censusOptions.breakdown, report, opts ); parentMap = CensusUtils.createParentMap(report); } return { report, parentMap }; } ); /** * @see HeapAnalysesClient.prototype.getCensusIndividuals */ workerHelper.createTask(self, "getCensusIndividuals", request => { const { dominatorTreeId, indices, censusBreakdown, labelBreakdown, maxRetainingPaths, maxIndividuals, } = request; const dominatorTree = dominatorTrees[dominatorTreeId]; if (!dominatorTree) { throw new Error( `There does not exist a DominatorTree with the id ${dominatorTreeId}` ); } const snapshot = dominatorTreeSnapshots[dominatorTreeId]; const nodeIds = CensusUtils.getCensusIndividuals( indices, censusBreakdown, snapshot ); const nodes = nodeIds .sort( (a, b) => dominatorTree.getRetainedSize(b) - dominatorTree.getRetainedSize(a) ) .slice(0, maxIndividuals) .map(id => { const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize( id, snapshot, labelBreakdown ); const retainedSize = dominatorTree.getRetainedSize(id); const node = new DominatorTreeNode(id, label, shallowSize, retainedSize); node.moreChildrenAvailable = false; return node; }); DominatorTreeNode.attachShortestPaths( snapshot, labelBreakdown, dominatorTree.root, nodes, maxRetainingPaths ); return { nodes }; }); /** * @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); let delta = CensusUtils.diff(censusOptions.breakdown, first, second); let parentMap; if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) { const opts = { filter: requestOptions.filter || null }; if (requestOptions.asInvertedTreeNode) { opts.invert = true; } delta = censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts); parentMap = CensusUtils.createParentMap(delta); } return { delta, parentMap }; }); /** * @see HeapAnalysesClient.prototype.getCreationTime */ workerHelper.createTask(self, "getCreationTime", snapshotFilePath => { if (!snapshots[snapshotFilePath]) { throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); } return snapshots[snapshotFilePath].creationTime; }); /** * @see HeapAnalysesClient.prototype.computeDominatorTree */ workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => { const snapshot = snapshots[snapshotFilePath]; if (!snapshot) { throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); } const id = dominatorTrees.length; dominatorTrees.push(snapshot.computeDominatorTree()); dominatorTreeSnapshots.push(snapshot); return id; }); /** * @see HeapAnalysesClient.prototype.getDominatorTree */ workerHelper.createTask(self, "getDominatorTree", request => { const { dominatorTreeId, breakdown, maxDepth, maxSiblings, maxRetainingPaths, } = request; if (!(dominatorTreeId >= 0 && dominatorTreeId < dominatorTrees.length)) { throw new Error( `There does not exist a DominatorTree with the id ${dominatorTreeId}` ); } const dominatorTree = dominatorTrees[dominatorTreeId]; const snapshot = dominatorTreeSnapshots[dominatorTreeId]; const tree = DominatorTreeNode.partialTraversal( dominatorTree, snapshot, breakdown, maxDepth, maxSiblings ); const nodes = []; (function getNodes(node) { nodes.push(node); if (node.children) { for (let i = 0, length = node.children.length; i < length; i++) { getNodes(node.children[i]); } } })(tree); DominatorTreeNode.attachShortestPaths( snapshot, breakdown, dominatorTree.root, nodes, maxRetainingPaths ); return tree; }); /** * @see HeapAnalysesClient.prototype.getImmediatelyDominated */ workerHelper.createTask(self, "getImmediatelyDominated", request => { const { dominatorTreeId, nodeId, breakdown, startIndex, maxCount, maxRetainingPaths, } = request; if (!(dominatorTreeId >= 0 && dominatorTreeId < dominatorTrees.length)) { throw new Error( `There does not exist a DominatorTree with the id ${dominatorTreeId}` ); } const dominatorTree = dominatorTrees[dominatorTreeId]; const snapshot = dominatorTreeSnapshots[dominatorTreeId]; const childIds = dominatorTree.getImmediatelyDominated(nodeId); if (!childIds) { throw new Error(`${nodeId} is not a node id in the dominator tree`); } const start = startIndex || DEFAULT_START_INDEX; const count = maxCount || DEFAULT_MAX_COUNT; const end = start + count; const nodes = childIds.slice(start, end).map(id => { const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize( id, snapshot, breakdown ); const retainedSize = dominatorTree.getRetainedSize(id); const node = new DominatorTreeNode(id, label, shallowSize, retainedSize); node.parentId = nodeId; // DominatorTree.getImmediatelyDominated will always return non-null here // because we got the id directly from the dominator tree. node.moreChildrenAvailable = !!dominatorTree.getImmediatelyDominated(id) .length; return node; }); const path = []; let id = nodeId; do { path.push(id); id = dominatorTree.getImmediateDominator(id); } while (id !== null); path.reverse(); const moreChildrenAvailable = childIds.length > end; DominatorTreeNode.attachShortestPaths( snapshot, breakdown, dominatorTree.root, nodes, maxRetainingPaths ); return { nodes, moreChildrenAvailable, path }; });