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 * @param {!WebInspector.HeapSnapshotProgress} progress
34 * @param {boolean} showHiddenData
35 * @extends {WebInspector.HeapSnapshot}
37 WebInspector.JSHeapSnapshot = function(profile, progress, showHiddenData)
39 this._nodeFlags = { // bit flags
41 detachedDOMTreeNode: 2,
42 pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger.
44 visitedMarkerMask: 0x0ffff, // bits: 0,1111,1111,1111,1111
45 visitedMarker: 0x10000 // bits: 1,0000,0000,0000,0000
47 this._lazyStringCache = { };
48 WebInspector.HeapSnapshot.call(this, profile, progress, showHiddenData);
51 WebInspector.JSHeapSnapshot.prototype = {
53 * @param {number} nodeIndex
54 * @return {!WebInspector.JSHeapSnapshotNode}
56 createNode: function(nodeIndex)
58 return new WebInspector.JSHeapSnapshotNode(this, nodeIndex);
63 * @param {number} edgeIndex
64 * @return {!WebInspector.JSHeapSnapshotEdge}
66 createEdge: function(edgeIndex)
68 return new WebInspector.JSHeapSnapshotEdge(this, edgeIndex);
73 * @param {number} retainerIndex
74 * @return {!WebInspector.JSHeapSnapshotRetainerEdge}
76 createRetainingEdge: function(retainerIndex)
78 return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainerIndex);
83 * @return {?function(!WebInspector.JSHeapSnapshotNode):boolean}
85 classNodesFilter: function()
88 * @param {!WebInspector.JSHeapSnapshotNode} node
93 return node.isUserObject();
95 return this._showHiddenData ? null : filter;
99 * @return {function(!WebInspector.HeapSnapshotEdge):boolean}
101 containmentEdgesFilter: function()
103 var showHiddenData = this._showHiddenData;
104 function filter(edge) {
105 if (edge.isInvisible())
109 return !edge.isHidden() && !edge.node().isHidden();
115 * @return {function(!WebInspector.HeapSnapshotEdge):boolean}
117 retainingEdgesFilter: function()
119 var containmentEdgesFilter = this.containmentEdgesFilter();
120 function filter(edge)
122 return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak();
129 WebInspector.HeapSnapshot.prototype.dispose.call(this);
133 _calculateFlags: function()
135 this._flags = new Uint32Array(this.nodeCount);
136 this._markDetachedDOMTreeNodes();
137 this._markQueriableHeapObjects();
138 this._markPageOwnedNodes();
142 * @param {!WebInspector.HeapSnapshotNode} node
145 _isUserRoot: function(node)
147 return node.isUserRoot() || node.isDocumentDOMTreesRoot();
151 * @param {function(!WebInspector.HeapSnapshotNode)} action
152 * @param {boolean=} userRootsOnly
154 forEachRoot: function(action, userRootsOnly)
157 * @param {!WebInspector.HeapSnapshotNode} node
158 * @param {!string} name
159 * @return {?WebInspector.HeapSnapshotNode}
161 function getChildNodeByName(node, name)
163 for (var iter = node.edges(); iter.hasNext(); iter.next()) {
164 var child = iter.edge.node();
165 if (child.name() === name)
171 var visitedNodes = {};
173 * @param {!WebInspector.HeapSnapshotNode} node
175 function doAction(node)
177 var ordinal = node._ordinal();
178 if (!visitedNodes[ordinal]) {
180 visitedNodes[ordinal] = true;
184 var gcRoots = getChildNodeByName(this.rootNode(), "(GC roots)");
189 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
190 var node = iter.edge.node();
191 if (this._isUserRoot(node))
195 for (var iter = gcRoots.edges(); iter.hasNext(); iter.next()) {
196 var subRoot = iter.edge.node();
197 for (var iter2 = subRoot.edges(); iter2.hasNext(); iter2.next())
198 doAction(iter2.edge.node());
201 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next())
202 doAction(iter.edge.node())
207 * @return {?{map: !Uint32Array, flag: number}}
209 userObjectsMapAndFlag: function()
211 return this._showHiddenData ? null : {
213 flag: this._nodeFlags.pageObject
217 _flagsOfNode: function(node)
219 return this._flags[node.nodeIndex / this._nodeFieldCount];
222 _markDetachedDOMTreeNodes: function()
224 var flag = this._nodeFlags.detachedDOMTreeNode;
225 var detachedDOMTreesRoot;
226 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
227 var node = iter.edge.node();
228 if (node.name() === "(Detached DOM trees)") {
229 detachedDOMTreesRoot = node;
234 if (!detachedDOMTreesRoot)
237 var detachedDOMTreeRE = /^Detached DOM tree/;
238 for (var iter = detachedDOMTreesRoot.edges(); iter.hasNext(); iter.next()) {
239 var node = iter.edge.node();
240 if (detachedDOMTreeRE.test(node.className())) {
241 for (var edgesIter = node.edges(); edgesIter.hasNext(); edgesIter.next())
242 this._flags[edgesIter.edge.node().nodeIndex / this._nodeFieldCount] |= flag;
247 _markQueriableHeapObjects: function()
249 // Allow runtime properties query for objects accessible from Window objects
250 // via regular properties, and for DOM wrappers. Trying to access random objects
251 // can cause a crash due to insonsistent state of internal properties of wrappers.
252 var flag = this._nodeFlags.canBeQueried;
253 var hiddenEdgeType = this._edgeHiddenType;
254 var internalEdgeType = this._edgeInternalType;
255 var invisibleEdgeType = this._edgeInvisibleType;
256 var weakEdgeType = this._edgeWeakType;
257 var edgeToNodeOffset = this._edgeToNodeOffset;
258 var edgeTypeOffset = this._edgeTypeOffset;
259 var edgeFieldsCount = this._edgeFieldsCount;
260 var containmentEdges = this._containmentEdges;
261 var nodes = this._nodes;
262 var nodeCount = this.nodeCount;
263 var nodeFieldCount = this._nodeFieldCount;
264 var firstEdgeIndexes = this._firstEdgeIndexes;
266 var flags = this._flags;
269 for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
270 if (iter.edge.node().isUserRoot())
271 list.push(iter.edge.node().nodeIndex / nodeFieldCount);
274 while (list.length) {
275 var nodeOrdinal = list.pop();
276 if (flags[nodeOrdinal] & flag)
278 flags[nodeOrdinal] |= flag;
279 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
280 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
281 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
282 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
283 var childNodeOrdinal = childNodeIndex / nodeFieldCount;
284 if (flags[childNodeOrdinal] & flag)
286 var type = containmentEdges[edgeIndex + edgeTypeOffset];
287 if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || type === weakEdgeType)
289 list.push(childNodeOrdinal);
294 _markPageOwnedNodes: function()
296 var edgeShortcutType = this._edgeShortcutType;
297 var edgeElementType = this._edgeElementType;
298 var edgeToNodeOffset = this._edgeToNodeOffset;
299 var edgeTypeOffset = this._edgeTypeOffset;
300 var edgeFieldsCount = this._edgeFieldsCount;
301 var edgeWeakType = this._edgeWeakType;
302 var firstEdgeIndexes = this._firstEdgeIndexes;
303 var containmentEdges = this._containmentEdges;
304 var containmentEdgesLength = containmentEdges.length;
305 var nodes = this._nodes;
306 var nodeFieldCount = this._nodeFieldCount;
307 var nodesCount = this.nodeCount;
309 var flags = this._flags;
310 var flag = this._nodeFlags.pageObject;
311 var visitedMarker = this._nodeFlags.visitedMarker;
312 var visitedMarkerMask = this._nodeFlags.visitedMarkerMask;
313 var markerAndFlag = visitedMarker | flag;
315 var nodesToVisit = new Uint32Array(nodesCount);
316 var nodesToVisitLength = 0;
318 var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
319 var node = this.rootNode();
320 for (var edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1];
321 edgeIndex < endEdgeIndex;
322 edgeIndex += edgeFieldsCount) {
323 var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
324 var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
325 if (edgeType === edgeElementType) {
326 node.nodeIndex = nodeIndex;
327 if (!node.isDocumentDOMTreesRoot())
329 } else if (edgeType !== edgeShortcutType)
331 var nodeOrdinal = nodeIndex / nodeFieldCount;
332 nodesToVisit[nodesToVisitLength++] = nodeOrdinal;
333 flags[nodeOrdinal] |= visitedMarker;
336 while (nodesToVisitLength) {
337 var nodeOrdinal = nodesToVisit[--nodesToVisitLength];
338 flags[nodeOrdinal] |= flag;
339 flags[nodeOrdinal] &= visitedMarkerMask;
340 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
341 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
342 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
343 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
344 var childNodeOrdinal = childNodeIndex / nodeFieldCount;
345 if (flags[childNodeOrdinal] & markerAndFlag)
347 var type = containmentEdges[edgeIndex + edgeTypeOffset];
348 if (type === edgeWeakType)
350 nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
351 flags[childNodeOrdinal] |= visitedMarker;
356 _calculateStatistics: function()
358 var nodeFieldCount = this._nodeFieldCount;
359 var nodes = this._nodes;
360 var nodesLength = nodes.length;
361 var nodeTypeOffset = this._nodeTypeOffset;
362 var nodeSizeOffset = this._nodeSelfSizeOffset;;
363 var nodeNativeType = this._nodeNativeType;
364 var nodeCodeType = this._nodeCodeType;
365 var nodeConsStringType = this._nodeConsStringType;
366 var nodeSlicedStringType = this._nodeSlicedStringType;
370 var sizeJSArrays = 0;
371 var node = this.rootNode();
372 for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) {
373 node.nodeIndex = nodeIndex;
374 var nodeType = nodes[nodeIndex + nodeTypeOffset];
375 var nodeSize = nodes[nodeIndex + nodeSizeOffset];
376 if (nodeType === nodeNativeType)
377 sizeNative += nodeSize;
378 else if (nodeType === nodeCodeType)
379 sizeCode += nodeSize;
380 else if (nodeType === nodeConsStringType || nodeType === nodeSlicedStringType || node.type() === "string")
381 sizeStrings += nodeSize;
382 else if (node.name() === "Array")
383 sizeJSArrays += this._calculateArraySize(node);
385 this._statistics = new WebInspector.HeapSnapshotCommon.Statistics();
386 this._statistics.total = this.totalSize;
387 this._statistics.v8heap = this.totalSize - sizeNative;
388 this._statistics.native = sizeNative;
389 this._statistics.code = sizeCode;
390 this._statistics.jsArrays = sizeJSArrays;
391 this._statistics.strings = sizeStrings;
395 * @param {!WebInspector.HeapSnapshotNode} node
398 _calculateArraySize: function(node)
400 var size = node.selfSize();
401 var beginEdgeIndex = node._edgeIndexesStart();
402 var endEdgeIndex = node._edgeIndexesEnd();
403 var containmentEdges = this._containmentEdges;
404 var strings = this._strings;
405 var edgeToNodeOffset = this._edgeToNodeOffset;
406 var edgeTypeOffset = this._edgeTypeOffset;
407 var edgeNameOffset = this._edgeNameOffset;
408 var edgeFieldsCount = this._edgeFieldsCount;
409 var edgeInternalType = this._edgeInternalType;
410 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
411 var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
412 if (edgeType !== edgeInternalType)
414 var edgeName = strings[containmentEdges[edgeIndex + edgeNameOffset]];
415 if (edgeName !== "elements")
417 var elementsNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
418 node.nodeIndex = elementsNodeIndex;
419 if (node.retainersCount() === 1)
420 size += node.selfSize();
427 * @return {!WebInspector.HeapSnapshotCommon.Statistics}
429 getStatistics: function()
431 return this._statistics;
434 __proto__: WebInspector.HeapSnapshot.prototype
439 * @extends {WebInspector.HeapSnapshotNode}
440 * @param {!WebInspector.JSHeapSnapshot} snapshot
441 * @param {number=} nodeIndex
443 WebInspector.JSHeapSnapshotNode = function(snapshot, nodeIndex)
445 WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex)
448 WebInspector.JSHeapSnapshotNode.prototype = {
452 canBeQueried: function()
454 var flags = this._snapshot._flagsOfNode(this);
455 return !!(flags & this._snapshot._nodeFlags.canBeQueried);
461 isUserObject: function()
463 var flags = this._snapshot._flagsOfNode(this);
464 return !!(flags & this._snapshot._nodeFlags.pageObject);
472 var snapshot = this._snapshot;
473 if (this._type() === snapshot._nodeConsStringType) {
474 var string = snapshot._lazyStringCache[this.nodeIndex];
475 if (typeof string === "undefined") {
476 string = this._consStringName();
477 snapshot._lazyStringCache[this.nodeIndex] = string;
481 return WebInspector.HeapSnapshotNode.prototype.name.call(this);
484 _consStringName: function()
486 var snapshot = this._snapshot;
487 var consStringType = snapshot._nodeConsStringType;
488 var edgeInternalType = snapshot._edgeInternalType;
489 var edgeFieldsCount = snapshot._edgeFieldsCount;
490 var edgeToNodeOffset = snapshot._edgeToNodeOffset;
491 var edgeTypeOffset = snapshot._edgeTypeOffset;
492 var edgeNameOffset = snapshot._edgeNameOffset;
493 var strings = snapshot._strings;
494 var edges = snapshot._containmentEdges;
495 var firstEdgeIndexes = snapshot._firstEdgeIndexes;
496 var nodeFieldCount = snapshot._nodeFieldCount;
497 var nodeTypeOffset = snapshot._nodeTypeOffset;
498 var nodeNameOffset = snapshot._nodeNameOffset;
499 var nodes = snapshot._nodes;
501 nodesStack.push(this.nodeIndex);
504 while (nodesStack.length && name.length < 1024) {
505 var nodeIndex = nodesStack.pop();
506 if (nodes[nodeIndex + nodeTypeOffset] !== consStringType) {
507 name += strings[nodes[nodeIndex + nodeNameOffset]];
510 var nodeOrdinal = nodeIndex / nodeFieldCount;
511 var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
512 var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
513 var firstNodeIndex = 0;
514 var secondNodeIndex = 0;
515 for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex && (!firstNodeIndex || !secondNodeIndex); edgeIndex += edgeFieldsCount) {
516 var edgeType = edges[edgeIndex + edgeTypeOffset];
517 if (edgeType === edgeInternalType) {
518 var edgeName = strings[edges[edgeIndex + edgeNameOffset]];
519 if (edgeName === "first")
520 firstNodeIndex = edges[edgeIndex + edgeToNodeOffset];
521 else if (edgeName === "second")
522 secondNodeIndex = edges[edgeIndex + edgeToNodeOffset];
525 nodesStack.push(secondNodeIndex);
526 nodesStack.push(firstNodeIndex);
534 className: function()
536 var type = this.type();
544 return "(compiled code)";
546 return "(" + type + ")";
553 classIndex: function()
555 var snapshot = this._snapshot;
556 var nodes = snapshot._nodes;
557 var type = nodes[this.nodeIndex + snapshot._nodeTypeOffset];;
558 if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType)
559 return nodes[this.nodeIndex + snapshot._nodeNameOffset];
568 var snapshot = this._snapshot;
569 return snapshot._nodes[this.nodeIndex + snapshot._nodeIdOffset];
577 return this._type() === this._snapshot._nodeHiddenType;
583 isSynthetic: function()
585 return this._type() === this._snapshot._nodeSyntheticType;
591 isUserRoot: function()
593 return !this.isSynthetic();
599 isDocumentDOMTreesRoot: function()
601 return this.isSynthetic() && this.name() === "(Document DOM trees)";
605 * @return {!WebInspector.HeapSnapshotNode.Serialized}
607 serialize: function()
609 var result = WebInspector.HeapSnapshotNode.prototype.serialize.call(this);
610 var flags = this._snapshot._flagsOfNode(this);
611 if (flags & this._snapshot._nodeFlags.canBeQueried)
612 result.canBeQueried = true;
613 if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode)
614 result.detachedDOMTreeNode = true;
618 __proto__: WebInspector.HeapSnapshotNode.prototype
623 * @extends {WebInspector.HeapSnapshotEdge}
624 * @param {!WebInspector.JSHeapSnapshot} snapshot
625 * @param {number=} edgeIndex
627 WebInspector.JSHeapSnapshotEdge = function(snapshot, edgeIndex)
629 WebInspector.HeapSnapshotEdge.call(this, snapshot, edgeIndex);
632 WebInspector.JSHeapSnapshotEdge.prototype = {
634 * @return {!WebInspector.JSHeapSnapshotEdge}
638 var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot);
639 return new WebInspector.JSHeapSnapshotEdge(snapshot, this.edgeIndex);
645 hasStringName: function()
647 if (!this.isShortcut())
648 return this._hasStringName();
649 return isNaN(parseInt(this._name(), 10));
655 isElement: function()
657 return this._type() === this._snapshot._edgeElementType;
665 return this._type() === this._snapshot._edgeHiddenType;
673 return this._type() === this._snapshot._edgeWeakType;
679 isInternal: function()
681 return this._type() === this._snapshot._edgeInternalType;
687 isInvisible: function()
689 return this._type() === this._snapshot._edgeInvisibleType;
695 isShortcut: function()
697 return this._type() === this._snapshot._edgeShortcutType;
705 if (!this.isShortcut())
707 var numName = parseInt(this._name(), 10);
708 return isNaN(numName) ? this._name() : numName;
716 var name = this.name();
717 switch (this.type()) {
718 case "context": return "->" + name;
719 case "element": return "[" + name + "]";
720 case "weak": return "[[" + name + "]]";
722 return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
724 if (typeof name === "string")
725 return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
727 return "[" + name + "]";
731 return "{" + name + "}";
733 return "?" + name + "?";
736 _hasStringName: function()
738 return !this.isElement() && !this.isHidden();
743 return this._hasStringName() ? this._snapshot._strings[this._nameOrIndex()] : this._nameOrIndex();
746 _nameOrIndex: function()
748 return this._edges[this.edgeIndex + this._snapshot._edgeNameOffset];
753 return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset];
756 __proto__: WebInspector.HeapSnapshotEdge.prototype
762 * @extends {WebInspector.HeapSnapshotRetainerEdge}
763 * @param {!WebInspector.JSHeapSnapshot} snapshot
764 * @param {number} retainerIndex
766 WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainerIndex)
768 WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainerIndex);
771 WebInspector.JSHeapSnapshotRetainerEdge.prototype = {
773 * @return {!WebInspector.JSHeapSnapshotRetainerEdge}
777 var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot);
778 return new WebInspector.JSHeapSnapshotRetainerEdge(snapshot, this.retainerIndex());
786 return this._edge().isHidden();
792 isInternal: function()
794 return this._edge().isInternal();
800 isInvisible: function()
802 return this._edge().isInvisible();
808 isShortcut: function()
810 return this._edge().isShortcut();
818 return this._edge().isWeak();
821 __proto__: WebInspector.HeapSnapshotRetainerEdge.prototype