HeapAnalyses.worker.js (9399B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // This is a worker which reads offline heap snapshots into memory and performs 6 // heavyweight analyses on them without blocking the main thread. A 7 // HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient 8 // instance. See HeapAnalysesClient.js. 9 10 "use strict"; 11 12 /* import-globals-from /toolkit/components/workerloader/require.js */ 13 importScripts("resource://gre/modules/workers/require.js"); 14 /* import-globals-from ../worker/helper.js */ 15 importScripts("resource://devtools/shared/worker/helper.js"); 16 const { 17 censusReportToCensusTreeNode, 18 } = require("resource://devtools/shared/heapsnapshot/census-tree-node.js"); 19 const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js"); 20 const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js"); 21 22 const DEFAULT_START_INDEX = 0; 23 const DEFAULT_MAX_COUNT = 50; 24 25 /** 26 * The set of HeapSnapshot instances this worker has read into memory. Keyed by 27 * snapshot file path. 28 */ 29 const snapshots = Object.create(null); 30 31 /** 32 * The set of `DominatorTree`s that have been computed, mapped by their id (aka 33 * the index into this array). 34 * 35 * @see /dom/webidl/DominatorTree.webidl 36 */ 37 const dominatorTrees = []; 38 39 /** 40 * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th 41 * dominator tree in `dominatorTrees` above. This lets us map from a dominator 42 * tree id to the snapshot it came from. 43 */ 44 const dominatorTreeSnapshots = []; 45 46 /** 47 * @see HeapAnalysesClient.prototype.readHeapSnapshot 48 */ 49 workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => { 50 snapshots[snapshotFilePath] = ChromeUtils.readHeapSnapshot(snapshotFilePath); 51 return true; 52 }); 53 54 /** 55 * @see HeapAnalysesClient.prototype.deleteHeapSnapshot 56 */ 57 workerHelper.createTask(self, "deleteHeapSnapshot", ({ snapshotFilePath }) => { 58 const snapshot = snapshots[snapshotFilePath]; 59 if (!snapshot) { 60 throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); 61 } 62 63 snapshots[snapshotFilePath] = undefined; 64 65 const dominatorTreeId = dominatorTreeSnapshots.indexOf(snapshot); 66 if (dominatorTreeId != -1) { 67 dominatorTreeSnapshots[dominatorTreeId] = undefined; 68 dominatorTrees[dominatorTreeId] = undefined; 69 } 70 }); 71 72 /** 73 * @see HeapAnalysesClient.prototype.takeCensus 74 */ 75 workerHelper.createTask( 76 self, 77 "takeCensus", 78 ({ snapshotFilePath, censusOptions, requestOptions }) => { 79 if (!snapshots[snapshotFilePath]) { 80 throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); 81 } 82 83 let report = snapshots[snapshotFilePath].takeCensus(censusOptions); 84 let parentMap; 85 86 if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) { 87 const opts = { filter: requestOptions.filter || null }; 88 if (requestOptions.asInvertedTreeNode) { 89 opts.invert = true; 90 } 91 report = censusReportToCensusTreeNode( 92 censusOptions.breakdown, 93 report, 94 opts 95 ); 96 parentMap = CensusUtils.createParentMap(report); 97 } 98 99 return { report, parentMap }; 100 } 101 ); 102 103 /** 104 * @see HeapAnalysesClient.prototype.getCensusIndividuals 105 */ 106 workerHelper.createTask(self, "getCensusIndividuals", request => { 107 const { 108 dominatorTreeId, 109 indices, 110 censusBreakdown, 111 labelBreakdown, 112 maxRetainingPaths, 113 maxIndividuals, 114 } = request; 115 116 const dominatorTree = dominatorTrees[dominatorTreeId]; 117 if (!dominatorTree) { 118 throw new Error( 119 `There does not exist a DominatorTree with the id ${dominatorTreeId}` 120 ); 121 } 122 123 const snapshot = dominatorTreeSnapshots[dominatorTreeId]; 124 const nodeIds = CensusUtils.getCensusIndividuals( 125 indices, 126 censusBreakdown, 127 snapshot 128 ); 129 130 const nodes = nodeIds 131 .sort( 132 (a, b) => 133 dominatorTree.getRetainedSize(b) - dominatorTree.getRetainedSize(a) 134 ) 135 .slice(0, maxIndividuals) 136 .map(id => { 137 const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize( 138 id, 139 snapshot, 140 labelBreakdown 141 ); 142 const retainedSize = dominatorTree.getRetainedSize(id); 143 const node = new DominatorTreeNode(id, label, shallowSize, retainedSize); 144 node.moreChildrenAvailable = false; 145 return node; 146 }); 147 148 DominatorTreeNode.attachShortestPaths( 149 snapshot, 150 labelBreakdown, 151 dominatorTree.root, 152 nodes, 153 maxRetainingPaths 154 ); 155 156 return { nodes }; 157 }); 158 159 /** 160 * @see HeapAnalysesClient.prototype.takeCensusDiff 161 */ 162 workerHelper.createTask(self, "takeCensusDiff", request => { 163 const { 164 firstSnapshotFilePath, 165 secondSnapshotFilePath, 166 censusOptions, 167 requestOptions, 168 } = request; 169 170 if (!snapshots[firstSnapshotFilePath]) { 171 throw new Error(`No known heap snapshot for '${firstSnapshotFilePath}'`); 172 } 173 174 if (!snapshots[secondSnapshotFilePath]) { 175 throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`); 176 } 177 178 const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions); 179 const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions); 180 let delta = CensusUtils.diff(censusOptions.breakdown, first, second); 181 let parentMap; 182 183 if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) { 184 const opts = { filter: requestOptions.filter || null }; 185 if (requestOptions.asInvertedTreeNode) { 186 opts.invert = true; 187 } 188 delta = censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts); 189 parentMap = CensusUtils.createParentMap(delta); 190 } 191 192 return { delta, parentMap }; 193 }); 194 195 /** 196 * @see HeapAnalysesClient.prototype.getCreationTime 197 */ 198 workerHelper.createTask(self, "getCreationTime", snapshotFilePath => { 199 if (!snapshots[snapshotFilePath]) { 200 throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); 201 } 202 return snapshots[snapshotFilePath].creationTime; 203 }); 204 205 /** 206 * @see HeapAnalysesClient.prototype.computeDominatorTree 207 */ 208 workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => { 209 const snapshot = snapshots[snapshotFilePath]; 210 if (!snapshot) { 211 throw new Error(`No known heap snapshot for '${snapshotFilePath}'`); 212 } 213 214 const id = dominatorTrees.length; 215 dominatorTrees.push(snapshot.computeDominatorTree()); 216 dominatorTreeSnapshots.push(snapshot); 217 return id; 218 }); 219 220 /** 221 * @see HeapAnalysesClient.prototype.getDominatorTree 222 */ 223 workerHelper.createTask(self, "getDominatorTree", request => { 224 const { 225 dominatorTreeId, 226 breakdown, 227 maxDepth, 228 maxSiblings, 229 maxRetainingPaths, 230 } = request; 231 232 if (!(dominatorTreeId >= 0 && dominatorTreeId < dominatorTrees.length)) { 233 throw new Error( 234 `There does not exist a DominatorTree with the id ${dominatorTreeId}` 235 ); 236 } 237 238 const dominatorTree = dominatorTrees[dominatorTreeId]; 239 const snapshot = dominatorTreeSnapshots[dominatorTreeId]; 240 241 const tree = DominatorTreeNode.partialTraversal( 242 dominatorTree, 243 snapshot, 244 breakdown, 245 maxDepth, 246 maxSiblings 247 ); 248 249 const nodes = []; 250 (function getNodes(node) { 251 nodes.push(node); 252 if (node.children) { 253 for (let i = 0, length = node.children.length; i < length; i++) { 254 getNodes(node.children[i]); 255 } 256 } 257 })(tree); 258 259 DominatorTreeNode.attachShortestPaths( 260 snapshot, 261 breakdown, 262 dominatorTree.root, 263 nodes, 264 maxRetainingPaths 265 ); 266 267 return tree; 268 }); 269 270 /** 271 * @see HeapAnalysesClient.prototype.getImmediatelyDominated 272 */ 273 workerHelper.createTask(self, "getImmediatelyDominated", request => { 274 const { 275 dominatorTreeId, 276 nodeId, 277 breakdown, 278 startIndex, 279 maxCount, 280 maxRetainingPaths, 281 } = request; 282 283 if (!(dominatorTreeId >= 0 && dominatorTreeId < dominatorTrees.length)) { 284 throw new Error( 285 `There does not exist a DominatorTree with the id ${dominatorTreeId}` 286 ); 287 } 288 289 const dominatorTree = dominatorTrees[dominatorTreeId]; 290 const snapshot = dominatorTreeSnapshots[dominatorTreeId]; 291 292 const childIds = dominatorTree.getImmediatelyDominated(nodeId); 293 if (!childIds) { 294 throw new Error(`${nodeId} is not a node id in the dominator tree`); 295 } 296 297 const start = startIndex || DEFAULT_START_INDEX; 298 const count = maxCount || DEFAULT_MAX_COUNT; 299 const end = start + count; 300 301 const nodes = childIds.slice(start, end).map(id => { 302 const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize( 303 id, 304 snapshot, 305 breakdown 306 ); 307 const retainedSize = dominatorTree.getRetainedSize(id); 308 const node = new DominatorTreeNode(id, label, shallowSize, retainedSize); 309 node.parentId = nodeId; 310 // DominatorTree.getImmediatelyDominated will always return non-null here 311 // because we got the id directly from the dominator tree. 312 node.moreChildrenAvailable = 313 !!dominatorTree.getImmediatelyDominated(id).length; 314 return node; 315 }); 316 317 const path = []; 318 let id = nodeId; 319 do { 320 path.push(id); 321 id = dominatorTree.getImmediateDominator(id); 322 } while (id !== null); 323 path.reverse(); 324 325 const moreChildrenAvailable = childIds.length > end; 326 327 DominatorTreeNode.attachShortestPaths( 328 snapshot, 329 breakdown, 330 dominatorTree.root, 331 nodes, 332 maxRetainingPaths 333 ); 334 335 return { nodes, moreChildrenAvailable, path }; 336 });