2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.HeapSnapshot}
34 * @param {!Object} profile
35 * @param {!WebInspector.HeapSnapshotProgress} progress
36 * @param {boolean} showHiddenData
38 WebInspector.JSHeapSnapshot = function(profile, progress, showHiddenData)
40 this._nodeFlags = { // bit flags
42 detachedDOMTreeNode: 2,
43 pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger.
45 visitedMarkerMask: 0x0ffff, // bits: 0,1111,1111,1111,1111
46 visitedMarker: 0x10000 // bits: 1,0000,0000,0000,0000
48 this._lazyStringCache = { };
49 WebInspector.HeapSnapshot.call(this, profile, progress, showHiddenData);
52 WebInspector.JSHeapSnapshot.prototype = {
54 * @param {number} nodeIndex
55 * @return {!WebInspector.JSHeapSnapshotNode}
57 createNode: function(nodeIndex)
59 return new WebInspector.JSHeapSnapshotNode(this, nodeIndex);
64 * @param {number} edgeIndex
65 * @return {!WebInspector.JSHeapSnapshotEdge}
67 createEdge: function(edgeIndex)
69 return new WebInspector.JSHeapSnapshotEdge(this, edgeIndex);
74 * @param {number} retainerIndex
75 * @return {!WebInspector.JSHeapSnapshotRetainerEdge}
77 createRetainingEdge: function(retainerIndex)
79 return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainerIndex);
84 * @return {?function(!WebInspector.JSHeapSnapshotNode):boolean}
86 classNodesFilter: function()
89 * @param {!WebInspector.JSHeapSnapshotNode} node
94 return node.isUserObject();
96 return this._showHiddenData ? null : filter;
100 * @return {function(!WebInspector.HeapSnapshotEdge):boolean}
102 containmentEdgesFilter: function()
104 var showHiddenData = this._showHiddenData;
105 function filter(edge) {
106 if (edge.isInvisible())
110 return !edge.isHidden() && !edge.node().isHidden();
116 * @return {function(!WebInspector.HeapSnapshotEdge):boolean}
118 retainingEdgesFilter: function()
120 var containmentEdgesFilter = this.containmentEdgesFilter();
121 function filter(edge)
123 return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak();
130 WebInspector.HeapSnapshot.prototype.dispose.call(this);
134 _calculateFlags: function()
136 this._flags = new Uint32Array(this.nodeCount);
137 this._markDetachedDOMTreeNodes();
138 this._markQueriableHeapObjects();
139 this._markPageOwnedNodes();
143 * @param {!WebInspector.HeapSnapshotNode} node
146 _isUserRoot: function(node)
148 return node.isUserRoot() || node.isDocumentDOMTreesRoot();
152 * @param {function(!WebInspector.HeapSnapshotNode)} action
153 * @param {boolean=} userRootsOnly
155 forEachRoot: function(action, userRootsOnly)
158 * @param {!WebInspector.HeapSnapshotNode} node
159 * @param {string} name
160 * @return {?WebInspector.HeapSnapshotNode}
162 function getChildNodeByName(node, name)
164 for (var iter = node.edges(); iter.hasNext(); iter.next()) {
165 var child = iter.edge.node();
166 if (child.name() === name)
172 var visitedNodes = {};
174 * @param {!WebInspector.HeapSnapshotNode} node
176 function doAction(node)
178 var ordinal = node._ordinal();
179 if (!visitedNodes[ordinal]) {
181 visitedNodes[ordinal] = true;
185 var gcRoots = getChildNodeByName(this.rootNode(), "(GC roots)");
190 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
191 var node = iter.edge.node();
192 if (this._isUserRoot(node))
196 for (var iter = gcRoots.edges(); iter.hasNext(); iter.next()) {
197 var subRoot = iter.edge.node();
198 for (var iter2 = subRoot.edges(); iter2.hasNext(); iter2.next())
199 doAction(iter2.edge.node());
202 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next())
203 doAction(iter.edge.node())
208 * @return {?{map: !Uint32Array, flag: number}}
210 userObjectsMapAndFlag: function()
212 return this._showHiddenData ? null : {
214 flag: this._nodeFlags.pageObject
218 _flagsOfNode: function(node)
220 return this._flags[node.nodeIndex / this._nodeFieldCount];
223 _markDetachedDOMTreeNodes: function()
225 var flag = this._nodeFlags.detachedDOMTreeNode;
226 var detachedDOMTreesRoot;
227 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
228 var node = iter.edge.node();
229 if (node.name() === "(Detached DOM trees)") {
230 detachedDOMTreesRoot = node;
235 if (!detachedDOMTreesRoot)
238 var detachedDOMTreeRE = /^Detached DOM tree/;
239 for (var iter = detachedDOMTreesRoot.edges(); iter.hasNext(); iter.next()) {
240 var node = iter.edge.node();
241 if (detachedDOMTreeRE.test(node.className())) {
242 for (var edgesIter = node.edges(); edgesIter.hasNext(); edgesIter.next())
243 this._flags[edgesIter.edge.node().nodeIndex / this._nodeFieldCount] |= flag;
248 _markQueriableHeapObjects: function()
250 // Allow runtime properties query for objects accessible from Window objects
251 // via regular properties, and for DOM wrappers. Trying to access random objects
252 // can cause a crash due to insonsistent state of internal properties of wrappers.
253 var flag = this._nodeFlags.canBeQueried;
254 var hiddenEdgeType = this._edgeHiddenType;
255 var internalEdgeType = this._edgeInternalType;
256 var invisibleEdgeType = this._edgeInvisibleType;
257 var weakEdgeType = this._edgeWeakType;
258 var edgeToNodeOffset = this._edgeToNodeOffset;
259 var edgeTypeOffset = this._edgeTypeOffset;
260 var edgeFieldsCount = this._edgeFieldsCount;
261 var containmentEdges = this._containmentEdges;
262 var nodes = this._nodes;
263 var nodeCount = this.nodeCount;
264 var nodeFieldCount = this._nodeFieldCount;
265 var firstEdgeIndexes = this._firstEdgeIndexes;
267 var flags = this._flags;
270 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
271 if (iter.edge.node().isUserRoot())
272 list.push(iter.edge.node().nodeIndex / nodeFieldCount);
275 while (list.length) {
276 var nodeOrdinal = list.pop();
277 if (flags[nodeOrdinal] & flag)
279 flags[nodeOrdinal] |= flag;
280 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
281 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
282 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
283 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
284 var childNodeOrdinal = childNodeIndex / nodeFieldCount;
285 if (flags[childNodeOrdinal] & flag)
287 var type = containmentEdges[edgeIndex + edgeTypeOffset];
288 if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || type === weakEdgeType)
290 list.push(childNodeOrdinal);
295 _markPageOwnedNodes: function()
297 var edgeShortcutType = this._edgeShortcutType;
298 var edgeElementType = this._edgeElementType;
299 var edgeToNodeOffset = this._edgeToNodeOffset;
300 var edgeTypeOffset = this._edgeTypeOffset;
301 var edgeFieldsCount = this._edgeFieldsCount;
302 var edgeWeakType = this._edgeWeakType;
303 var firstEdgeIndexes = this._firstEdgeIndexes;
304 var containmentEdges = this._containmentEdges;
305 var containmentEdgesLength = containmentEdges.length;
306 var nodes = this._nodes;
307 var nodeFieldCount = this._nodeFieldCount;
308 var nodesCount = this.nodeCount;
310 var flags = this._flags;
311 var flag = this._nodeFlags.pageObject;
312 var visitedMarker = this._nodeFlags.visitedMarker;
313 var visitedMarkerMask = this._nodeFlags.visitedMarkerMask;
314 var markerAndFlag = visitedMarker | flag;
316 var nodesToVisit = new Uint32Array(nodesCount);
317 var nodesToVisitLength = 0;
319 var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
320 var node = this.rootNode();
321 for (var edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1];
322 edgeIndex < endEdgeIndex;
323 edgeIndex += edgeFieldsCount) {
324 var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
325 var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
326 if (edgeType === edgeElementType) {
327 node.nodeIndex = nodeIndex;
328 if (!node.isDocumentDOMTreesRoot())
330 } else if (edgeType !== edgeShortcutType)
332 var nodeOrdinal = nodeIndex / nodeFieldCount;
333 nodesToVisit[nodesToVisitLength++] = nodeOrdinal;
334 flags[nodeOrdinal] |= visitedMarker;
337 while (nodesToVisitLength) {
338 var nodeOrdinal = nodesToVisit[--nodesToVisitLength];
339 flags[nodeOrdinal] |= flag;
340 flags[nodeOrdinal] &= visitedMarkerMask;
341 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
342 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
343 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
344 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
345 var childNodeOrdinal = childNodeIndex / nodeFieldCount;
346 if (flags[childNodeOrdinal] & markerAndFlag)
348 var type = containmentEdges[edgeIndex + edgeTypeOffset];
349 if (type === edgeWeakType)
351 nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
352 flags[childNodeOrdinal] |= visitedMarker;
357 _calculateStatistics: function()
359 var nodeFieldCount = this._nodeFieldCount;
360 var nodes = this._nodes;
361 var nodesLength = nodes.length;
362 var nodeTypeOffset = this._nodeTypeOffset;
363 var nodeSizeOffset = this._nodeSelfSizeOffset;;
364 var nodeNativeType = this._nodeNativeType;
365 var nodeCodeType = this._nodeCodeType;
366 var nodeConsStringType = this._nodeConsStringType;
367 var nodeSlicedStringType = this._nodeSlicedStringType;
371 var sizeJSArrays = 0;
372 var node = this.rootNode();
373 for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) {
374 node.nodeIndex = nodeIndex;
375 var nodeType = nodes[nodeIndex + nodeTypeOffset];
376 var nodeSize = nodes[nodeIndex + nodeSizeOffset];
377 if (nodeType === nodeNativeType)
378 sizeNative += nodeSize;
379 else if (nodeType === nodeCodeType)
380 sizeCode += nodeSize;
381 else if (nodeType === nodeConsStringType || nodeType === nodeSlicedStringType || node.type() === "string")
382 sizeStrings += nodeSize;
383 else if (node.name() === "Array")
384 sizeJSArrays += this._calculateArraySize(node);
386 this._statistics = new WebInspector.HeapSnapshotCommon.Statistics();
387 this._statistics.total = this.totalSize;
388 this._statistics.v8heap = this.totalSize - sizeNative;
389 this._statistics.native = sizeNative;
390 this._statistics.code = sizeCode;
391 this._statistics.jsArrays = sizeJSArrays;
392 this._statistics.strings = sizeStrings;
396 * @param {!WebInspector.HeapSnapshotNode} node
399 _calculateArraySize: function(node)
401 var size = node.selfSize();
402 var beginEdgeIndex = node._edgeIndexesStart();
403 var endEdgeIndex = node._edgeIndexesEnd();
404 var containmentEdges = this._containmentEdges;
405 var strings = this._strings;
406 var edgeToNodeOffset = this._edgeToNodeOffset;
407 var edgeTypeOffset = this._edgeTypeOffset;
408 var edgeNameOffset = this._edgeNameOffset;
409 var edgeFieldsCount = this._edgeFieldsCount;
410 var edgeInternalType = this._edgeInternalType;
411 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
412 var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
413 if (edgeType !== edgeInternalType)
415 var edgeName = strings[containmentEdges[edgeIndex + edgeNameOffset]];
416 if (edgeName !== "elements")
418 var elementsNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
419 node.nodeIndex = elementsNodeIndex;
420 if (node.retainersCount() === 1)
421 size += node.selfSize();
428 * @return {!WebInspector.HeapSnapshotCommon.Statistics}
430 getStatistics: function()
432 return this._statistics;
435 __proto__: WebInspector.HeapSnapshot.prototype
440 * @extends {WebInspector.HeapSnapshotNode}
441 * @param {!WebInspector.JSHeapSnapshot} snapshot
442 * @param {number=} nodeIndex
444 WebInspector.JSHeapSnapshotNode = function(snapshot, nodeIndex)
446 WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex)
449 WebInspector.JSHeapSnapshotNode.prototype = {
453 canBeQueried: function()
455 var flags = this._snapshot._flagsOfNode(this);
456 return !!(flags & this._snapshot._nodeFlags.canBeQueried);
462 isUserObject: function()
464 var flags = this._snapshot._flagsOfNode(this);
465 return !!(flags & this._snapshot._nodeFlags.pageObject);
473 var snapshot = this._snapshot;
474 if (this._type() === snapshot._nodeConsStringType) {
475 var string = snapshot._lazyStringCache[this.nodeIndex];
476 if (typeof string === "undefined") {
477 string = this._consStringName();
478 snapshot._lazyStringCache[this.nodeIndex] = string;
482 return WebInspector.HeapSnapshotNode.prototype.name.call(this);
485 _consStringName: function()
487 var snapshot = this._snapshot;
488 var consStringType = snapshot._nodeConsStringType;
489 var edgeInternalType = snapshot._edgeInternalType;
490 var edgeFieldsCount = snapshot._edgeFieldsCount;
491 var edgeToNodeOffset = snapshot._edgeToNodeOffset;
492 var edgeTypeOffset = snapshot._edgeTypeOffset;
493 var edgeNameOffset = snapshot._edgeNameOffset;
494 var strings = snapshot._strings;
495 var edges = snapshot._containmentEdges;
496 var firstEdgeIndexes = snapshot._firstEdgeIndexes;
497 var nodeFieldCount = snapshot._nodeFieldCount;
498 var nodeTypeOffset = snapshot._nodeTypeOffset;
499 var nodeNameOffset = snapshot._nodeNameOffset;
500 var nodes = snapshot._nodes;
502 nodesStack.push(this.nodeIndex);
505 while (nodesStack.length && name.length < 1024) {
506 var nodeIndex = nodesStack.pop();
507 if (nodes[nodeIndex + nodeTypeOffset] !== consStringType) {
508 name += strings[nodes[nodeIndex + nodeNameOffset]];
511 var nodeOrdinal = nodeIndex / nodeFieldCount;
512 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
513 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
514 var firstNodeIndex = 0;
515 var secondNodeIndex = 0;
516 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex && (!firstNodeIndex || !secondNodeIndex); edgeIndex += edgeFieldsCount) {
517 var edgeType = edges[edgeIndex + edgeTypeOffset];
518 if (edgeType === edgeInternalType) {
519 var edgeName = strings[edges[edgeIndex + edgeNameOffset]];
520 if (edgeName === "first")
521 firstNodeIndex = edges[edgeIndex + edgeToNodeOffset];
522 else if (edgeName === "second")
523 secondNodeIndex = edges[edgeIndex + edgeToNodeOffset];
526 nodesStack.push(secondNodeIndex);
527 nodesStack.push(firstNodeIndex);
535 className: function()
537 var type = this.type();
545 return "(compiled code)";
547 return "(" + type + ")";
554 classIndex: function()
556 var snapshot = this._snapshot;
557 var nodes = snapshot._nodes;
558 var type = nodes[this.nodeIndex + snapshot._nodeTypeOffset];;
559 if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType)
560 return nodes[this.nodeIndex + snapshot._nodeNameOffset];
569 var snapshot = this._snapshot;
570 return snapshot._nodes[this.nodeIndex + snapshot._nodeIdOffset];
578 return this._type() === this._snapshot._nodeHiddenType;
584 isSynthetic: function()
586 return this._type() === this._snapshot._nodeSyntheticType;
592 isUserRoot: function()
594 return !this.isSynthetic();
600 isDocumentDOMTreesRoot: function()
602 return this.isSynthetic() && this.name() === "(Document DOM trees)";
606 * @return {!WebInspector.HeapSnapshotCommon.Node}
608 serialize: function()
610 var result = WebInspector.HeapSnapshotNode.prototype.serialize.call(this);
611 var flags = this._snapshot._flagsOfNode(this);
612 if (flags & this._snapshot._nodeFlags.canBeQueried)
613 result.canBeQueried = true;
614 if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode)
615 result.detachedDOMTreeNode = true;
619 __proto__: WebInspector.HeapSnapshotNode.prototype
624 * @extends {WebInspector.HeapSnapshotEdge}
625 * @param {!WebInspector.JSHeapSnapshot} snapshot
626 * @param {number=} edgeIndex
628 WebInspector.JSHeapSnapshotEdge = function(snapshot, edgeIndex)
630 WebInspector.HeapSnapshotEdge.call(this, snapshot, edgeIndex);
633 WebInspector.JSHeapSnapshotEdge.prototype = {
635 * @return {!WebInspector.JSHeapSnapshotEdge}
639 var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot);
640 return new WebInspector.JSHeapSnapshotEdge(snapshot, this.edgeIndex);
646 hasStringName: function()
648 if (!this.isShortcut())
649 return this._hasStringName();
650 return isNaN(parseInt(this._name(), 10));
656 isElement: function()
658 return this._type() === this._snapshot._edgeElementType;
666 return this._type() === this._snapshot._edgeHiddenType;
674 return this._type() === this._snapshot._edgeWeakType;
680 isInternal: function()
682 return this._type() === this._snapshot._edgeInternalType;
688 isInvisible: function()
690 return this._type() === this._snapshot._edgeInvisibleType;
696 isShortcut: function()
698 return this._type() === this._snapshot._edgeShortcutType;
706 if (!this.isShortcut())
708 var numName = parseInt(this._name(), 10);
709 return isNaN(numName) ? this._name() : numName;
717 var name = this.name();
718 switch (this.type()) {
719 case "context": return "->" + name;
720 case "element": return "[" + name + "]";
721 case "weak": return "[[" + name + "]]";
723 return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
725 if (typeof name === "string")
726 return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
728 return "[" + name + "]";
732 return "{" + name + "}";
734 return "?" + name + "?";
737 _hasStringName: function()
739 return !this.isElement() && !this.isHidden();
744 return this._hasStringName() ? this._snapshot._strings[this._nameOrIndex()] : this._nameOrIndex();
747 _nameOrIndex: function()
749 return this._edges[this.edgeIndex + this._snapshot._edgeNameOffset];
754 return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset];
757 __proto__: WebInspector.HeapSnapshotEdge.prototype
763 * @extends {WebInspector.HeapSnapshotRetainerEdge}
764 * @param {!WebInspector.JSHeapSnapshot} snapshot
765 * @param {number} retainerIndex
767 WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainerIndex)
769 WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainerIndex);
772 WebInspector.JSHeapSnapshotRetainerEdge.prototype = {
774 * @return {!WebInspector.JSHeapSnapshotRetainerEdge}
778 var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot);
779 return new WebInspector.JSHeapSnapshotRetainerEdge(snapshot, this.retainerIndex());
787 return this._edge().isHidden();
793 isInternal: function()
795 return this._edge().isInternal();
801 isInvisible: function()
803 return this._edge().isInvisible();
809 isShortcut: function()
811 return this._edge().isShortcut();
819 return this._edge().isWeak();
822 __proto__: WebInspector.HeapSnapshotRetainerEdge.prototype