1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 */ 34 WebInspector.HeapSnapshotArraySlice = function(array, start, end) 35 { 36 this._array = array; 37 this._start = start; 38 this.length = end - start; 39 } 40 41 WebInspector.HeapSnapshotArraySlice.prototype = { 42 item: function(index) 43 { 44 return this._array[this._start + index]; 45 }, 46 47 slice: function(start, end) 48 { 49 if (typeof end === "undefined") 50 end = this.length; 51 return this._array.subarray(this._start + start, this._start + end); 52 } 53 } 54 55 /** 56 * @constructor 57 * @param {number=} edgeIndex 58 */ 59 WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex) 60 { 61 this._snapshot = snapshot; 62 this._edges = edges; 63 this.edgeIndex = edgeIndex || 0; 64 } 65 66 WebInspector.HeapSnapshotEdge.prototype = { 67 clone: function() 68 { 69 return new WebInspector.HeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex); 70 }, 71 72 hasStringName: function() 73 { 74 throw new Error("Not implemented"); 75 }, 76 77 name: function() 78 { 79 throw new Error("Not implemented"); 80 }, 81 82 node: function() 83 { 84 return this._snapshot.createNode(this.nodeIndex()); 85 }, 86 87 nodeIndex: function() 88 { 89 return this._edges.item(this.edgeIndex + this._snapshot._edgeToNodeOffset); 90 }, 91 92 rawEdges: function() 93 { 94 return this._edges; 95 }, 96 97 toString: function() 98 { 99 return "HeapSnapshotEdge: " + this.name(); 100 }, 101 102 type: function() 103 { 104 return this._snapshot._edgeTypes[this._type()]; 105 }, 106 107 serialize: function() 108 { 109 var node = this.node(); 110 return { 111 name: this.name(), 112 node: node.serialize(), 113 nodeIndex: this.nodeIndex(), 114 type: this.type(), 115 distance: node.distance() 116 }; 117 }, 118 119 _type: function() 120 { 121 return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset); 122 } 123 }; 124 125 /** 126 * @constructor 127 */ 128 WebInspector.HeapSnapshotEdgeIterator = function(edge) 129 { 130 this.edge = edge; 131 } 132 133 WebInspector.HeapSnapshotEdgeIterator.prototype = { 134 rewind: function() 135 { 136 this.edge.edgeIndex = 0; 137 }, 138 139 hasNext: function() 140 { 141 return this.edge.edgeIndex < this.edge._edges.length; 142 }, 143 144 index: function() 145 { 146 return this.edge.edgeIndex; 147 }, 148 149 setIndex: function(newIndex) 150 { 151 this.edge.edgeIndex = newIndex; 152 }, 153 154 item: function() 155 { 156 return this.edge; 157 }, 158 159 next: function() 160 { 161 this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount; 162 } 163 }; 164 165 /** 166 * @constructor 167 */ 168 WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex) 169 { 170 this._snapshot = snapshot; 171 this._retainedNodeIndex = retainedNodeIndex; 172 173 var retainedNodeOrdinal = retainedNodeIndex / snapshot._nodeFieldCount; 174 this._firstRetainer = snapshot._firstRetainerIndex[retainedNodeOrdinal]; 175 this._retainersCount = snapshot._firstRetainerIndex[retainedNodeOrdinal + 1] - this._firstRetainer; 176 177 this.setRetainerIndex(retainerIndex); 178 } 179 180 WebInspector.HeapSnapshotRetainerEdge.prototype = { 181 clone: function() 182 { 183 return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex()); 184 }, 185 186 hasStringName: function() 187 { 188 return this._edge().hasStringName(); 189 }, 190 191 name: function() 192 { 193 return this._edge().name(); 194 }, 195 196 node: function() 197 { 198 return this._node(); 199 }, 200 201 nodeIndex: function() 202 { 203 return this._nodeIndex; 204 }, 205 206 retainerIndex: function() 207 { 208 return this._retainerIndex; 209 }, 210 211 setRetainerIndex: function(newIndex) 212 { 213 if (newIndex !== this._retainerIndex) { 214 this._retainerIndex = newIndex; 215 this.edgeIndex = newIndex; 216 } 217 }, 218 219 set edgeIndex(edgeIndex) 220 { 221 var retainerIndex = this._firstRetainer + edgeIndex; 222 this._globalEdgeIndex = this._snapshot._retainingEdges[retainerIndex]; 223 this._nodeIndex = this._snapshot._retainingNodes[retainerIndex]; 224 delete this._edgeInstance; 225 delete this._nodeInstance; 226 }, 227 228 _node: function() 229 { 230 if (!this._nodeInstance) 231 this._nodeInstance = this._snapshot.createNode(this._nodeIndex); 232 return this._nodeInstance; 233 }, 234 235 _edge: function() 236 { 237 if (!this._edgeInstance) { 238 var edgeIndex = this._globalEdgeIndex - this._node()._edgeIndexesStart(); 239 this._edgeInstance = this._snapshot.createEdge(this._node().rawEdges(), edgeIndex); 240 } 241 return this._edgeInstance; 242 }, 243 244 toString: function() 245 { 246 return this._edge().toString(); 247 }, 248 249 serialize: function() 250 { 251 var node = this.node(); 252 return { 253 name: this.name(), 254 node: node.serialize(), 255 nodeIndex: this.nodeIndex(), 256 type: this.type(), 257 distance: node.distance() 258 }; 259 }, 260 261 type: function() 262 { 263 return this._edge().type(); 264 } 265 } 266 267 /** 268 * @constructor 269 */ 270 WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer) 271 { 272 this.retainer = retainer; 273 } 274 275 WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = { 276 rewind: function() 277 { 278 this.retainer.setRetainerIndex(0); 279 }, 280 281 hasNext: function() 282 { 283 return this.retainer.retainerIndex() < this.retainer._retainersCount; 284 }, 285 286 index: function() 287 { 288 return this.retainer.retainerIndex(); 289 }, 290 291 setIndex: function(newIndex) 292 { 293 this.retainer.setRetainerIndex(newIndex); 294 }, 295 296 item: function() 297 { 298 return this.retainer; 299 }, 300 301 next: function() 302 { 303 this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1); 304 } 305 }; 306 307 /** 308 * @constructor 309 * @param {number=} nodeIndex 310 */ 311 WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex) 312 { 313 this._snapshot = snapshot; 314 this._firstNodeIndex = nodeIndex; 315 this.nodeIndex = nodeIndex; 316 } 317 318 WebInspector.HeapSnapshotNode.prototype = { 319 distance: function() 320 { 321 return this._snapshot._nodeDistances[this.nodeIndex / this._snapshot._nodeFieldCount]; 322 }, 323 324 className: function() 325 { 326 throw new Error("Not implemented"); 327 }, 328 329 classIndex: function() 330 { 331 throw new Error("Not implemented"); 332 }, 333 334 dominatorIndex: function() 335 { 336 var nodeFieldCount = this._snapshot._nodeFieldCount; 337 return this._snapshot._dominatorsTree[this.nodeIndex / this._snapshot._nodeFieldCount] * nodeFieldCount; 338 }, 339 340 edges: function() 341 { 342 return new WebInspector.HeapSnapshotEdgeIterator(this._snapshot.createEdge(this.rawEdges(), 0)); 343 }, 344 345 edgesCount: function() 346 { 347 return (this._edgeIndexesEnd() - this._edgeIndexesStart()) / this._snapshot._edgeFieldsCount; 348 }, 349 350 id: function() 351 { 352 throw new Error("Not implemented"); 353 }, 354 355 isRoot: function() 356 { 357 return this.nodeIndex === this._snapshot._rootNodeIndex; 358 }, 359 360 name: function() 361 { 362 return this._snapshot._strings[this._name()]; 363 }, 364 365 rawEdges: function() 366 { 367 return new WebInspector.HeapSnapshotArraySlice(this._snapshot._containmentEdges, this._edgeIndexesStart(), this._edgeIndexesEnd()); 368 }, 369 370 retainedSize: function() 371 { 372 var snapshot = this._snapshot; 373 return snapshot._nodes[this.nodeIndex + snapshot._nodeRetainedSizeOffset]; 374 }, 375 376 retainers: function() 377 { 378 return new WebInspector.HeapSnapshotRetainerEdgeIterator(this._snapshot.createRetainingEdge(this.nodeIndex, 0)); 379 }, 380 381 selfSize: function() 382 { 383 var snapshot = this._snapshot; 384 return snapshot._nodes[this.nodeIndex + snapshot._nodeSelfSizeOffset]; 385 }, 386 387 type: function() 388 { 389 return this._snapshot._nodeTypes[this._type()]; 390 }, 391 392 serialize: function() 393 { 394 return { 395 id: this.id(), 396 name: this.name(), 397 distance: this.distance(), 398 nodeIndex: this.nodeIndex, 399 retainedSize: this.retainedSize(), 400 selfSize: this.selfSize(), 401 type: this.type(), 402 }; 403 }, 404 405 _name: function() 406 { 407 var snapshot = this._snapshot; 408 return snapshot._nodes[this.nodeIndex + snapshot._nodeNameOffset]; 409 }, 410 411 _edgeIndexesStart: function() 412 { 413 return this._snapshot._firstEdgeIndexes[this._ordinal()]; 414 }, 415 416 _edgeIndexesEnd: function() 417 { 418 return this._snapshot._firstEdgeIndexes[this._ordinal() + 1]; 419 }, 420 421 _ordinal: function() 422 { 423 return this.nodeIndex / this._snapshot._nodeFieldCount; 424 }, 425 426 _nextNodeIndex: function() 427 { 428 return this.nodeIndex + this._snapshot._nodeFieldCount; 429 }, 430 431 _type: function() 432 { 433 var snapshot = this._snapshot; 434 return snapshot._nodes[this.nodeIndex + snapshot._nodeTypeOffset]; 435 } 436 }; 437 438 /** 439 * @constructor 440 */ 441 WebInspector.HeapSnapshotNodeIterator = function(node) 442 { 443 this.node = node; 444 this._nodesLength = node._snapshot._nodes.length; 445 } 446 447 WebInspector.HeapSnapshotNodeIterator.prototype = { 448 rewind: function() 449 { 450 this.node.nodeIndex = this.node._firstNodeIndex; 451 }, 452 453 hasNext: function() 454 { 455 return this.node.nodeIndex < this._nodesLength; 456 }, 457 458 index: function() 459 { 460 return this.node.nodeIndex; 461 }, 462 463 setIndex: function(newIndex) 464 { 465 this.node.nodeIndex = newIndex; 466 }, 467 468 item: function() 469 { 470 return this.node; 471 }, 472 473 next: function() 474 { 475 this.node.nodeIndex = this.node._nextNodeIndex(); 476 } 477 } 478 479 /** 480 * @constructor 481 */ 482 WebInspector.HeapSnapshot = function(profile) 483 { 484 this.uid = profile.snapshot.uid; 485 this._nodes = profile.nodes; 486 this._containmentEdges = profile.edges; 487 /** @type{HeapSnapshotMetainfo} */ 488 this._metaNode = profile.snapshot.meta; 489 this._strings = profile.strings; 490 491 this._noDistance = -5; 492 this._rootNodeIndex = 0; 493 if (profile.snapshot.root_index) 494 this._rootNodeIndex = profile.snapshot.root_index; 495 496 this._snapshotDiffs = {}; 497 this._aggregatesForDiff = null; 498 499 this._init(); 500 } 501 502 /** 503 * @constructor 504 */ 505 function HeapSnapshotMetainfo() 506 { 507 // New format. 508 this.node_fields = []; 509 this.node_types = []; 510 this.edge_fields = []; 511 this.edge_types = []; 512 this.type_strings = {}; 513 514 // Old format. 515 this.fields = []; 516 this.types = []; 517 } 518 519 /** 520 * @constructor 521 */ 522 function HeapSnapshotHeader() 523 { 524 // New format. 525 this.title = ""; 526 this.uid = 0; 527 this.meta = new HeapSnapshotMetainfo(); 528 this.node_count = 0; 529 this.edge_count = 0; 530 } 531 532 WebInspector.HeapSnapshot.prototype = { 533 _init: function() 534 { 535 var meta = this._metaNode; 536 537 this._nodeTypeOffset = meta.node_fields.indexOf("type"); 538 this._nodeNameOffset = meta.node_fields.indexOf("name"); 539 this._nodeIdOffset = meta.node_fields.indexOf("id"); 540 this._nodeSelfSizeOffset = meta.node_fields.indexOf("self_size"); 541 this._nodeEdgeCountOffset = meta.node_fields.indexOf("edge_count"); 542 this._nodeFieldCount = meta.node_fields.length; 543 544 this._nodeTypes = meta.node_types[this._nodeTypeOffset]; 545 this._nodeHiddenType = this._nodeTypes.indexOf("hidden"); 546 this._nodeObjectType = this._nodeTypes.indexOf("object"); 547 this._nodeNativeType = this._nodeTypes.indexOf("native"); 548 this._nodeCodeType = this._nodeTypes.indexOf("code"); 549 this._nodeSyntheticType = this._nodeTypes.indexOf("synthetic"); 550 551 this._edgeFieldsCount = meta.edge_fields.length; 552 this._edgeTypeOffset = meta.edge_fields.indexOf("type"); 553 this._edgeNameOffset = meta.edge_fields.indexOf("name_or_index"); 554 this._edgeToNodeOffset = meta.edge_fields.indexOf("to_node"); 555 556 this._edgeTypes = meta.edge_types[this._edgeTypeOffset]; 557 this._edgeTypes.push("invisible"); 558 this._edgeElementType = this._edgeTypes.indexOf("element"); 559 this._edgeHiddenType = this._edgeTypes.indexOf("hidden"); 560 this._edgeInternalType = this._edgeTypes.indexOf("internal"); 561 this._edgeShortcutType = this._edgeTypes.indexOf("shortcut"); 562 this._edgeWeakType = this._edgeTypes.indexOf("weak"); 563 this._edgeInvisibleType = this._edgeTypes.indexOf("invisible"); 564 565 this.nodeCount = this._nodes.length / this._nodeFieldCount; 566 this._edgeCount = this._containmentEdges.length / this._edgeFieldsCount; 567 568 this._buildEdgeIndexes(); 569 this._markInvisibleEdges(); 570 this._buildRetainers(); 571 this._calculateFlags(); 572 this._calculateDistances(); 573 var result = this._buildPostOrderIndex(); 574 // Actually it is array that maps node ordinal number to dominator node ordinal number. 575 this._dominatorsTree = this._buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex); 576 this._calculateRetainedSizes(result.postOrderIndex2NodeOrdinal); 577 this._buildDominatedNodes(); 578 }, 579 580 _buildEdgeIndexes: function() 581 { 582 var nodes = this._nodes; 583 var nodeCount = this.nodeCount; 584 var firstEdgeIndexes = this._firstEdgeIndexes = new Uint32Array(nodeCount + 1); 585 var nodeFieldCount = this._nodeFieldCount; 586 var edgeFieldsCount = this._edgeFieldsCount; 587 var nodeEdgeCountOffset = this._nodeEdgeCountOffset; 588 firstEdgeIndexes[nodeCount] = this._containmentEdges.length; 589 for (var nodeOrdinal = 0, edgeIndex = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) { 590 firstEdgeIndexes[nodeOrdinal] = edgeIndex; 591 edgeIndex += nodes[nodeOrdinal * nodeFieldCount + nodeEdgeCountOffset] * edgeFieldsCount; 592 } 593 }, 594 595 _buildRetainers: function() 596 { 597 var retainingNodes = this._retainingNodes = new Uint32Array(this._edgeCount); 598 var retainingEdges = this._retainingEdges = new Uint32Array(this._edgeCount); 599 // Index of the first retainer in the _retainingNodes and _retainingEdges 600 // arrays. Addressed by retained node index. 601 var firstRetainerIndex = this._firstRetainerIndex = new Uint32Array(this.nodeCount + 1); 602 603 var containmentEdges = this._containmentEdges; 604 var edgeFieldsCount = this._edgeFieldsCount; 605 var nodeFieldCount = this._nodeFieldCount; 606 var edgeToNodeOffset = this._edgeToNodeOffset; 607 var nodes = this._nodes; 608 var firstEdgeIndexes = this._firstEdgeIndexes; 609 var nodeCount = this.nodeCount; 610 611 for (var toNodeFieldIndex = edgeToNodeOffset, l = containmentEdges.length; toNodeFieldIndex < l; toNodeFieldIndex += edgeFieldsCount) { 612 var toNodeIndex = containmentEdges[toNodeFieldIndex]; 613 if (toNodeIndex % nodeFieldCount) 614 throw new Error("Invalid toNodeIndex " + toNodeIndex); 615 ++firstRetainerIndex[toNodeIndex / nodeFieldCount]; 616 } 617 for (var i = 0, firstUnusedRetainerSlot = 0; i < nodeCount; i++) { 618 var retainersCount = firstRetainerIndex[i]; 619 firstRetainerIndex[i] = firstUnusedRetainerSlot; 620 retainingNodes[firstUnusedRetainerSlot] = retainersCount; 621 firstUnusedRetainerSlot += retainersCount; 622 } 623 firstRetainerIndex[nodeCount] = retainingNodes.length; 624 625 var nextNodeFirstEdgeIndex = firstEdgeIndexes[0]; 626 for (var srcNodeOrdinal = 0; srcNodeOrdinal < nodeCount; ++srcNodeOrdinal) { 627 var firstEdgeIndex = nextNodeFirstEdgeIndex; 628 nextNodeFirstEdgeIndex = firstEdgeIndexes[srcNodeOrdinal + 1]; 629 var srcNodeIndex = srcNodeOrdinal * nodeFieldCount; 630 for (var edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += edgeFieldsCount) { 631 var toNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; 632 if (toNodeIndex % nodeFieldCount) 633 throw new Error("Invalid toNodeIndex " + toNodeIndex); 634 var firstRetainerSlotIndex = firstRetainerIndex[toNodeIndex / nodeFieldCount]; 635 var nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--retainingNodes[firstRetainerSlotIndex]); 636 retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex; 637 retainingEdges[nextUnusedRetainerSlotIndex] = edgeIndex; 638 } 639 } 640 }, 641 642 /** 643 * @param {number=} nodeIndex 644 */ 645 createNode: function(nodeIndex) 646 { 647 throw new Error("Not implemented"); 648 }, 649 650 createEdge: function(edges, edgeIndex) 651 { 652 throw new Error("Not implemented"); 653 }, 654 655 createRetainingEdge: function(retainedNodeIndex, retainerIndex) 656 { 657 throw new Error("Not implemented"); 658 }, 659 660 dispose: function() 661 { 662 delete this._nodes; 663 delete this._strings; 664 delete this._retainingEdges; 665 delete this._retainingNodes; 666 delete this._firstRetainerIndex; 667 if (this._aggregates) { 668 delete this._aggregates; 669 delete this._aggregatesSortedFlags; 670 } 671 delete this._dominatedNodes; 672 delete this._firstDominatedNodeIndex; 673 delete this._nodeDistances; 674 delete this._dominatorsTree; 675 }, 676 677 _allNodes: function() 678 { 679 return new WebInspector.HeapSnapshotNodeIterator(this.rootNode()); 680 }, 681 682 rootNode: function() 683 { 684 return this.createNode(this._rootNodeIndex); 685 }, 686 687 get rootNodeIndex() 688 { 689 return this._rootNodeIndex; 690 }, 691 692 get totalSize() 693 { 694 return this.rootNode().retainedSize(); 695 }, 696 697 _getDominatedIndex: function(nodeIndex) 698 { 699 if (nodeIndex % this._nodeFieldCount) 700 throw new Error("Invalid nodeIndex: " + nodeIndex); 701 return this._firstDominatedNodeIndex[nodeIndex / this._nodeFieldCount]; 702 }, 703 704 _dominatedNodesOfNode: function(node) 705 { 706 var dominatedIndexFrom = this._getDominatedIndex(node.nodeIndex); 707 var dominatedIndexTo = this._getDominatedIndex(node._nextNodeIndex()); 708 return new WebInspector.HeapSnapshotArraySlice(this._dominatedNodes, dominatedIndexFrom, dominatedIndexTo); 709 }, 710 711 /** 712 * @param {boolean} sortedIndexes 713 * @param {string} key 714 * @param {string=} filterString 715 */ 716 aggregates: function(sortedIndexes, key, filterString) 717 { 718 if (!this._aggregates) { 719 this._aggregates = {}; 720 this._aggregatesSortedFlags = {}; 721 } 722 723 var aggregatesByClassName = this._aggregates[key]; 724 if (aggregatesByClassName) { 725 if (sortedIndexes && !this._aggregatesSortedFlags[key]) { 726 this._sortAggregateIndexes(aggregatesByClassName); 727 this._aggregatesSortedFlags[key] = sortedIndexes; 728 } 729 return aggregatesByClassName; 730 } 731 732 var filter; 733 if (filterString) 734 filter = this._parseFilter(filterString); 735 736 var aggregates = this._buildAggregates(filter); 737 this._calculateClassesRetainedSize(aggregates.aggregatesByClassIndex, filter); 738 aggregatesByClassName = aggregates.aggregatesByClassName; 739 740 if (sortedIndexes) 741 this._sortAggregateIndexes(aggregatesByClassName); 742 743 this._aggregatesSortedFlags[key] = sortedIndexes; 744 this._aggregates[key] = aggregatesByClassName; 745 746 return aggregatesByClassName; 747 }, 748 749 aggregatesForDiff: function() 750 { 751 if (this._aggregatesForDiff) 752 return this._aggregatesForDiff; 753 754 var aggregatesByClassName = this.aggregates(true, "allObjects"); 755 this._aggregatesForDiff = {}; 756 757 var node = this.createNode(); 758 for (var className in aggregatesByClassName) { 759 var aggregate = aggregatesByClassName[className]; 760 var indexes = aggregate.idxs; 761 var ids = new Array(indexes.length); 762 var selfSizes = new Array(indexes.length); 763 for (var i = 0; i < indexes.length; i++) { 764 node.nodeIndex = indexes[i]; 765 ids[i] = node.id(); 766 selfSizes[i] = node.selfSize(); 767 } 768 769 this._aggregatesForDiff[className] = { 770 indexes: indexes, 771 ids: ids, 772 selfSizes: selfSizes 773 }; 774 } 775 return this._aggregatesForDiff; 776 }, 777 778 /** 779 * @param {!WebInspector.HeapSnapshotNode} node 780 * @return {!boolean} 781 */ 782 _isUserRoot: function(node) 783 { 784 return true; 785 }, 786 787 /** 788 * @param {function(!WebInspector.HeapSnapshotNode)} action 789 * @param {boolean=} userRootsOnly 790 */ 791 forEachRoot: function(action, userRootsOnly) 792 { 793 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { 794 var node = iter.edge.node(); 795 if (!userRootsOnly || this._isUserRoot(node)) 796 action(node); 797 } 798 }, 799 800 _calculateDistances: function() 801 { 802 var nodeFieldCount = this._nodeFieldCount; 803 var nodeCount = this.nodeCount; 804 var distances = new Int32Array(nodeCount); 805 var noDistance = this._noDistance; 806 for (var i = 0; i < nodeCount; ++i) 807 distances[i] = noDistance; 808 809 var nodesToVisit = new Uint32Array(this.nodeCount); 810 var nodesToVisitLength = 0; 811 812 /** 813 * @param {!WebInspector.HeapSnapshotNode} node 814 */ 815 function enqueueNode(node) 816 { 817 var ordinal = node._ordinal(); 818 if (distances[ordinal] !== noDistance) 819 return; 820 distances[ordinal] = 0; 821 nodesToVisit[nodesToVisitLength++] = node.nodeIndex; 822 } 823 824 this.forEachRoot(enqueueNode, true); 825 this._bfs(nodesToVisit, nodesToVisitLength, distances); 826 827 // bfs for the rest of objects 828 nodesToVisitLength = 0; 829 this.forEachRoot(enqueueNode); 830 this._bfs(nodesToVisit, nodesToVisitLength, distances); 831 832 this._nodeDistances = distances; 833 }, 834 835 /** 836 * @param {!Uint32Array} nodesToVisit 837 * @param {!number} nodesToVisitLength 838 * @param {!Int32Array} distances 839 */ 840 _bfs: function(nodesToVisit, nodesToVisitLength, distances) 841 { 842 // Preload fields into local variables for better performance. 843 var edgeFieldsCount = this._edgeFieldsCount; 844 var nodeFieldCount = this._nodeFieldCount; 845 var containmentEdges = this._containmentEdges; 846 var firstEdgeIndexes = this._firstEdgeIndexes; 847 var edgeToNodeOffset = this._edgeToNodeOffset; 848 var edgeTypeOffset = this._edgeTypeOffset; 849 var nodes = this._nodes; 850 var nodeCount = this.nodeCount; 851 var containmentEdgesLength = containmentEdges.length; 852 var edgeWeakType = this._edgeWeakType; 853 var noDistance = this._noDistance; 854 855 var index = 0; 856 while (index < nodesToVisitLength) { 857 var nodeIndex = nodesToVisit[index++]; // shift generates too much garbage. 858 var nodeOrdinal = nodeIndex / nodeFieldCount; 859 var distance = distances[nodeOrdinal] + 1; 860 var firstEdgeIndex = firstEdgeIndexes[nodeOrdinal]; 861 var edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; 862 for (var edgeIndex = firstEdgeIndex; edgeIndex < edgesEnd; edgeIndex += edgeFieldsCount) { 863 var edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; 864 if (edgeType == edgeWeakType) 865 continue; 866 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; 867 var childNodeOrdinal = childNodeIndex / nodeFieldCount; 868 if (distances[childNodeOrdinal] !== noDistance) 869 continue; 870 distances[childNodeOrdinal] = distance; 871 nodesToVisit[nodesToVisitLength++] = childNodeIndex; 872 } 873 } 874 if (nodesToVisitLength > nodeCount) 875 throw new Error("BFS failed. Nodes to visit (" + nodesToVisitLength + ") is more than nodes count (" + nodeCount + ")"); 876 }, 877 878 _buildAggregates: function(filter) 879 { 880 var aggregates = {}; 881 var aggregatesByClassName = {}; 882 var classIndexes = []; 883 var nodes = this._nodes; 884 var mapAndFlag = this.userObjectsMapAndFlag(); 885 var flags = mapAndFlag ? mapAndFlag.map : null; 886 var flag = mapAndFlag ? mapAndFlag.flag : 0; 887 var nodesLength = nodes.length; 888 var nodeNativeType = this._nodeNativeType; 889 var nodeFieldCount = this._nodeFieldCount; 890 var selfSizeOffset = this._nodeSelfSizeOffset; 891 var nodeTypeOffset = this._nodeTypeOffset; 892 var node = this.rootNode(); 893 var nodeDistances = this._nodeDistances; 894 895 for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { 896 var nodeOrdinal = nodeIndex / nodeFieldCount; 897 if (flags && !(flags[nodeOrdinal] & flag)) 898 continue; 899 node.nodeIndex = nodeIndex; 900 if (filter && !filter(node)) 901 continue; 902 var selfSize = nodes[nodeIndex + selfSizeOffset]; 903 if (!selfSize && nodes[nodeIndex + nodeTypeOffset] !== nodeNativeType) 904 continue; 905 var classIndex = node.classIndex(); 906 if (!(classIndex in aggregates)) { 907 var nodeType = node.type(); 908 var nameMatters = nodeType === "object" || nodeType === "native"; 909 var value = { 910 count: 1, 911 distance: nodeDistances[nodeOrdinal], 912 self: selfSize, 913 maxRet: 0, 914 type: nodeType, 915 name: nameMatters ? node.name() : null, 916 idxs: [nodeIndex] 917 }; 918 aggregates[classIndex] = value; 919 classIndexes.push(classIndex); 920 aggregatesByClassName[node.className()] = value; 921 } else { 922 var clss = aggregates[classIndex]; 923 clss.distance = Math.min(clss.distance, nodeDistances[nodeOrdinal]); 924 ++clss.count; 925 clss.self += selfSize; 926 clss.idxs.push(nodeIndex); 927 } 928 } 929 930 // Shave off provisionally allocated space. 931 for (var i = 0, l = classIndexes.length; i < l; ++i) { 932 var classIndex = classIndexes[i]; 933 aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice(); 934 } 935 return {aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates}; 936 }, 937 938 _calculateClassesRetainedSize: function(aggregates, filter) 939 { 940 var rootNodeIndex = this._rootNodeIndex; 941 var node = this.createNode(rootNodeIndex); 942 var list = [rootNodeIndex]; 943 var sizes = [-1]; 944 var classes = []; 945 var seenClassNameIndexes = {}; 946 var nodeFieldCount = this._nodeFieldCount; 947 var nodeTypeOffset = this._nodeTypeOffset; 948 var nodeNativeType = this._nodeNativeType; 949 var dominatedNodes = this._dominatedNodes; 950 var nodes = this._nodes; 951 var mapAndFlag = this.userObjectsMapAndFlag(); 952 var flags = mapAndFlag ? mapAndFlag.map : null; 953 var flag = mapAndFlag ? mapAndFlag.flag : 0; 954 var firstDominatedNodeIndex = this._firstDominatedNodeIndex; 955 956 while (list.length) { 957 var nodeIndex = list.pop(); 958 node.nodeIndex = nodeIndex; 959 var classIndex = node.classIndex(); 960 var seen = !!seenClassNameIndexes[classIndex]; 961 var nodeOrdinal = nodeIndex / nodeFieldCount; 962 var dominatedIndexFrom = firstDominatedNodeIndex[nodeOrdinal]; 963 var dominatedIndexTo = firstDominatedNodeIndex[nodeOrdinal + 1]; 964 965 if (!seen && 966 (!flags || (flags[nodeOrdinal] & flag)) && 967 (!filter || filter(node)) && 968 (node.selfSize() || nodes[nodeIndex + nodeTypeOffset] === nodeNativeType) 969 ) { 970 aggregates[classIndex].maxRet += node.retainedSize(); 971 if (dominatedIndexFrom !== dominatedIndexTo) { 972 seenClassNameIndexes[classIndex] = true; 973 sizes.push(list.length); 974 classes.push(classIndex); 975 } 976 } 977 for (var i = dominatedIndexFrom; i < dominatedIndexTo; i++) 978 list.push(dominatedNodes[i]); 979 980 var l = list.length; 981 while (sizes[sizes.length - 1] === l) { 982 sizes.pop(); 983 classIndex = classes.pop(); 984 seenClassNameIndexes[classIndex] = false; 985 } 986 } 987 }, 988 989 _sortAggregateIndexes: function(aggregates) 990 { 991 var nodeA = this.createNode(); 992 var nodeB = this.createNode(); 993 for (var clss in aggregates) 994 aggregates[clss].idxs.sort( 995 function(idxA, idxB) { 996 nodeA.nodeIndex = idxA; 997 nodeB.nodeIndex = idxB; 998 return nodeA.id() < nodeB.id() ? -1 : 1; 999 }); 1000 }, 1001 1002 _buildPostOrderIndex: function() 1003 { 1004 var nodeFieldCount = this._nodeFieldCount; 1005 var nodes = this._nodes; 1006 var nodeCount = this.nodeCount; 1007 var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; 1008 1009 var edgeFieldsCount = this._edgeFieldsCount; 1010 var edgeTypeOffset = this._edgeTypeOffset; 1011 var edgeToNodeOffset = this._edgeToNodeOffset; 1012 var edgeShortcutType = this._edgeShortcutType; 1013 var firstEdgeIndexes = this._firstEdgeIndexes; 1014 var containmentEdges = this._containmentEdges; 1015 var containmentEdgesLength = this._containmentEdges.length; 1016 1017 var mapAndFlag = this.userObjectsMapAndFlag(); 1018 var flags = mapAndFlag ? mapAndFlag.map : null; 1019 var flag = mapAndFlag ? mapAndFlag.flag : 0; 1020 1021 var nodesToVisit = new Uint32Array(nodeCount); 1022 var postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount); 1023 var nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount); 1024 var painted = new Uint8Array(nodeCount); 1025 var nodesToVisitLength = 0; 1026 var postOrderIndex = 0; 1027 var grey = 1; 1028 var black = 2; 1029 1030 nodesToVisit[nodesToVisitLength++] = rootNodeOrdinal; 1031 painted[rootNodeOrdinal] = grey; 1032 1033 while (nodesToVisitLength) { 1034 var nodeOrdinal = nodesToVisit[nodesToVisitLength - 1]; 1035 1036 if (painted[nodeOrdinal] === grey) { 1037 painted[nodeOrdinal] = black; 1038 var nodeFlag = !flags || (flags[nodeOrdinal] & flag); 1039 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; 1040 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; 1041 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { 1042 if (nodeOrdinal !== rootNodeOrdinal && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType) 1043 continue; 1044 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; 1045 var childNodeOrdinal = childNodeIndex / nodeFieldCount; 1046 var childNodeFlag = !flags || (flags[childNodeOrdinal] & flag); 1047 // We are skipping the edges from non-page-owned nodes to page-owned nodes. 1048 // Otherwise the dominators for the objects that also were retained by debugger would be affected. 1049 if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag) 1050 continue; 1051 if (!painted[childNodeOrdinal]) { 1052 painted[childNodeOrdinal] = grey; 1053 nodesToVisit[nodesToVisitLength++] = childNodeOrdinal; 1054 } 1055 } 1056 } else { 1057 nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex; 1058 postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal; 1059 --nodesToVisitLength; 1060 } 1061 } 1062 1063 if (postOrderIndex !== nodeCount) { 1064 console.log("Error: Corrupted snapshot. " + (nodeCount - postOrderIndex) + " nodes are unreachable from the root:"); 1065 var dumpNode = this.rootNode(); 1066 for (var i = 0; i < nodeCount; ++i) { 1067 if (painted[i] !== black) { 1068 // Fix it by giving the node a postorder index anyway. 1069 nodeOrdinal2PostOrderIndex[i] = postOrderIndex; 1070 postOrderIndex2NodeOrdinal[postOrderIndex++] = i; 1071 dumpNode.nodeIndex = i * nodeFieldCount; 1072 console.log(JSON.stringify(dumpNode.serialize())); 1073 for (var retainers = dumpNode.retainers(); retainers.hasNext(); retainers = retainers.item().node().retainers()) 1074 console.log(" edgeName: " + retainers.item().name() + " nodeClassName: " + retainers.item().node().className()); 1075 } 1076 } 1077 } 1078 1079 return {postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex}; 1080 }, 1081 1082 // The algorithm is based on the article: 1083 // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm" 1084 // Softw. Pract. Exper. 4 (2001), pp. 1-10. 1085 /** 1086 * @param {Array.<number>} postOrderIndex2NodeOrdinal 1087 * @param {Array.<number>} nodeOrdinal2PostOrderIndex 1088 */ 1089 _buildDominatorTree: function(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex) 1090 { 1091 var nodeFieldCount = this._nodeFieldCount; 1092 var nodes = this._nodes; 1093 var firstRetainerIndex = this._firstRetainerIndex; 1094 var retainingNodes = this._retainingNodes; 1095 var retainingEdges = this._retainingEdges; 1096 var edgeFieldsCount = this._edgeFieldsCount; 1097 var edgeTypeOffset = this._edgeTypeOffset; 1098 var edgeToNodeOffset = this._edgeToNodeOffset; 1099 var edgeShortcutType = this._edgeShortcutType; 1100 var firstEdgeIndexes = this._firstEdgeIndexes; 1101 var containmentEdges = this._containmentEdges; 1102 var containmentEdgesLength = this._containmentEdges.length; 1103 var rootNodeIndex = this._rootNodeIndex; 1104 1105 var mapAndFlag = this.userObjectsMapAndFlag(); 1106 var flags = mapAndFlag ? mapAndFlag.map : null; 1107 var flag = mapAndFlag ? mapAndFlag.flag : 0; 1108 1109 var nodesCount = postOrderIndex2NodeOrdinal.length; 1110 var rootPostOrderedIndex = nodesCount - 1; 1111 var noEntry = nodesCount; 1112 var dominators = new Uint32Array(nodesCount); 1113 for (var i = 0; i < rootPostOrderedIndex; ++i) 1114 dominators[i] = noEntry; 1115 dominators[rootPostOrderedIndex] = rootPostOrderedIndex; 1116 1117 // The affected array is used to mark entries which dominators 1118 // have to be racalculated because of changes in their retainers. 1119 var affected = new Uint8Array(nodesCount); 1120 var nodeOrdinal; 1121 1122 { // Mark the root direct children as affected. 1123 nodeOrdinal = this._rootNodeIndex / nodeFieldCount; 1124 var beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset; 1125 var endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1]; 1126 for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex; 1127 toNodeFieldIndex < endEdgeToNodeFieldIndex; 1128 toNodeFieldIndex += edgeFieldsCount) { 1129 var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount; 1130 affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1; 1131 } 1132 } 1133 1134 var changed = true; 1135 while (changed) { 1136 changed = false; 1137 for (var postOrderIndex = rootPostOrderedIndex - 1; postOrderIndex >= 0; --postOrderIndex) { 1138 if (affected[postOrderIndex] === 0) 1139 continue; 1140 affected[postOrderIndex] = 0; 1141 // If dominator of the entry has already been set to root, 1142 // then it can't propagate any further. 1143 if (dominators[postOrderIndex] === rootPostOrderedIndex) 1144 continue; 1145 nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; 1146 var nodeFlag = !flags || (flags[nodeOrdinal] & flag); 1147 var newDominatorIndex = noEntry; 1148 var beginRetainerIndex = firstRetainerIndex[nodeOrdinal]; 1149 var endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1]; 1150 for (var retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) { 1151 var retainerEdgeIndex = retainingEdges[retainerIndex]; 1152 var retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset]; 1153 var retainerNodeIndex = retainingNodes[retainerIndex]; 1154 if (retainerNodeIndex !== rootNodeIndex && retainerEdgeType === edgeShortcutType) 1155 continue; 1156 var retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount; 1157 var retainerNodeFlag = !flags || (flags[retainerNodeOrdinal] & flag); 1158 // We are skipping the edges from non-page-owned nodes to page-owned nodes. 1159 // Otherwise the dominators for the objects that also were retained by debugger would be affected. 1160 if (retainerNodeIndex !== rootNodeIndex && nodeFlag && !retainerNodeFlag) 1161 continue; 1162 var retanerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal]; 1163 if (dominators[retanerPostOrderIndex] !== noEntry) { 1164 if (newDominatorIndex === noEntry) 1165 newDominatorIndex = retanerPostOrderIndex; 1166 else { 1167 while (retanerPostOrderIndex !== newDominatorIndex) { 1168 while (retanerPostOrderIndex < newDominatorIndex) 1169 retanerPostOrderIndex = dominators[retanerPostOrderIndex]; 1170 while (newDominatorIndex < retanerPostOrderIndex) 1171 newDominatorIndex = dominators[newDominatorIndex]; 1172 } 1173 } 1174 // If idom has already reached the root, it doesn't make sense 1175 // to check other retainers. 1176 if (newDominatorIndex === rootPostOrderedIndex) 1177 break; 1178 } 1179 } 1180 if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) { 1181 dominators[postOrderIndex] = newDominatorIndex; 1182 changed = true; 1183 nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; 1184 beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset; 1185 endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1]; 1186 for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex; 1187 toNodeFieldIndex < endEdgeToNodeFieldIndex; 1188 toNodeFieldIndex += edgeFieldsCount) { 1189 var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount; 1190 affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1; 1191 } 1192 } 1193 } 1194 } 1195 1196 var dominatorsTree = new Uint32Array(nodesCount); 1197 for (var postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) { 1198 nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; 1199 dominatorsTree[nodeOrdinal] = postOrderIndex2NodeOrdinal[dominators[postOrderIndex]]; 1200 } 1201 return dominatorsTree; 1202 }, 1203 1204 _calculateRetainedSizes: function(postOrderIndex2NodeOrdinal) 1205 { 1206 var nodeCount = this.nodeCount; 1207 var nodes = this._nodes; 1208 var nodeSelfSizeOffset = this._nodeSelfSizeOffset; 1209 var nodeFieldCount = this._nodeFieldCount; 1210 var dominatorsTree = this._dominatorsTree; 1211 // Reuse now unused edge_count field to store retained size. 1212 var nodeRetainedSizeOffset = this._nodeRetainedSizeOffset = this._nodeEdgeCountOffset; 1213 delete this._nodeEdgeCountOffset; 1214 1215 for (var nodeIndex = 0, l = nodes.length; nodeIndex < l; nodeIndex += nodeFieldCount) 1216 nodes[nodeIndex + nodeRetainedSizeOffset] = nodes[nodeIndex + nodeSelfSizeOffset]; 1217 1218 // Propagate retained sizes for each node excluding root. 1219 for (var postOrderIndex = 0; postOrderIndex < nodeCount - 1; ++postOrderIndex) { 1220 var nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; 1221 var nodeIndex = nodeOrdinal * nodeFieldCount; 1222 var dominatorIndex = dominatorsTree[nodeOrdinal] * nodeFieldCount; 1223 nodes[dominatorIndex + nodeRetainedSizeOffset] += nodes[nodeIndex + nodeRetainedSizeOffset]; 1224 } 1225 }, 1226 1227 _buildDominatedNodes: function() 1228 { 1229 // Builds up two arrays: 1230 // - "dominatedNodes" is a continuous array, where each node owns an 1231 // interval (can be empty) with corresponding dominated nodes. 1232 // - "indexArray" is an array of indexes in the "dominatedNodes" 1233 // with the same positions as in the _nodeIndex. 1234 var indexArray = this._firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1); 1235 // All nodes except the root have dominators. 1236 var dominatedNodes = this._dominatedNodes = new Uint32Array(this.nodeCount - 1); 1237 1238 // Count the number of dominated nodes for each node. Skip the root (node at 1239 // index 0) as it is the only node that dominates itself. 1240 var nodeFieldCount = this._nodeFieldCount; 1241 var dominatorsTree = this._dominatorsTree; 1242 1243 var fromNodeOrdinal = 0; 1244 var toNodeOrdinal = this.nodeCount; 1245 var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; 1246 if (rootNodeOrdinal === fromNodeOrdinal) 1247 fromNodeOrdinal = 1; 1248 else if (rootNodeOrdinal === toNodeOrdinal - 1) 1249 toNodeOrdinal = toNodeOrdinal - 1; 1250 else 1251 throw new Error("Root node is expected to be either first or last"); 1252 for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) 1253 ++indexArray[dominatorsTree[nodeOrdinal]]; 1254 // Put in the first slot of each dominatedNodes slice the count of entries 1255 // that will be filled. 1256 var firstDominatedNodeIndex = 0; 1257 for (var i = 0, l = this.nodeCount; i < l; ++i) { 1258 var dominatedCount = dominatedNodes[firstDominatedNodeIndex] = indexArray[i]; 1259 indexArray[i] = firstDominatedNodeIndex; 1260 firstDominatedNodeIndex += dominatedCount; 1261 } 1262 indexArray[this.nodeCount] = dominatedNodes.length; 1263 // Fill up the dominatedNodes array with indexes of dominated nodes. Skip the root (node at 1264 // index 0) as it is the only node that dominates itself. 1265 for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) { 1266 var dominatorOrdinal = dominatorsTree[nodeOrdinal]; 1267 var dominatedRefIndex = indexArray[dominatorOrdinal]; 1268 dominatedRefIndex += (--dominatedNodes[dominatedRefIndex]); 1269 dominatedNodes[dominatedRefIndex] = nodeOrdinal * nodeFieldCount; 1270 } 1271 }, 1272 1273 _markInvisibleEdges: function() 1274 { 1275 throw new Error("Not implemented"); 1276 }, 1277 1278 _calculateFlags: function() 1279 { 1280 throw new Error("Not implemented"); 1281 }, 1282 1283 userObjectsMapAndFlag: function() 1284 { 1285 throw new Error("Not implemented"); 1286 }, 1287 1288 calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates) 1289 { 1290 var snapshotDiff = this._snapshotDiffs[baseSnapshotId]; 1291 if (snapshotDiff) 1292 return snapshotDiff; 1293 snapshotDiff = {}; 1294 1295 var aggregates = this.aggregates(true, "allObjects"); 1296 for (var className in baseSnapshotAggregates) { 1297 var baseAggregate = baseSnapshotAggregates[className]; 1298 var diff = this._calculateDiffForClass(baseAggregate, aggregates[className]); 1299 if (diff) 1300 snapshotDiff[className] = diff; 1301 } 1302 var emptyBaseAggregate = { ids: [], indexes: [], selfSizes: [] }; 1303 for (var className in aggregates) { 1304 if (className in baseSnapshotAggregates) 1305 continue; 1306 snapshotDiff[className] = this._calculateDiffForClass(emptyBaseAggregate, aggregates[className]); 1307 } 1308 1309 this._snapshotDiffs[baseSnapshotId] = snapshotDiff; 1310 return snapshotDiff; 1311 }, 1312 1313 _calculateDiffForClass: function(baseAggregate, aggregate) 1314 { 1315 var baseIds = baseAggregate.ids; 1316 var baseIndexes = baseAggregate.indexes; 1317 var baseSelfSizes = baseAggregate.selfSizes; 1318 1319 var indexes = aggregate ? aggregate.idxs : []; 1320 1321 var i = 0, l = baseIds.length; 1322 var j = 0, m = indexes.length; 1323 var diff = { addedCount: 0, 1324 removedCount: 0, 1325 addedSize: 0, 1326 removedSize: 0, 1327 deletedIndexes: [], 1328 addedIndexes: [] }; 1329 1330 var nodeB = this.createNode(indexes[j]); 1331 while (i < l && j < m) { 1332 var nodeAId = baseIds[i]; 1333 if (nodeAId < nodeB.id()) { 1334 diff.deletedIndexes.push(baseIndexes[i]); 1335 diff.removedCount++; 1336 diff.removedSize += baseSelfSizes[i]; 1337 ++i; 1338 } else if (nodeAId > nodeB.id()) { // Native nodes(e.g. dom groups) may have ids less than max JS object id in the base snapshot 1339 diff.addedIndexes.push(indexes[j]); 1340 diff.addedCount++; 1341 diff.addedSize += nodeB.selfSize(); 1342 nodeB.nodeIndex = indexes[++j]; 1343 } else { // nodeAId === nodeB.id() 1344 ++i; 1345 nodeB.nodeIndex = indexes[++j]; 1346 } 1347 } 1348 while (i < l) { 1349 diff.deletedIndexes.push(baseIndexes[i]); 1350 diff.removedCount++; 1351 diff.removedSize += baseSelfSizes[i]; 1352 ++i; 1353 } 1354 while (j < m) { 1355 diff.addedIndexes.push(indexes[j]); 1356 diff.addedCount++; 1357 diff.addedSize += nodeB.selfSize(); 1358 nodeB.nodeIndex = indexes[++j]; 1359 } 1360 diff.countDelta = diff.addedCount - diff.removedCount; 1361 diff.sizeDelta = diff.addedSize - diff.removedSize; 1362 if (!diff.addedCount && !diff.removedCount) 1363 return null; 1364 return diff; 1365 }, 1366 1367 _nodeForSnapshotObjectId: function(snapshotObjectId) 1368 { 1369 for (var it = this._allNodes(); it.hasNext(); it.next()) { 1370 if (it.node.id() === snapshotObjectId) 1371 return it.node; 1372 } 1373 return null; 1374 }, 1375 1376 nodeClassName: function(snapshotObjectId) 1377 { 1378 var node = this._nodeForSnapshotObjectId(snapshotObjectId); 1379 if (node) 1380 return node.className(); 1381 return null; 1382 }, 1383 1384 dominatorIdsForNode: function(snapshotObjectId) 1385 { 1386 var node = this._nodeForSnapshotObjectId(snapshotObjectId); 1387 if (!node) 1388 return null; 1389 var result = []; 1390 while (!node.isRoot()) { 1391 result.push(node.id()); 1392 node.nodeIndex = node.dominatorIndex(); 1393 } 1394 return result; 1395 }, 1396 1397 _parseFilter: function(filter) 1398 { 1399 if (!filter) 1400 return null; 1401 var parsedFilter = eval("(function(){return " + filter + "})()"); 1402 return parsedFilter.bind(this); 1403 }, 1404 1405 createEdgesProvider: function(nodeIndex, showHiddenData) 1406 { 1407 var node = this.createNode(nodeIndex); 1408 var filter = this.containmentEdgesFilter(showHiddenData); 1409 return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges()); 1410 }, 1411 1412 createEdgesProviderForTest: function(nodeIndex, filter) 1413 { 1414 var node = this.createNode(nodeIndex); 1415 return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges()); 1416 }, 1417 1418 retainingEdgesFilter: function(showHiddenData) 1419 { 1420 return null; 1421 }, 1422 1423 containmentEdgesFilter: function(showHiddenData) 1424 { 1425 return null; 1426 }, 1427 1428 createRetainingEdgesProvider: function(nodeIndex, showHiddenData) 1429 { 1430 var node = this.createNode(nodeIndex); 1431 var filter = this.retainingEdgesFilter(showHiddenData); 1432 return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.retainers()); 1433 }, 1434 1435 createAddedNodesProvider: function(baseSnapshotId, className) 1436 { 1437 var snapshotDiff = this._snapshotDiffs[baseSnapshotId]; 1438 var diffForClass = snapshotDiff[className]; 1439 return new WebInspector.HeapSnapshotNodesProvider(this, null, diffForClass.addedIndexes); 1440 }, 1441 1442 createDeletedNodesProvider: function(nodeIndexes) 1443 { 1444 return new WebInspector.HeapSnapshotNodesProvider(this, null, nodeIndexes); 1445 }, 1446 1447 classNodesFilter: function() 1448 { 1449 return null; 1450 }, 1451 1452 createNodesProviderForClass: function(className, aggregatesKey) 1453 { 1454 return new WebInspector.HeapSnapshotNodesProvider(this, this.classNodesFilter(), this.aggregates(false, aggregatesKey)[className].idxs); 1455 }, 1456 1457 createNodesProviderForDominator: function(nodeIndex) 1458 { 1459 var node = this.createNode(nodeIndex); 1460 return new WebInspector.HeapSnapshotNodesProvider(this, null, this._dominatedNodesOfNode(node)); 1461 }, 1462 1463 updateStaticData: function() 1464 { 1465 return {nodeCount: this.nodeCount, rootNodeIndex: this._rootNodeIndex, totalSize: this.totalSize, uid: this.uid}; 1466 } 1467 }; 1468 1469 /** 1470 * @constructor 1471 * @param {Array.<number>=} unfilteredIterationOrder 1472 */ 1473 WebInspector.HeapSnapshotFilteredOrderedIterator = function(iterator, filter, unfilteredIterationOrder) 1474 { 1475 this._filter = filter; 1476 this._iterator = iterator; 1477 this._unfilteredIterationOrder = unfilteredIterationOrder; 1478 this._iterationOrder = null; 1479 this._position = 0; 1480 this._currentComparator = null; 1481 this._sortedPrefixLength = 0; 1482 } 1483 1484 WebInspector.HeapSnapshotFilteredOrderedIterator.prototype = { 1485 _createIterationOrder: function() 1486 { 1487 if (this._iterationOrder) 1488 return; 1489 if (this._unfilteredIterationOrder && !this._filter) { 1490 this._iterationOrder = this._unfilteredIterationOrder.slice(0); 1491 this._unfilteredIterationOrder = null; 1492 return; 1493 } 1494 this._iterationOrder = []; 1495 var iterator = this._iterator; 1496 if (!this._unfilteredIterationOrder && !this._filter) { 1497 for (iterator.rewind(); iterator.hasNext(); iterator.next()) 1498 this._iterationOrder.push(iterator.index()); 1499 } else if (!this._unfilteredIterationOrder) { 1500 for (iterator.rewind(); iterator.hasNext(); iterator.next()) { 1501 if (this._filter(iterator.item())) 1502 this._iterationOrder.push(iterator.index()); 1503 } 1504 } else { 1505 var order = this._unfilteredIterationOrder.constructor === Array ? 1506 this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0); 1507 for (var i = 0, l = order.length; i < l; ++i) { 1508 iterator.setIndex(order[i]); 1509 if (this._filter(iterator.item())) 1510 this._iterationOrder.push(iterator.index()); 1511 } 1512 this._unfilteredIterationOrder = null; 1513 } 1514 }, 1515 1516 rewind: function() 1517 { 1518 this._position = 0; 1519 }, 1520 1521 hasNext: function() 1522 { 1523 return this._position < this._iterationOrder.length; 1524 }, 1525 1526 isEmpty: function() 1527 { 1528 if (this._iterationOrder) 1529 return !this._iterationOrder.length; 1530 if (this._unfilteredIterationOrder && !this._filter) 1531 return !this._unfilteredIterationOrder.length; 1532 var iterator = this._iterator; 1533 if (!this._unfilteredIterationOrder && !this._filter) { 1534 iterator.rewind(); 1535 return !iterator.hasNext(); 1536 } else if (!this._unfilteredIterationOrder) { 1537 for (iterator.rewind(); iterator.hasNext(); iterator.next()) 1538 if (this._filter(iterator.item())) 1539 return false; 1540 } else { 1541 var order = this._unfilteredIterationOrder.constructor === Array ? 1542 this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0); 1543 for (var i = 0, l = order.length; i < l; ++i) { 1544 iterator.setIndex(order[i]); 1545 if (this._filter(iterator.item())) 1546 return false; 1547 } 1548 } 1549 return true; 1550 }, 1551 1552 item: function() 1553 { 1554 this._iterator.setIndex(this._iterationOrder[this._position]); 1555 return this._iterator.item(); 1556 }, 1557 1558 get length() 1559 { 1560 this._createIterationOrder(); 1561 return this._iterationOrder.length; 1562 }, 1563 1564 next: function() 1565 { 1566 ++this._position; 1567 }, 1568 1569 /** 1570 * @param {number} begin 1571 * @param {number} end 1572 */ 1573 serializeItemsRange: function(begin, end) 1574 { 1575 this._createIterationOrder(); 1576 if (begin > end) 1577 throw new Error("Start position > end position: " + begin + " > " + end); 1578 if (end >= this._iterationOrder.length) 1579 end = this._iterationOrder.length; 1580 if (this._sortedPrefixLength < end) { 1581 this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1, end - this._sortedPrefixLength); 1582 this._sortedPrefixLength = end; 1583 } 1584 1585 this._position = begin; 1586 var startPosition = this._position; 1587 var count = end - begin; 1588 var result = new Array(count); 1589 for (var i = 0 ; i < count && this.hasNext(); ++i, this.next()) 1590 result[i] = this.item().serialize(); 1591 result.length = i; 1592 result.totalLength = this._iterationOrder.length; 1593 1594 result.startPosition = startPosition; 1595 result.endPosition = this._position; 1596 return result; 1597 }, 1598 1599 sortAll: function() 1600 { 1601 this._createIterationOrder(); 1602 if (this._sortedPrefixLength === this._iterationOrder.length) 1603 return; 1604 this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1, this._iterationOrder.length); 1605 this._sortedPrefixLength = this._iterationOrder.length; 1606 }, 1607 1608 sortAndRewind: function(comparator) 1609 { 1610 this._currentComparator = comparator; 1611 this._sortedPrefixLength = 0; 1612 this.rewind(); 1613 } 1614 } 1615 1616 WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator = function(fieldNames) 1617 { 1618 return {fieldName1: fieldNames[0], ascending1: fieldNames[1], fieldName2: fieldNames[2], ascending2: fieldNames[3]}; 1619 } 1620 1621 /** 1622 * @constructor 1623 * @extends {WebInspector.HeapSnapshotFilteredOrderedIterator} 1624 */ 1625 WebInspector.HeapSnapshotEdgesProvider = function(snapshot, filter, edgesIter) 1626 { 1627 this.snapshot = snapshot; 1628 WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, edgesIter, filter); 1629 } 1630 1631 WebInspector.HeapSnapshotEdgesProvider.prototype = { 1632 sort: function(comparator, leftBound, rightBound, count) 1633 { 1634 var fieldName1 = comparator.fieldName1; 1635 var fieldName2 = comparator.fieldName2; 1636 var ascending1 = comparator.ascending1; 1637 var ascending2 = comparator.ascending2; 1638 1639 var edgeA = this._iterator.item().clone(); 1640 var edgeB = edgeA.clone(); 1641 var nodeA = this.snapshot.createNode(); 1642 var nodeB = this.snapshot.createNode(); 1643 1644 function compareEdgeFieldName(ascending, indexA, indexB) 1645 { 1646 edgeA.edgeIndex = indexA; 1647 edgeB.edgeIndex = indexB; 1648 if (edgeB.name() === "__proto__") return -1; 1649 if (edgeA.name() === "__proto__") return 1; 1650 var result = 1651 edgeA.hasStringName() === edgeB.hasStringName() ? 1652 (edgeA.name() < edgeB.name() ? -1 : (edgeA.name() > edgeB.name() ? 1 : 0)) : 1653 (edgeA.hasStringName() ? -1 : 1); 1654 return ascending ? result : -result; 1655 } 1656 1657 function compareNodeField(fieldName, ascending, indexA, indexB) 1658 { 1659 edgeA.edgeIndex = indexA; 1660 nodeA.nodeIndex = edgeA.nodeIndex(); 1661 var valueA = nodeA[fieldName](); 1662 1663 edgeB.edgeIndex = indexB; 1664 nodeB.nodeIndex = edgeB.nodeIndex(); 1665 var valueB = nodeB[fieldName](); 1666 1667 var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0); 1668 return ascending ? result : -result; 1669 } 1670 1671 function compareEdgeAndNode(indexA, indexB) { 1672 var result = compareEdgeFieldName(ascending1, indexA, indexB); 1673 if (result === 0) 1674 result = compareNodeField(fieldName2, ascending2, indexA, indexB); 1675 return result; 1676 } 1677 1678 function compareNodeAndEdge(indexA, indexB) { 1679 var result = compareNodeField(fieldName1, ascending1, indexA, indexB); 1680 if (result === 0) 1681 result = compareEdgeFieldName(ascending2, indexA, indexB); 1682 return result; 1683 } 1684 1685 function compareNodeAndNode(indexA, indexB) { 1686 var result = compareNodeField(fieldName1, ascending1, indexA, indexB); 1687 if (result === 0) 1688 result = compareNodeField(fieldName2, ascending2, indexA, indexB); 1689 return result; 1690 } 1691 1692 if (fieldName1 === "!edgeName") 1693 this._iterationOrder.sortRange(compareEdgeAndNode, leftBound, rightBound, count); 1694 else if (fieldName2 === "!edgeName") 1695 this._iterationOrder.sortRange(compareNodeAndEdge, leftBound, rightBound, count); 1696 else 1697 this._iterationOrder.sortRange(compareNodeAndNode, leftBound, rightBound, count); 1698 }, 1699 1700 __proto__: WebInspector.HeapSnapshotFilteredOrderedIterator.prototype 1701 } 1702 1703 1704 /** 1705 * @constructor 1706 * @extends {WebInspector.HeapSnapshotFilteredOrderedIterator} 1707 * @param {Array.<number>=} nodeIndexes 1708 */ 1709 WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter, nodeIndexes) 1710 { 1711 this.snapshot = snapshot; 1712 WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, snapshot._allNodes(), filter, nodeIndexes); 1713 } 1714 1715 WebInspector.HeapSnapshotNodesProvider.prototype = { 1716 nodePosition: function(snapshotObjectId) 1717 { 1718 this._createIterationOrder(); 1719 if (this.isEmpty()) 1720 return -1; 1721 this.sortAll(); 1722 1723 var node = this.snapshot.createNode(); 1724 for (var i = 0; i < this._iterationOrder.length; i++) { 1725 node.nodeIndex = this._iterationOrder[i]; 1726 if (node.id() === snapshotObjectId) 1727 return i; 1728 } 1729 return -1; 1730 }, 1731 1732 sort: function(comparator, leftBound, rightBound, count) 1733 { 1734 var fieldName1 = comparator.fieldName1; 1735 var fieldName2 = comparator.fieldName2; 1736 var ascending1 = comparator.ascending1; 1737 var ascending2 = comparator.ascending2; 1738 1739 var nodeA = this.snapshot.createNode(); 1740 var nodeB = this.snapshot.createNode(); 1741 1742 function sortByNodeField(fieldName, ascending) 1743 { 1744 var valueOrFunctionA = nodeA[fieldName]; 1745 var valueA = typeof valueOrFunctionA !== "function" ? valueOrFunctionA : valueOrFunctionA.call(nodeA); 1746 var valueOrFunctionB = nodeB[fieldName]; 1747 var valueB = typeof valueOrFunctionB !== "function" ? valueOrFunctionB : valueOrFunctionB.call(nodeB); 1748 var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0); 1749 return ascending ? result : -result; 1750 } 1751 1752 function sortByComparator(indexA, indexB) { 1753 nodeA.nodeIndex = indexA; 1754 nodeB.nodeIndex = indexB; 1755 var result = sortByNodeField(fieldName1, ascending1); 1756 if (result === 0) 1757 result = sortByNodeField(fieldName2, ascending2); 1758 return result; 1759 } 1760 1761 this._iterationOrder.sortRange(sortByComparator, leftBound, rightBound, count); 1762 }, 1763 1764 __proto__: WebInspector.HeapSnapshotFilteredOrderedIterator.prototype 1765 } 1766 1767