From e9ee12c988d80f847f8163c0873db9639b3fa8f4 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 23 Feb 2020 10:38:36 -0500 Subject: Follow-up to 4e2e9be6a - Move HeapSnapshot DevTools-only Modules back to DevTools I am so done with this. Resolves #316 --- devtools/shared/heapsnapshot/HeapAnalysesWorker.js | 303 +++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 devtools/shared/heapsnapshot/HeapAnalysesWorker.js (limited to 'devtools/shared/heapsnapshot/HeapAnalysesWorker.js') diff --git a/devtools/shared/heapsnapshot/HeapAnalysesWorker.js b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js new file mode 100644 index 000000000..d07d67f80 --- /dev/null +++ b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js @@ -0,0 +1,303 @@ +/* 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/. */ +/* global ThreadSafeChromeUtils*/ + +// 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"; + +importScripts("resource://gre/modules/workers/require.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] = + ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + return true; +}); + +/** + * @see HeapAnalysesClient.prototype.deleteHeapSnapshot + */ +workerHelper.createTask(self, "deleteHeapSnapshot", ({ snapshotFilePath }) => { + let snapshot = snapshots[snapshotFilePath]; + if (!snapshot) { + throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); + } + + snapshots[snapshotFilePath] = undefined; + + let 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 (!(0 <= dominatorTreeId && 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 (!(0 <= dominatorTreeId && 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 > 0; + 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 }; +}); -- cgit v1.2.3