| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| import * as Common from '../common/common.js'; |
| import * as HeapSnapshotModel from '../heap_snapshot_model/heap_snapshot_model.js'; |
| |
| import {AllocationProfile} from './AllocationProfile.js'; |
| import {HeapSnapshotWorkerDispatcher} from './HeapSnapshotWorkerDispatcher.js'; // eslint-disable-line no-unused-vars |
| |
| /** |
| * @interface |
| */ |
| export class HeapSnapshotItem { |
| /** |
| * @return {number} |
| */ |
| itemIndex() { |
| } |
| |
| /** |
| * @return {!Object} |
| */ |
| serialize() {} |
| } |
| |
| /** |
| * @implements {HeapSnapshotItem} |
| * @unrestricted |
| */ |
| export class HeapSnapshotEdge { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| * @param {number=} edgeIndex |
| */ |
| constructor(snapshot, edgeIndex) { |
| this._snapshot = snapshot; |
| this._edges = snapshot.containmentEdges; |
| this.edgeIndex = edgeIndex || 0; |
| } |
| |
| /** |
| * @return {!HeapSnapshotEdge} |
| */ |
| clone() { |
| return new HeapSnapshotEdge(this._snapshot, this.edgeIndex); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| hasStringName() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| name() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @return {!HeapSnapshotNode} |
| */ |
| node() { |
| return this._snapshot.createNode(this.nodeIndex()); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| nodeIndex() { |
| return this._edges[this.edgeIndex + this._snapshot._edgeToNodeOffset]; |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| toString() { |
| return 'HeapSnapshotEdge: ' + this.name(); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| type() { |
| return this._snapshot._edgeTypes[this.rawType()]; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| itemIndex() { |
| return this.edgeIndex; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.Edge} |
| */ |
| serialize() { |
| return new HeapSnapshotModel.HeapSnapshotModel.Edge( |
| this.name(), this.node().serialize(), this.type(), this.edgeIndex); |
| } |
| |
| /** |
| * @protected |
| * @return {number} |
| */ |
| rawType() { |
| return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset]; |
| } |
| } |
| |
| /** |
| * @interface |
| */ |
| export class HeapSnapshotItemIterator { |
| /** |
| * @return {boolean} |
| */ |
| hasNext() { |
| } |
| |
| /** |
| * @return {!HeapSnapshotItem} |
| */ |
| item() { |
| } |
| |
| next() {} |
| } |
| |
| /** |
| * @interface |
| */ |
| export class HeapSnapshotItemIndexProvider { |
| /** |
| * @param {number} newIndex |
| * @return {!HeapSnapshotItem} |
| */ |
| itemForIndex(newIndex) { |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIndexProvider} |
| * @unrestricted |
| */ |
| export class HeapSnapshotNodeIndexProvider { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| */ |
| constructor(snapshot) { |
| this._node = snapshot.createNode(); |
| } |
| |
| /** |
| * @override |
| * @param {number} index |
| * @return {!HeapSnapshotNode} |
| */ |
| itemForIndex(index) { |
| this._node.nodeIndex = index; |
| return this._node; |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIndexProvider} |
| * @unrestricted |
| */ |
| export class HeapSnapshotEdgeIndexProvider { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| */ |
| constructor(snapshot) { |
| this._edge = snapshot.createEdge(0); |
| } |
| |
| /** |
| * @override |
| * @param {number} index |
| * @return {!HeapSnapshotEdge} |
| */ |
| itemForIndex(index) { |
| this._edge.edgeIndex = index; |
| return this._edge; |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIndexProvider} |
| * @unrestricted |
| */ |
| export class HeapSnapshotRetainerEdgeIndexProvider { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| */ |
| constructor(snapshot) { |
| this._retainerEdge = snapshot.createRetainingEdge(0); |
| } |
| |
| /** |
| * @override |
| * @param {number} index |
| * @return {!HeapSnapshotRetainerEdge} |
| */ |
| itemForIndex(index) { |
| this._retainerEdge.setRetainerIndex(index); |
| return this._retainerEdge; |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIterator} |
| * @unrestricted |
| */ |
| export class HeapSnapshotEdgeIterator { |
| /** |
| * @param {!HeapSnapshotNode} node |
| */ |
| constructor(node) { |
| this._sourceNode = node; |
| this.edge = node._snapshot.createEdge(node.edgeIndexesStart()); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasNext() { |
| return this.edge.edgeIndex < this._sourceNode.edgeIndexesEnd(); |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotEdge} |
| */ |
| item() { |
| return this.edge; |
| } |
| |
| /** |
| * @override |
| */ |
| next() { |
| this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount; |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItem} |
| * @unrestricted |
| */ |
| export class HeapSnapshotRetainerEdge { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| * @param {number} retainerIndex |
| */ |
| constructor(snapshot, retainerIndex) { |
| this._snapshot = snapshot; |
| this.setRetainerIndex(retainerIndex); |
| } |
| |
| /** |
| * @return {!HeapSnapshotRetainerEdge} |
| */ |
| clone() { |
| return new HeapSnapshotRetainerEdge(this._snapshot, this.retainerIndex()); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| hasStringName() { |
| return this._edge().hasStringName(); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| name() { |
| return this._edge().name(); |
| } |
| |
| /** |
| * @return {!HeapSnapshotNode} |
| */ |
| node() { |
| return this._node(); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| nodeIndex() { |
| return this._retainingNodeIndex; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| retainerIndex() { |
| return this._retainerIndex; |
| } |
| |
| /** |
| * @param {number} retainerIndex |
| */ |
| setRetainerIndex(retainerIndex) { |
| if (retainerIndex === this._retainerIndex) { |
| return; |
| } |
| this._retainerIndex = retainerIndex; |
| this._globalEdgeIndex = this._snapshot._retainingEdges[retainerIndex]; |
| this._retainingNodeIndex = this._snapshot._retainingNodes[retainerIndex]; |
| this._edgeInstance = null; |
| this._nodeInstance = null; |
| } |
| |
| /** |
| * @param {number} edgeIndex |
| */ |
| set edgeIndex(edgeIndex) { |
| this.setRetainerIndex(edgeIndex); |
| } |
| |
| _node() { |
| if (!this._nodeInstance) { |
| this._nodeInstance = this._snapshot.createNode(this._retainingNodeIndex); |
| } |
| return this._nodeInstance; |
| } |
| |
| _edge() { |
| if (!this._edgeInstance) { |
| this._edgeInstance = this._snapshot.createEdge(this._globalEdgeIndex); |
| } |
| return this._edgeInstance; |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| toString() { |
| return this._edge().toString(); |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| itemIndex() { |
| return this._retainerIndex; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.Edge} |
| */ |
| serialize() { |
| return new HeapSnapshotModel.HeapSnapshotModel.Edge( |
| this.name(), this.node().serialize(), this.type(), this._globalEdgeIndex); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| type() { |
| return this._edge().type(); |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIterator} |
| * @unrestricted |
| */ |
| export class HeapSnapshotRetainerEdgeIterator { |
| /** |
| * @param {!HeapSnapshotNode} retainedNode |
| */ |
| constructor(retainedNode) { |
| const snapshot = retainedNode._snapshot; |
| const retainedNodeOrdinal = retainedNode.ordinal(); |
| const retainerIndex = snapshot._firstRetainerIndex[retainedNodeOrdinal]; |
| this._retainersEnd = snapshot._firstRetainerIndex[retainedNodeOrdinal + 1]; |
| this.retainer = snapshot.createRetainingEdge(retainerIndex); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasNext() { |
| return this.retainer.retainerIndex() < this._retainersEnd; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotRetainerEdge} |
| */ |
| item() { |
| return this.retainer; |
| } |
| |
| /** |
| * @override |
| */ |
| next() { |
| this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1); |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItem} |
| * @unrestricted |
| */ |
| export class HeapSnapshotNode { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| * @param {number=} nodeIndex |
| */ |
| constructor(snapshot, nodeIndex) { |
| this._snapshot = snapshot; |
| this.nodeIndex = nodeIndex || 0; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| distance() { |
| return this._snapshot._nodeDistances[this.nodeIndex / this._snapshot._nodeFieldCount]; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| className() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| classIndex() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| dominatorIndex() { |
| const nodeFieldCount = this._snapshot._nodeFieldCount; |
| return this._snapshot._dominatorsTree[this.nodeIndex / this._snapshot._nodeFieldCount] * nodeFieldCount; |
| } |
| |
| /** |
| * @return {!HeapSnapshotEdgeIterator} |
| */ |
| edges() { |
| return new HeapSnapshotEdgeIterator(this); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| edgesCount() { |
| return (this.edgeIndexesEnd() - this.edgeIndexesStart()) / this._snapshot._edgeFieldsCount; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| id() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isRoot() { |
| return this.nodeIndex === this._snapshot._rootNodeIndex; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| name() { |
| return this._snapshot.strings[this._name()]; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| retainedSize() { |
| return this._snapshot._retainedSizes[this.ordinal()]; |
| } |
| |
| /** |
| * @return {!HeapSnapshotRetainerEdgeIterator} |
| */ |
| retainers() { |
| return new HeapSnapshotRetainerEdgeIterator(this); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| retainersCount() { |
| const snapshot = this._snapshot; |
| const ordinal = this.ordinal(); |
| return snapshot._firstRetainerIndex[ordinal + 1] - snapshot._firstRetainerIndex[ordinal]; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| selfSize() { |
| const snapshot = this._snapshot; |
| return snapshot.nodes[this.nodeIndex + snapshot._nodeSelfSizeOffset]; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| type() { |
| return this._snapshot._nodeTypes[this.rawType()]; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| traceNodeId() { |
| const snapshot = this._snapshot; |
| return snapshot.nodes[this.nodeIndex + snapshot._nodeTraceNodeIdOffset]; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| itemIndex() { |
| return this.nodeIndex; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.Node} |
| */ |
| serialize() { |
| return new HeapSnapshotModel.HeapSnapshotModel.Node( |
| this.id(), this.name(), this.distance(), this.nodeIndex, this.retainedSize(), this.selfSize(), this.type()); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _name() { |
| const snapshot = this._snapshot; |
| return snapshot.nodes[this.nodeIndex + snapshot._nodeNameOffset]; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| edgeIndexesStart() { |
| return this._snapshot._firstEdgeIndexes[this.ordinal()]; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| edgeIndexesEnd() { |
| return this._snapshot._firstEdgeIndexes[this.ordinal() + 1]; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| ordinal() { |
| return this.nodeIndex / this._snapshot._nodeFieldCount; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _nextNodeIndex() { |
| return this.nodeIndex + this._snapshot._nodeFieldCount; |
| } |
| |
| /** |
| * @protected |
| * @return {number} |
| */ |
| rawType() { |
| const snapshot = this._snapshot; |
| return snapshot.nodes[this.nodeIndex + snapshot._nodeTypeOffset]; |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIterator} |
| * @unrestricted |
| */ |
| export class HeapSnapshotNodeIterator { |
| /** |
| * @param {!HeapSnapshotNode} node |
| */ |
| constructor(node) { |
| this.node = node; |
| this._nodesLength = node._snapshot.nodes.length; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasNext() { |
| return this.node.nodeIndex < this._nodesLength; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotNode} |
| */ |
| item() { |
| return this.node; |
| } |
| |
| /** |
| * @override |
| */ |
| next() { |
| this.node.nodeIndex = this.node._nextNodeIndex(); |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIterator} |
| * @unrestricted |
| */ |
| export class HeapSnapshotIndexRangeIterator { |
| /** |
| * @param {!HeapSnapshotItemIndexProvider} itemProvider |
| * @param {!Array.<number>|!Uint32Array} indexes |
| */ |
| constructor(itemProvider, indexes) { |
| this._itemProvider = itemProvider; |
| this._indexes = indexes; |
| this._position = 0; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasNext() { |
| return this._position < this._indexes.length; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotItem} |
| */ |
| item() { |
| const index = this._indexes[this._position]; |
| return this._itemProvider.itemForIndex(index); |
| } |
| |
| /** |
| * @override |
| */ |
| next() { |
| ++this._position; |
| } |
| } |
| |
| /** |
| * @implements {HeapSnapshotItemIterator} |
| * @unrestricted |
| */ |
| export class HeapSnapshotFilteredIterator { |
| /** |
| * @param {!HeapSnapshotItemIterator} iterator |
| * @param {function(!HeapSnapshotItem):boolean=} filter |
| */ |
| constructor(iterator, filter) { |
| this._iterator = iterator; |
| this._filter = filter; |
| this._skipFilteredItems(); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasNext() { |
| return this._iterator.hasNext(); |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotItem} |
| */ |
| item() { |
| return this._iterator.item(); |
| } |
| |
| /** |
| * @override |
| */ |
| next() { |
| this._iterator.next(); |
| this._skipFilteredItems(); |
| } |
| |
| _skipFilteredItems() { |
| while (this._iterator.hasNext() && !this._filter(this._iterator.item())) { |
| this._iterator.next(); |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshotProgress { |
| /** |
| * @param {!HeapSnapshotWorkerDispatcher=} dispatcher |
| */ |
| constructor(dispatcher) { |
| this._dispatcher = dispatcher; |
| } |
| |
| /** |
| * @param {string} status |
| */ |
| updateStatus(status) { |
| this._sendUpdateEvent(Common.UIString.serializeUIString(status)); |
| } |
| |
| /** |
| * @param {string} title |
| * @param {number} value |
| * @param {number} total |
| */ |
| updateProgress(title, value, total) { |
| const percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); |
| this._sendUpdateEvent(Common.UIString.serializeUIString(title, [percentValue])); |
| } |
| |
| /** |
| * @param {string} error |
| */ |
| reportProblem(error) { |
| // May be undefined in tests. |
| if (this._dispatcher) { |
| this._dispatcher.sendEvent(HeapSnapshotModel.HeapSnapshotModel.HeapSnapshotProgressEvent.BrokenSnapshot, error); |
| } |
| } |
| |
| /** |
| * @param {string} serializedText |
| */ |
| _sendUpdateEvent(serializedText) { |
| // May be undefined in tests. |
| if (this._dispatcher) { |
| this._dispatcher.sendEvent(HeapSnapshotModel.HeapSnapshotModel.HeapSnapshotProgressEvent.Update, serializedText); |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshotProblemReport { |
| /** |
| * @param {string} title |
| */ |
| constructor(title) { |
| this._errors = [title]; |
| } |
| |
| /** |
| * @param {string} error |
| */ |
| addError(error) { |
| if (this._errors.length > 100) { |
| return; |
| } |
| this._errors.push(error); |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| toString() { |
| return this._errors.join('\n '); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshot { |
| /** |
| * @param {!Object} profile |
| * @param {!HeapSnapshotProgress} progress |
| */ |
| constructor(profile, progress) { |
| /** @type {!Uint32Array} */ |
| this.nodes = profile.nodes; |
| /** @type {!Uint32Array} */ |
| this.containmentEdges = profile.edges; |
| /** @type {!HeapSnapshotMetainfo} */ |
| this._metaNode = profile.snapshot.meta; |
| /** @type {!Array.<number>} */ |
| this._rawSamples = profile.samples; |
| /** @type {?HeapSnapshotModel.HeapSnapshotModel.Samples} */ |
| this._samples = null; |
| /** @type {!Array.<string>} */ |
| this.strings = profile.strings; |
| /** @type {!Array.<number>} */ |
| this._locations = profile.locations; |
| this._progress = progress; |
| |
| this._noDistance = -5; |
| this._rootNodeIndex = 0; |
| if (profile.snapshot.root_index) { |
| this._rootNodeIndex = profile.snapshot.root_index; |
| } |
| |
| this._snapshotDiffs = {}; |
| this._aggregatesForDiff = null; |
| this._aggregates = {}; |
| this._aggregatesSortedFlags = {}; |
| this._profile = profile; |
| } |
| |
| /** |
| * @protected |
| */ |
| initialize() { |
| const meta = this._metaNode; |
| |
| this._nodeTypeOffset = meta.node_fields.indexOf('type'); |
| this._nodeNameOffset = meta.node_fields.indexOf('name'); |
| this._nodeIdOffset = meta.node_fields.indexOf('id'); |
| this._nodeSelfSizeOffset = meta.node_fields.indexOf('self_size'); |
| this._nodeEdgeCountOffset = meta.node_fields.indexOf('edge_count'); |
| this._nodeTraceNodeIdOffset = meta.node_fields.indexOf('trace_node_id'); |
| this._nodeFieldCount = meta.node_fields.length; |
| |
| this._nodeTypes = meta.node_types[this._nodeTypeOffset]; |
| this._nodeArrayType = this._nodeTypes.indexOf('array'); |
| this._nodeHiddenType = this._nodeTypes.indexOf('hidden'); |
| this._nodeObjectType = this._nodeTypes.indexOf('object'); |
| this._nodeNativeType = this._nodeTypes.indexOf('native'); |
| this._nodeConsStringType = this._nodeTypes.indexOf('concatenated string'); |
| this._nodeSlicedStringType = this._nodeTypes.indexOf('sliced string'); |
| this._nodeCodeType = this._nodeTypes.indexOf('code'); |
| this._nodeSyntheticType = this._nodeTypes.indexOf('synthetic'); |
| |
| this._edgeFieldsCount = meta.edge_fields.length; |
| this._edgeTypeOffset = meta.edge_fields.indexOf('type'); |
| this._edgeNameOffset = meta.edge_fields.indexOf('name_or_index'); |
| this._edgeToNodeOffset = meta.edge_fields.indexOf('to_node'); |
| |
| this._edgeTypes = meta.edge_types[this._edgeTypeOffset]; |
| this._edgeTypes.push('invisible'); |
| this._edgeElementType = this._edgeTypes.indexOf('element'); |
| this._edgeHiddenType = this._edgeTypes.indexOf('hidden'); |
| this._edgeInternalType = this._edgeTypes.indexOf('internal'); |
| this._edgeShortcutType = this._edgeTypes.indexOf('shortcut'); |
| this._edgeWeakType = this._edgeTypes.indexOf('weak'); |
| this._edgeInvisibleType = this._edgeTypes.indexOf('invisible'); |
| |
| const location_fields = meta.location_fields || []; |
| |
| this._locationIndexOffset = location_fields.indexOf('object_index'); |
| this._locationScriptIdOffset = location_fields.indexOf('script_id'); |
| this._locationLineOffset = location_fields.indexOf('line'); |
| this._locationColumnOffset = location_fields.indexOf('column'); |
| this._locationFieldCount = location_fields.length; |
| |
| this.nodeCount = this.nodes.length / this._nodeFieldCount; |
| this._edgeCount = this.containmentEdges.length / this._edgeFieldsCount; |
| |
| this._retainedSizes = new Float64Array(this.nodeCount); |
| this._firstEdgeIndexes = new Uint32Array(this.nodeCount + 1); |
| this._retainingNodes = new Uint32Array(this._edgeCount); |
| this._retainingEdges = new Uint32Array(this._edgeCount); |
| this._firstRetainerIndex = new Uint32Array(this.nodeCount + 1); |
| this._nodeDistances = new Int32Array(this.nodeCount); |
| this._firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1); |
| this._dominatedNodes = new Uint32Array(this.nodeCount - 1); |
| |
| this._progress.updateStatus(ls`Building edge indexes…`); |
| this._buildEdgeIndexes(); |
| this._progress.updateStatus(ls`Building retainers…`); |
| this._buildRetainers(); |
| this._progress.updateStatus(ls`Calculating node flags…`); |
| this.calculateFlags(); |
| this._progress.updateStatus(ls`Calculating distances…`); |
| this.calculateDistances(); |
| this._progress.updateStatus(ls`Building postorder index…`); |
| const result = this._buildPostOrderIndex(); |
| // Actually it is array that maps node ordinal number to dominator node ordinal number. |
| this._progress.updateStatus(ls`Building dominator tree…`); |
| this._dominatorsTree = |
| this._buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex); |
| this._progress.updateStatus(ls`Calculating retained sizes…`); |
| this._calculateRetainedSizes(result.postOrderIndex2NodeOrdinal); |
| this._progress.updateStatus(ls`Building dominated nodes…`); |
| this._buildDominatedNodes(); |
| this._progress.updateStatus(ls`Calculating statistics…`); |
| this.calculateStatistics(); |
| this._progress.updateStatus(ls`Calculating samples…`); |
| this._buildSamples(); |
| this._progress.updateStatus(ls`Building locations…`); |
| this._buildLocationMap(); |
| this._progress.updateStatus(ls`Finished processing.`); |
| |
| if (this._profile.snapshot.trace_function_count) { |
| this._progress.updateStatus(ls`Building allocation statistics…`); |
| const nodes = this.nodes; |
| const nodesLength = nodes.length; |
| const nodeFieldCount = this._nodeFieldCount; |
| const node = this.rootNode(); |
| const liveObjects = {}; |
| for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { |
| node.nodeIndex = nodeIndex; |
| const traceNodeId = node.traceNodeId(); |
| let stats = liveObjects[traceNodeId]; |
| if (!stats) { |
| liveObjects[traceNodeId] = stats = {count: 0, size: 0, ids: []}; |
| } |
| stats.count++; |
| stats.size += node.selfSize(); |
| stats.ids.push(node.id()); |
| } |
| this._allocationProfile = new AllocationProfile(this._profile, liveObjects); |
| this._progress.updateStatus(ls`Done`); |
| } |
| } |
| |
| _buildEdgeIndexes() { |
| const nodes = this.nodes; |
| const nodeCount = this.nodeCount; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| const nodeFieldCount = this._nodeFieldCount; |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const nodeEdgeCountOffset = this._nodeEdgeCountOffset; |
| firstEdgeIndexes[nodeCount] = this.containmentEdges.length; |
| for (let nodeOrdinal = 0, edgeIndex = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) { |
| firstEdgeIndexes[nodeOrdinal] = edgeIndex; |
| edgeIndex += nodes[nodeOrdinal * nodeFieldCount + nodeEdgeCountOffset] * edgeFieldsCount; |
| } |
| } |
| |
| _buildRetainers() { |
| const retainingNodes = this._retainingNodes; |
| const retainingEdges = this._retainingEdges; |
| // Index of the first retainer in the _retainingNodes and _retainingEdges |
| // arrays. Addressed by retained node index. |
| const firstRetainerIndex = this._firstRetainerIndex; |
| |
| const containmentEdges = this.containmentEdges; |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const nodeFieldCount = this._nodeFieldCount; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| const nodeCount = this.nodeCount; |
| |
| for (let toNodeFieldIndex = edgeToNodeOffset, l = containmentEdges.length; toNodeFieldIndex < l; |
| toNodeFieldIndex += edgeFieldsCount) { |
| const toNodeIndex = containmentEdges[toNodeFieldIndex]; |
| if (toNodeIndex % nodeFieldCount) { |
| throw new Error('Invalid toNodeIndex ' + toNodeIndex); |
| } |
| ++firstRetainerIndex[toNodeIndex / nodeFieldCount]; |
| } |
| for (let i = 0, firstUnusedRetainerSlot = 0; i < nodeCount; i++) { |
| const retainersCount = firstRetainerIndex[i]; |
| firstRetainerIndex[i] = firstUnusedRetainerSlot; |
| retainingNodes[firstUnusedRetainerSlot] = retainersCount; |
| firstUnusedRetainerSlot += retainersCount; |
| } |
| firstRetainerIndex[nodeCount] = retainingNodes.length; |
| |
| let nextNodeFirstEdgeIndex = firstEdgeIndexes[0]; |
| for (let srcNodeOrdinal = 0; srcNodeOrdinal < nodeCount; ++srcNodeOrdinal) { |
| const firstEdgeIndex = nextNodeFirstEdgeIndex; |
| nextNodeFirstEdgeIndex = firstEdgeIndexes[srcNodeOrdinal + 1]; |
| const srcNodeIndex = srcNodeOrdinal * nodeFieldCount; |
| for (let edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += edgeFieldsCount) { |
| const toNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| if (toNodeIndex % nodeFieldCount) { |
| throw new Error('Invalid toNodeIndex ' + toNodeIndex); |
| } |
| const firstRetainerSlotIndex = firstRetainerIndex[toNodeIndex / nodeFieldCount]; |
| const nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--retainingNodes[firstRetainerSlotIndex]); |
| retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex; |
| retainingEdges[nextUnusedRetainerSlotIndex] = edgeIndex; |
| } |
| } |
| } |
| |
| /** |
| * @param {number=} nodeIndex |
| */ |
| createNode(nodeIndex) { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @param {number} edgeIndex |
| * @return {!JSHeapSnapshotEdge} |
| */ |
| createEdge(edgeIndex) { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @param {number} retainerIndex |
| * @return {!JSHeapSnapshotRetainerEdge} |
| */ |
| createRetainingEdge(retainerIndex) { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @return {!HeapSnapshotNodeIterator} |
| */ |
| _allNodes() { |
| return new HeapSnapshotNodeIterator(this.rootNode()); |
| } |
| |
| /** |
| * @return {!HeapSnapshotNode} |
| */ |
| rootNode() { |
| return this.createNode(this._rootNodeIndex); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| get rootNodeIndex() { |
| return this._rootNodeIndex; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| get totalSize() { |
| return this.rootNode().retainedSize(); |
| } |
| |
| /** |
| * @param {number} nodeIndex |
| * @return {number} |
| */ |
| _getDominatedIndex(nodeIndex) { |
| if (nodeIndex % this._nodeFieldCount) { |
| throw new Error('Invalid nodeIndex: ' + nodeIndex); |
| } |
| return this._firstDominatedNodeIndex[nodeIndex / this._nodeFieldCount]; |
| } |
| |
| /** |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.NodeFilter} nodeFilter |
| * @return {undefined|function(!HeapSnapshotNode):boolean} |
| */ |
| _createFilter(nodeFilter) { |
| const minNodeId = nodeFilter.minNodeId; |
| const maxNodeId = nodeFilter.maxNodeId; |
| const allocationNodeId = nodeFilter.allocationNodeId; |
| let filter; |
| if (typeof allocationNodeId === 'number') { |
| filter = this._createAllocationStackFilter(allocationNodeId); |
| filter.key = 'AllocationNodeId: ' + allocationNodeId; |
| } else if (typeof minNodeId === 'number' && typeof maxNodeId === 'number') { |
| filter = this._createNodeIdFilter(minNodeId, maxNodeId); |
| filter.key = 'NodeIdRange: ' + minNodeId + '..' + maxNodeId; |
| } |
| return filter; |
| } |
| |
| /** |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.SearchConfig} searchConfig |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.NodeFilter} nodeFilter |
| * @return {!Array.<number>} |
| */ |
| search(searchConfig, nodeFilter) { |
| const query = searchConfig.query; |
| |
| function filterString(matchedStringIndexes, string, index) { |
| if (string.indexOf(query) !== -1) { |
| matchedStringIndexes.add(index); |
| } |
| return matchedStringIndexes; |
| } |
| |
| const regexp = searchConfig.isRegex ? new RegExp(query) : createPlainTextSearchRegex(query, 'i'); |
| function filterRegexp(matchedStringIndexes, string, index) { |
| if (regexp.test(string)) { |
| matchedStringIndexes.add(index); |
| } |
| return matchedStringIndexes; |
| } |
| |
| const stringFilter = (searchConfig.isRegex || !searchConfig.caseSensitive) ? filterRegexp : filterString; |
| const stringIndexes = this.strings.reduce(stringFilter, new Set()); |
| |
| if (!stringIndexes.size) { |
| return []; |
| } |
| |
| const filter = this._createFilter(nodeFilter); |
| const nodeIds = []; |
| const nodesLength = this.nodes.length; |
| const nodes = this.nodes; |
| const nodeNameOffset = this._nodeNameOffset; |
| const nodeIdOffset = this._nodeIdOffset; |
| const nodeFieldCount = this._nodeFieldCount; |
| const node = this.rootNode(); |
| |
| for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { |
| node.nodeIndex = nodeIndex; |
| if (filter && !filter(node)) { |
| continue; |
| } |
| if (stringIndexes.has(nodes[nodeIndex + nodeNameOffset])) { |
| nodeIds.push(nodes[nodeIndex + nodeIdOffset]); |
| } |
| } |
| return nodeIds; |
| } |
| |
| /** |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.NodeFilter} nodeFilter |
| * @return {!Object.<string, !HeapSnapshotModel.HeapSnapshotModel.Aggregate>} |
| */ |
| aggregatesWithFilter(nodeFilter) { |
| const filter = this._createFilter(nodeFilter); |
| const key = filter ? filter.key : 'allObjects'; |
| return this.aggregates(false, key, filter); |
| } |
| |
| /** |
| * @param {number} minNodeId |
| * @param {number} maxNodeId |
| * @return {function(!HeapSnapshotNode):boolean} |
| */ |
| _createNodeIdFilter(minNodeId, maxNodeId) { |
| /** |
| * @param {!HeapSnapshotNode} node |
| * @return {boolean} |
| */ |
| function nodeIdFilter(node) { |
| const id = node.id(); |
| return id > minNodeId && id <= maxNodeId; |
| } |
| return nodeIdFilter; |
| } |
| |
| /** |
| * @param {number} bottomUpAllocationNodeId |
| * @return {function(!HeapSnapshotNode):boolean|undefined} |
| */ |
| _createAllocationStackFilter(bottomUpAllocationNodeId) { |
| const traceIds = this._allocationProfile.traceIds(bottomUpAllocationNodeId); |
| if (!traceIds.length) { |
| return undefined; |
| } |
| const set = {}; |
| for (let i = 0; i < traceIds.length; i++) { |
| set[traceIds[i]] = true; |
| } |
| /** |
| * @param {!HeapSnapshotNode} node |
| * @return {boolean} |
| */ |
| function traceIdFilter(node) { |
| return !!set[node.traceNodeId()]; |
| } |
| return traceIdFilter; |
| } |
| |
| /** |
| * @param {boolean} sortedIndexes |
| * @param {string=} key |
| * @param {function(!HeapSnapshotNode):boolean=} filter |
| * @return {!Object.<string, !HeapSnapshotModel.HeapSnapshotModel.Aggregate>} |
| */ |
| aggregates(sortedIndexes, key, filter) { |
| let aggregatesByClassName = key && this._aggregates[key]; |
| if (!aggregatesByClassName) { |
| const aggregates = this._buildAggregates(filter); |
| this._calculateClassesRetainedSize(aggregates.aggregatesByClassIndex, filter); |
| aggregatesByClassName = aggregates.aggregatesByClassName; |
| if (key) { |
| this._aggregates[key] = aggregatesByClassName; |
| } |
| } |
| |
| if (sortedIndexes && (!key || !this._aggregatesSortedFlags[key])) { |
| this._sortAggregateIndexes(aggregatesByClassName); |
| if (key) { |
| this._aggregatesSortedFlags[key] = sortedIndexes; |
| } |
| } |
| return aggregatesByClassName; |
| } |
| |
| /** |
| * @return {!Array.<!HeapSnapshotModel.HeapSnapshotModel.SerializedAllocationNode>} |
| */ |
| allocationTracesTops() { |
| return this._allocationProfile.serializeTraceTops(); |
| } |
| |
| /** |
| * @param {number} nodeId |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.AllocationNodeCallers} |
| */ |
| allocationNodeCallers(nodeId) { |
| return this._allocationProfile.serializeCallers(nodeId); |
| } |
| |
| /** |
| * @param {number} nodeIndex |
| * @return {?Array.<!HeapSnapshotModel.HeapSnapshotModel.AllocationStackFrame>} |
| */ |
| allocationStack(nodeIndex) { |
| const node = this.createNode(nodeIndex); |
| const allocationNodeId = node.traceNodeId(); |
| if (!allocationNodeId) { |
| return null; |
| } |
| return this._allocationProfile.serializeAllocationStack(allocationNodeId); |
| } |
| |
| /** |
| * @return {!Object.<string, !HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>} |
| */ |
| aggregatesForDiff() { |
| if (this._aggregatesForDiff) { |
| return this._aggregatesForDiff; |
| } |
| |
| const aggregatesByClassName = this.aggregates(true, 'allObjects'); |
| this._aggregatesForDiff = {}; |
| |
| const node = this.createNode(); |
| for (const className in aggregatesByClassName) { |
| const aggregate = aggregatesByClassName[className]; |
| const indexes = aggregate.idxs; |
| const ids = new Array(indexes.length); |
| const selfSizes = new Array(indexes.length); |
| for (let i = 0; i < indexes.length; i++) { |
| node.nodeIndex = indexes[i]; |
| ids[i] = node.id(); |
| selfSizes[i] = node.selfSize(); |
| } |
| |
| this._aggregatesForDiff[className] = {indexes: indexes, ids: ids, selfSizes: selfSizes}; |
| } |
| return this._aggregatesForDiff; |
| } |
| |
| /** |
| * @protected |
| * @param {!HeapSnapshotNode} node |
| * @return {boolean} |
| */ |
| isUserRoot(node) { |
| return true; |
| } |
| |
| /** |
| * @param {function(!HeapSnapshotNode,!HeapSnapshotEdge):boolean=} filter |
| */ |
| calculateDistances(filter) { |
| const nodeCount = this.nodeCount; |
| const distances = this._nodeDistances; |
| const noDistance = this._noDistance; |
| for (let i = 0; i < nodeCount; ++i) { |
| distances[i] = noDistance; |
| } |
| |
| const nodesToVisit = new Uint32Array(this.nodeCount); |
| let nodesToVisitLength = 0; |
| |
| // BFS for user root objects. |
| for (let iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { |
| const node = iter.edge.node(); |
| if (this.isUserRoot(node)) { |
| distances[node.ordinal()] = 1; |
| nodesToVisit[nodesToVisitLength++] = node.nodeIndex; |
| } |
| } |
| this._bfs(nodesToVisit, nodesToVisitLength, distances, filter); |
| |
| // BFS for objects not reached from user roots. |
| distances[this.rootNode().ordinal()] = |
| nodesToVisitLength > 0 ? HeapSnapshotModel.HeapSnapshotModel.baseSystemDistance : 0; |
| nodesToVisit[0] = this.rootNode().nodeIndex; |
| nodesToVisitLength = 1; |
| this._bfs(nodesToVisit, nodesToVisitLength, distances, filter); |
| } |
| |
| /** |
| * @param {!Uint32Array} nodesToVisit |
| * @param {number} nodesToVisitLength |
| * @param {!Int32Array} distances |
| * @param {function(!HeapSnapshotNode,!HeapSnapshotEdge):boolean=} filter |
| */ |
| _bfs(nodesToVisit, nodesToVisitLength, distances, filter) { |
| // Preload fields into local variables for better performance. |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const nodeFieldCount = this._nodeFieldCount; |
| const containmentEdges = this.containmentEdges; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const nodeCount = this.nodeCount; |
| const edgeWeakType = this._edgeWeakType; |
| const noDistance = this._noDistance; |
| |
| let index = 0; |
| const edge = this.createEdge(0); |
| const node = this.createNode(0); |
| while (index < nodesToVisitLength) { |
| const nodeIndex = nodesToVisit[index++]; // shift generates too much garbage. |
| const nodeOrdinal = nodeIndex / nodeFieldCount; |
| const distance = distances[nodeOrdinal] + 1; |
| const firstEdgeIndex = firstEdgeIndexes[nodeOrdinal]; |
| const edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; |
| node.nodeIndex = nodeIndex; |
| for (let edgeIndex = firstEdgeIndex; edgeIndex < edgesEnd; edgeIndex += edgeFieldsCount) { |
| const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; |
| if (edgeType === edgeWeakType) { |
| continue; |
| } |
| const childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| const childNodeOrdinal = childNodeIndex / nodeFieldCount; |
| if (distances[childNodeOrdinal] !== noDistance) { |
| continue; |
| } |
| edge.edgeIndex = edgeIndex; |
| if (filter && !filter(node, edge)) { |
| continue; |
| } |
| distances[childNodeOrdinal] = distance; |
| nodesToVisit[nodesToVisitLength++] = childNodeIndex; |
| } |
| } |
| if (nodesToVisitLength > nodeCount) { |
| throw new Error( |
| 'BFS failed. Nodes to visit (' + nodesToVisitLength + ') is more than nodes count (' + nodeCount + ')'); |
| } |
| } |
| |
| /** |
| * @param {function(!HeapSnapshotNode):boolean=} filter |
| * @return {!{aggregatesByClassName: !Object<string, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>, |
| * aggregatesByClassIndex: !Object<number, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>}} |
| */ |
| _buildAggregates(filter) { |
| const aggregates = {}; |
| const aggregatesByClassName = {}; |
| const classIndexes = []; |
| const nodes = this.nodes; |
| const nodesLength = nodes.length; |
| const nodeNativeType = this._nodeNativeType; |
| const nodeFieldCount = this._nodeFieldCount; |
| const selfSizeOffset = this._nodeSelfSizeOffset; |
| const nodeTypeOffset = this._nodeTypeOffset; |
| const node = this.rootNode(); |
| const nodeDistances = this._nodeDistances; |
| |
| for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { |
| node.nodeIndex = nodeIndex; |
| if (filter && !filter(node)) { |
| continue; |
| } |
| const selfSize = nodes[nodeIndex + selfSizeOffset]; |
| if (!selfSize && nodes[nodeIndex + nodeTypeOffset] !== nodeNativeType) { |
| continue; |
| } |
| const classIndex = node.classIndex(); |
| const nodeOrdinal = nodeIndex / nodeFieldCount; |
| const distance = nodeDistances[nodeOrdinal]; |
| if (!(classIndex in aggregates)) { |
| const nodeType = node.type(); |
| const nameMatters = nodeType === 'object' || nodeType === 'native'; |
| const value = { |
| count: 1, |
| distance: distance, |
| self: selfSize, |
| maxRet: 0, |
| type: nodeType, |
| name: nameMatters ? node.name() : null, |
| idxs: [nodeIndex] |
| }; |
| aggregates[classIndex] = value; |
| classIndexes.push(classIndex); |
| aggregatesByClassName[node.className()] = value; |
| } else { |
| const clss = aggregates[classIndex]; |
| clss.distance = Math.min(clss.distance, distance); |
| ++clss.count; |
| clss.self += selfSize; |
| clss.idxs.push(nodeIndex); |
| } |
| } |
| |
| // Shave off provisionally allocated space. |
| for (let i = 0, l = classIndexes.length; i < l; ++i) { |
| const classIndex = classIndexes[i]; |
| aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice(); |
| } |
| return {aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates}; |
| } |
| |
| /** |
| * @param {!Object<number, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>} aggregates |
| * @param {function(!HeapSnapshotNode):boolean=} filter |
| */ |
| _calculateClassesRetainedSize(aggregates, filter) { |
| const rootNodeIndex = this._rootNodeIndex; |
| const node = this.createNode(rootNodeIndex); |
| const list = [rootNodeIndex]; |
| const sizes = [-1]; |
| const classes = []; |
| const seenClassNameIndexes = {}; |
| const nodeFieldCount = this._nodeFieldCount; |
| const nodeTypeOffset = this._nodeTypeOffset; |
| const nodeNativeType = this._nodeNativeType; |
| const dominatedNodes = this._dominatedNodes; |
| const nodes = this.nodes; |
| const firstDominatedNodeIndex = this._firstDominatedNodeIndex; |
| |
| while (list.length) { |
| const nodeIndex = list.pop(); |
| node.nodeIndex = nodeIndex; |
| let classIndex = node.classIndex(); |
| const seen = !!seenClassNameIndexes[classIndex]; |
| const nodeOrdinal = nodeIndex / nodeFieldCount; |
| const dominatedIndexFrom = firstDominatedNodeIndex[nodeOrdinal]; |
| const dominatedIndexTo = firstDominatedNodeIndex[nodeOrdinal + 1]; |
| |
| if (!seen && (!filter || filter(node)) && |
| (node.selfSize() || nodes[nodeIndex + nodeTypeOffset] === nodeNativeType)) { |
| aggregates[classIndex].maxRet += node.retainedSize(); |
| if (dominatedIndexFrom !== dominatedIndexTo) { |
| seenClassNameIndexes[classIndex] = true; |
| sizes.push(list.length); |
| classes.push(classIndex); |
| } |
| } |
| for (let i = dominatedIndexFrom; i < dominatedIndexTo; i++) { |
| list.push(dominatedNodes[i]); |
| } |
| |
| const l = list.length; |
| while (sizes[sizes.length - 1] === l) { |
| sizes.pop(); |
| classIndex = classes.pop(); |
| seenClassNameIndexes[classIndex] = false; |
| } |
| } |
| } |
| |
| /** |
| * @param {!{aggregatesByClassName: !Object<string, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>, aggregatesByClassIndex: !Object<number, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>}} aggregates |
| */ |
| _sortAggregateIndexes(aggregates) { |
| const nodeA = this.createNode(); |
| const nodeB = this.createNode(); |
| for (const clss in aggregates) { |
| aggregates[clss].idxs.sort((idxA, idxB) => { |
| nodeA.nodeIndex = idxA; |
| nodeB.nodeIndex = idxB; |
| return nodeA.id() < nodeB.id() ? -1 : 1; |
| }); |
| } |
| } |
| |
| /** |
| * The function checks is the edge should be considered during building |
| * postorder iterator and dominator tree. |
| * |
| * @param {number} nodeIndex |
| * @param {number} edgeType |
| * @return {boolean} |
| */ |
| _isEssentialEdge(nodeIndex, edgeType) { |
| // Shortcuts at the root node have special meaning of marking user global objects. |
| return edgeType !== this._edgeWeakType && |
| (edgeType !== this._edgeShortcutType || nodeIndex === this._rootNodeIndex); |
| } |
| |
| _buildPostOrderIndex() { |
| const nodeFieldCount = this._nodeFieldCount; |
| const nodeCount = this.nodeCount; |
| const rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; |
| |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| const containmentEdges = this.containmentEdges; |
| |
| const mapAndFlag = this.userObjectsMapAndFlag(); |
| const flags = mapAndFlag ? mapAndFlag.map : null; |
| const flag = mapAndFlag ? mapAndFlag.flag : 0; |
| |
| const stackNodes = new Uint32Array(nodeCount); |
| const stackCurrentEdge = new Uint32Array(nodeCount); |
| const postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount); |
| const nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount); |
| const visited = new Uint8Array(nodeCount); |
| let postOrderIndex = 0; |
| |
| let stackTop = 0; |
| stackNodes[0] = rootNodeOrdinal; |
| stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal]; |
| visited[rootNodeOrdinal] = 1; |
| |
| let iteration = 0; |
| while (true) { |
| ++iteration; |
| while (stackTop >= 0) { |
| const nodeOrdinal = stackNodes[stackTop]; |
| const edgeIndex = stackCurrentEdge[stackTop]; |
| const edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; |
| |
| if (edgeIndex < edgesEnd) { |
| stackCurrentEdge[stackTop] += edgeFieldsCount; |
| const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; |
| if (!this._isEssentialEdge(nodeOrdinal * nodeFieldCount, edgeType)) { |
| continue; |
| } |
| const childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| const childNodeOrdinal = childNodeIndex / nodeFieldCount; |
| if (visited[childNodeOrdinal]) { |
| continue; |
| } |
| const nodeFlag = !flags || (flags[nodeOrdinal] & flag); |
| const childNodeFlag = !flags || (flags[childNodeOrdinal] & flag); |
| // We are skipping the edges from non-page-owned nodes to page-owned nodes. |
| // Otherwise the dominators for the objects that also were retained by debugger would be affected. |
| if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag) { |
| continue; |
| } |
| ++stackTop; |
| stackNodes[stackTop] = childNodeOrdinal; |
| stackCurrentEdge[stackTop] = firstEdgeIndexes[childNodeOrdinal]; |
| visited[childNodeOrdinal] = 1; |
| } else { |
| // Done with all the node children |
| nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex; |
| postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal; |
| --stackTop; |
| } |
| } |
| |
| if (postOrderIndex === nodeCount || iteration > 1) { |
| break; |
| } |
| const errors = new HeapSnapshotProblemReport(`Heap snapshot: ${ |
| nodeCount - postOrderIndex} nodes are unreachable from the root. Following nodes have only weak retainers:`); |
| const dumpNode = this.rootNode(); |
| // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is |
| // visited after all orphan nodes and their subgraphs. |
| --postOrderIndex; |
| stackTop = 0; |
| stackNodes[0] = rootNodeOrdinal; |
| stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal + 1]; // no need to reiterate its edges |
| for (let i = 0; i < nodeCount; ++i) { |
| if (visited[i] || !this._hasOnlyWeakRetainers(i)) { |
| continue; |
| } |
| |
| // Add all nodes that have only weak retainers to traverse their subgraphs. |
| stackNodes[++stackTop] = i; |
| stackCurrentEdge[stackTop] = firstEdgeIndexes[i]; |
| visited[i] = 1; |
| |
| dumpNode.nodeIndex = i * nodeFieldCount; |
| const retainers = []; |
| for (let it = dumpNode.retainers(); it.hasNext(); it.next()) { |
| retainers.push(`${it.item().node().name()}@${it.item().node().id()}.${it.item().name()}`); |
| } |
| errors.addError(`${dumpNode.name()} @${dumpNode.id()} weak retainers: ${retainers.join(', ')}`); |
| } |
| console.warn(errors.toString()); |
| } |
| |
| // If we already processed all orphan nodes that have only weak retainers and still have some orphans... |
| if (postOrderIndex !== nodeCount) { |
| const errors = new HeapSnapshotProblemReport( |
| 'Still found ' + (nodeCount - postOrderIndex) + ' unreachable nodes in heap snapshot:'); |
| const dumpNode = this.rootNode(); |
| // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is |
| // visited after all orphan nodes and their subgraphs. |
| --postOrderIndex; |
| for (let i = 0; i < nodeCount; ++i) { |
| if (visited[i]) { |
| continue; |
| } |
| dumpNode.nodeIndex = i * nodeFieldCount; |
| errors.addError(dumpNode.name() + ' @' + dumpNode.id()); |
| // Fix it by giving the node a postorder index anyway. |
| nodeOrdinal2PostOrderIndex[i] = postOrderIndex; |
| postOrderIndex2NodeOrdinal[postOrderIndex++] = i; |
| } |
| nodeOrdinal2PostOrderIndex[rootNodeOrdinal] = postOrderIndex; |
| postOrderIndex2NodeOrdinal[postOrderIndex++] = rootNodeOrdinal; |
| console.warn(errors.toString()); |
| } |
| |
| return { |
| postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal, |
| nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex |
| }; |
| } |
| |
| /** |
| * @param {number} nodeOrdinal |
| * @return {boolean} |
| */ |
| _hasOnlyWeakRetainers(nodeOrdinal) { |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const edgeWeakType = this._edgeWeakType; |
| const edgeShortcutType = this._edgeShortcutType; |
| const containmentEdges = this.containmentEdges; |
| const retainingEdges = this._retainingEdges; |
| const beginRetainerIndex = this._firstRetainerIndex[nodeOrdinal]; |
| const endRetainerIndex = this._firstRetainerIndex[nodeOrdinal + 1]; |
| for (let retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) { |
| const retainerEdgeIndex = retainingEdges[retainerIndex]; |
| const retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset]; |
| if (retainerEdgeType !== edgeWeakType && retainerEdgeType !== edgeShortcutType) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // The algorithm is based on the article: |
| // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm" |
| // Softw. Pract. Exper. 4 (2001), pp. 1-10. |
| /** |
| * @param {!Array.<number>} postOrderIndex2NodeOrdinal |
| * @param {!Array.<number>} nodeOrdinal2PostOrderIndex |
| */ |
| _buildDominatorTree(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex) { |
| const nodeFieldCount = this._nodeFieldCount; |
| const firstRetainerIndex = this._firstRetainerIndex; |
| const retainingNodes = this._retainingNodes; |
| const retainingEdges = this._retainingEdges; |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| const containmentEdges = this.containmentEdges; |
| const rootNodeIndex = this._rootNodeIndex; |
| |
| const mapAndFlag = this.userObjectsMapAndFlag(); |
| const flags = mapAndFlag ? mapAndFlag.map : null; |
| const flag = mapAndFlag ? mapAndFlag.flag : 0; |
| |
| const nodesCount = postOrderIndex2NodeOrdinal.length; |
| const rootPostOrderedIndex = nodesCount - 1; |
| const noEntry = nodesCount; |
| const dominators = new Uint32Array(nodesCount); |
| for (let i = 0; i < rootPostOrderedIndex; ++i) { |
| dominators[i] = noEntry; |
| } |
| dominators[rootPostOrderedIndex] = rootPostOrderedIndex; |
| |
| // The affected array is used to mark entries which dominators |
| // have to be racalculated because of changes in their retainers. |
| const affected = new Uint8Array(nodesCount); |
| let nodeOrdinal; |
| |
| { // Mark the root direct children as affected. |
| nodeOrdinal = this._rootNodeIndex / nodeFieldCount; |
| const endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; |
| for (let edgeIndex = firstEdgeIndexes[nodeOrdinal]; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { |
| const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; |
| if (!this._isEssentialEdge(this._rootNodeIndex, edgeType)) { |
| continue; |
| } |
| const childNodeOrdinal = containmentEdges[edgeIndex + edgeToNodeOffset] / nodeFieldCount; |
| affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1; |
| } |
| } |
| |
| let changed = true; |
| while (changed) { |
| changed = false; |
| for (let postOrderIndex = rootPostOrderedIndex - 1; postOrderIndex >= 0; --postOrderIndex) { |
| if (affected[postOrderIndex] === 0) { |
| continue; |
| } |
| affected[postOrderIndex] = 0; |
| // If dominator of the entry has already been set to root, |
| // then it can't propagate any further. |
| if (dominators[postOrderIndex] === rootPostOrderedIndex) { |
| continue; |
| } |
| nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; |
| const nodeFlag = !flags || (flags[nodeOrdinal] & flag); |
| let newDominatorIndex = noEntry; |
| const beginRetainerIndex = firstRetainerIndex[nodeOrdinal]; |
| const endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1]; |
| let orphanNode = true; |
| for (let retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) { |
| const retainerEdgeIndex = retainingEdges[retainerIndex]; |
| const retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset]; |
| const retainerNodeIndex = retainingNodes[retainerIndex]; |
| if (!this._isEssentialEdge(retainerNodeIndex, retainerEdgeType)) { |
| continue; |
| } |
| orphanNode = false; |
| const retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount; |
| const retainerNodeFlag = !flags || (flags[retainerNodeOrdinal] & flag); |
| // We are skipping the edges from non-page-owned nodes to page-owned nodes. |
| // Otherwise the dominators for the objects that also were retained by debugger would be affected. |
| if (retainerNodeIndex !== rootNodeIndex && nodeFlag && !retainerNodeFlag) { |
| continue; |
| } |
| let retanerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal]; |
| if (dominators[retanerPostOrderIndex] !== noEntry) { |
| if (newDominatorIndex === noEntry) { |
| newDominatorIndex = retanerPostOrderIndex; |
| } else { |
| while (retanerPostOrderIndex !== newDominatorIndex) { |
| while (retanerPostOrderIndex < newDominatorIndex) { |
| retanerPostOrderIndex = dominators[retanerPostOrderIndex]; |
| } |
| while (newDominatorIndex < retanerPostOrderIndex) { |
| newDominatorIndex = dominators[newDominatorIndex]; |
| } |
| } |
| } |
| // If idom has already reached the root, it doesn't make sense |
| // to check other retainers. |
| if (newDominatorIndex === rootPostOrderedIndex) { |
| break; |
| } |
| } |
| } |
| // Make root dominator of orphans. |
| if (orphanNode) { |
| newDominatorIndex = rootPostOrderedIndex; |
| } |
| if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) { |
| dominators[postOrderIndex] = newDominatorIndex; |
| changed = true; |
| nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; |
| const beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset; |
| const endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1]; |
| for (let toNodeFieldIndex = beginEdgeToNodeFieldIndex; toNodeFieldIndex < endEdgeToNodeFieldIndex; |
| toNodeFieldIndex += edgeFieldsCount) { |
| const childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount; |
| affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1; |
| } |
| } |
| } |
| } |
| |
| const dominatorsTree = new Uint32Array(nodesCount); |
| for (let postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) { |
| nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; |
| dominatorsTree[nodeOrdinal] = postOrderIndex2NodeOrdinal[dominators[postOrderIndex]]; |
| } |
| return dominatorsTree; |
| } |
| |
| /** |
| * @param {!Array<number>} postOrderIndex2NodeOrdinal |
| */ |
| _calculateRetainedSizes(postOrderIndex2NodeOrdinal) { |
| const nodeCount = this.nodeCount; |
| const nodes = this.nodes; |
| const nodeSelfSizeOffset = this._nodeSelfSizeOffset; |
| const nodeFieldCount = this._nodeFieldCount; |
| const dominatorsTree = this._dominatorsTree; |
| const retainedSizes = this._retainedSizes; |
| |
| for (let nodeOrdinal = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) { |
| retainedSizes[nodeOrdinal] = nodes[nodeOrdinal * nodeFieldCount + nodeSelfSizeOffset]; |
| } |
| |
| // Propagate retained sizes for each node excluding root. |
| for (let postOrderIndex = 0; postOrderIndex < nodeCount - 1; ++postOrderIndex) { |
| const nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; |
| const dominatorOrdinal = dominatorsTree[nodeOrdinal]; |
| retainedSizes[dominatorOrdinal] += retainedSizes[nodeOrdinal]; |
| } |
| } |
| |
| _buildDominatedNodes() { |
| // Builds up two arrays: |
| // - "dominatedNodes" is a continuous array, where each node owns an |
| // interval (can be empty) with corresponding dominated nodes. |
| // - "indexArray" is an array of indexes in the "dominatedNodes" |
| // with the same positions as in the _nodeIndex. |
| const indexArray = this._firstDominatedNodeIndex; |
| // All nodes except the root have dominators. |
| const dominatedNodes = this._dominatedNodes; |
| |
| // Count the number of dominated nodes for each node. Skip the root (node at |
| // index 0) as it is the only node that dominates itself. |
| const nodeFieldCount = this._nodeFieldCount; |
| const dominatorsTree = this._dominatorsTree; |
| |
| let fromNodeOrdinal = 0; |
| let toNodeOrdinal = this.nodeCount; |
| const rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; |
| if (rootNodeOrdinal === fromNodeOrdinal) { |
| fromNodeOrdinal = 1; |
| } else if (rootNodeOrdinal === toNodeOrdinal - 1) { |
| toNodeOrdinal = toNodeOrdinal - 1; |
| } else { |
| throw new Error('Root node is expected to be either first or last'); |
| } |
| for (let nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) { |
| ++indexArray[dominatorsTree[nodeOrdinal]]; |
| } |
| // Put in the first slot of each dominatedNodes slice the count of entries |
| // that will be filled. |
| let firstDominatedNodeIndex = 0; |
| for (let i = 0, l = this.nodeCount; i < l; ++i) { |
| const dominatedCount = dominatedNodes[firstDominatedNodeIndex] = indexArray[i]; |
| indexArray[i] = firstDominatedNodeIndex; |
| firstDominatedNodeIndex += dominatedCount; |
| } |
| indexArray[this.nodeCount] = dominatedNodes.length; |
| // Fill up the dominatedNodes array with indexes of dominated nodes. Skip the root (node at |
| // index 0) as it is the only node that dominates itself. |
| for (let nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) { |
| const dominatorOrdinal = dominatorsTree[nodeOrdinal]; |
| let dominatedRefIndex = indexArray[dominatorOrdinal]; |
| dominatedRefIndex += (--dominatedNodes[dominatedRefIndex]); |
| dominatedNodes[dominatedRefIndex] = nodeOrdinal * nodeFieldCount; |
| } |
| } |
| |
| _buildSamples() { |
| const samples = this._rawSamples; |
| if (!samples || !samples.length) { |
| return; |
| } |
| const sampleCount = samples.length / 2; |
| const sizeForRange = new Array(sampleCount); |
| const timestamps = new Array(sampleCount); |
| const lastAssignedIds = new Array(sampleCount); |
| |
| const timestampOffset = this._metaNode.sample_fields.indexOf('timestamp_us'); |
| const lastAssignedIdOffset = this._metaNode.sample_fields.indexOf('last_assigned_id'); |
| for (let i = 0; i < sampleCount; i++) { |
| sizeForRange[i] = 0; |
| timestamps[i] = (samples[2 * i + timestampOffset]) / 1000; |
| lastAssignedIds[i] = samples[2 * i + lastAssignedIdOffset]; |
| } |
| |
| const nodes = this.nodes; |
| const nodesLength = nodes.length; |
| const nodeFieldCount = this._nodeFieldCount; |
| const node = this.rootNode(); |
| for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { |
| node.nodeIndex = nodeIndex; |
| |
| const nodeId = node.id(); |
| // JS objects have odd ids, skip native objects. |
| if (nodeId % 2 === 0) { |
| continue; |
| } |
| const rangeIndex = lastAssignedIds.lowerBound(nodeId); |
| if (rangeIndex === sampleCount) { |
| // TODO: make heap profiler not allocate while taking snapshot |
| continue; |
| } |
| sizeForRange[rangeIndex] += node.selfSize(); |
| } |
| this._samples = new HeapSnapshotModel.HeapSnapshotModel.Samples(timestamps, lastAssignedIds, sizeForRange); |
| } |
| |
| _buildLocationMap() { |
| /** @type {!Map<number, !HeapSnapshotModel.HeapSnapshotModel.Location>} */ |
| const map = new Map(); |
| const locations = this._locations; |
| |
| for (let i = 0; i < locations.length; i += this._locationFieldCount) { |
| const nodeIndex = locations[i + this._locationIndexOffset]; |
| const scriptId = locations[i + this._locationScriptIdOffset]; |
| const line = locations[i + this._locationLineOffset]; |
| const col = locations[i + this._locationColumnOffset]; |
| map.set(nodeIndex, new HeapSnapshotModel.HeapSnapshotModel.Location(scriptId, line, col)); |
| } |
| |
| this._locationMap = map; |
| } |
| |
| /** |
| * @param {number} nodeIndex |
| * @return {?HeapSnapshotModel.HeapSnapshotModel.Location} |
| */ |
| getLocation(nodeIndex) { |
| return this._locationMap.get(nodeIndex) || null; |
| } |
| |
| /** |
| * @return {?HeapSnapshotModel.HeapSnapshotModel.Samples} |
| */ |
| getSamples() { |
| return this._samples; |
| } |
| |
| /** |
| * @protected |
| */ |
| calculateFlags() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @protected |
| */ |
| calculateStatistics() { |
| throw new Error('Not implemented'); |
| } |
| |
| userObjectsMapAndFlag() { |
| throw new Error('Not implemented'); |
| } |
| |
| /** |
| * @param {string} baseSnapshotId |
| * @param {!Object.<string, !HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>} baseSnapshotAggregates |
| * @return {!Object.<string, !HeapSnapshotModel.HeapSnapshotModel.Diff>} |
| */ |
| calculateSnapshotDiff(baseSnapshotId, baseSnapshotAggregates) { |
| let snapshotDiff = this._snapshotDiffs[baseSnapshotId]; |
| if (snapshotDiff) { |
| return snapshotDiff; |
| } |
| snapshotDiff = {}; |
| |
| const aggregates = this.aggregates(true, 'allObjects'); |
| for (const className in baseSnapshotAggregates) { |
| const baseAggregate = baseSnapshotAggregates[className]; |
| const diff = this._calculateDiffForClass(baseAggregate, aggregates[className]); |
| if (diff) { |
| snapshotDiff[className] = diff; |
| } |
| } |
| const emptyBaseAggregate = new HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff(); |
| for (const className in aggregates) { |
| if (className in baseSnapshotAggregates) { |
| continue; |
| } |
| snapshotDiff[className] = this._calculateDiffForClass(emptyBaseAggregate, aggregates[className]); |
| } |
| |
| this._snapshotDiffs[baseSnapshotId] = snapshotDiff; |
| return snapshotDiff; |
| } |
| |
| /** |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff} baseAggregate |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.Aggregate} aggregate |
| * @return {?HeapSnapshotModel.HeapSnapshotModel.Diff} |
| */ |
| _calculateDiffForClass(baseAggregate, aggregate) { |
| const baseIds = baseAggregate.ids; |
| const baseIndexes = baseAggregate.indexes; |
| const baseSelfSizes = baseAggregate.selfSizes; |
| |
| const indexes = aggregate ? aggregate.idxs : []; |
| |
| let i = 0; |
| let j = 0; |
| const l = baseIds.length; |
| const m = indexes.length; |
| const diff = new HeapSnapshotModel.HeapSnapshotModel.Diff(); |
| |
| const nodeB = this.createNode(indexes[j]); |
| while (i < l && j < m) { |
| const nodeAId = baseIds[i]; |
| if (nodeAId < nodeB.id()) { |
| diff.deletedIndexes.push(baseIndexes[i]); |
| diff.removedCount++; |
| diff.removedSize += baseSelfSizes[i]; |
| ++i; |
| } else if ( |
| nodeAId > |
| nodeB.id()) { // Native nodes(e.g. dom groups) may have ids less than max JS object id in the base snapshot |
| diff.addedIndexes.push(indexes[j]); |
| diff.addedCount++; |
| diff.addedSize += nodeB.selfSize(); |
| nodeB.nodeIndex = indexes[++j]; |
| } else { // nodeAId === nodeB.id() |
| ++i; |
| nodeB.nodeIndex = indexes[++j]; |
| } |
| } |
| while (i < l) { |
| diff.deletedIndexes.push(baseIndexes[i]); |
| diff.removedCount++; |
| diff.removedSize += baseSelfSizes[i]; |
| ++i; |
| } |
| while (j < m) { |
| diff.addedIndexes.push(indexes[j]); |
| diff.addedCount++; |
| diff.addedSize += nodeB.selfSize(); |
| nodeB.nodeIndex = indexes[++j]; |
| } |
| diff.countDelta = diff.addedCount - diff.removedCount; |
| diff.sizeDelta = diff.addedSize - diff.removedSize; |
| if (!diff.addedCount && !diff.removedCount) { |
| return null; |
| } |
| return diff; |
| } |
| |
| _nodeForSnapshotObjectId(snapshotObjectId) { |
| for (let it = this._allNodes(); it.hasNext(); it.next()) { |
| if (it.node.id() === snapshotObjectId) { |
| return it.node; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param {string} snapshotObjectId |
| * @return {?string} |
| */ |
| nodeClassName(snapshotObjectId) { |
| const node = this._nodeForSnapshotObjectId(snapshotObjectId); |
| if (node) { |
| return node.className(); |
| } |
| return null; |
| } |
| |
| /** |
| * @param {string} name |
| * @return {!Array.<number>} |
| */ |
| idsOfObjectsWithName(name) { |
| const ids = []; |
| for (let it = this._allNodes(); it.hasNext(); it.next()) { |
| if (it.item().name() === name) { |
| ids.push(it.item().id()); |
| } |
| } |
| return ids; |
| } |
| |
| /** |
| * @param {number} nodeIndex |
| * @return {!HeapSnapshotEdgesProvider} |
| */ |
| createEdgesProvider(nodeIndex) { |
| const node = this.createNode(nodeIndex); |
| const filter = this.containmentEdgesFilter(); |
| const indexProvider = new HeapSnapshotEdgeIndexProvider(this); |
| return new HeapSnapshotEdgesProvider(this, filter, node.edges(), indexProvider); |
| } |
| |
| /** |
| * @param {number} nodeIndex |
| * @param {?function(!HeapSnapshotEdge):boolean} filter |
| * @return {!HeapSnapshotEdgesProvider} |
| */ |
| createEdgesProviderForTest(nodeIndex, filter) { |
| const node = this.createNode(nodeIndex); |
| const indexProvider = new HeapSnapshotEdgeIndexProvider(this); |
| return new HeapSnapshotEdgesProvider(this, filter, node.edges(), indexProvider); |
| } |
| |
| /** |
| * @return {?function(!HeapSnapshotEdge):boolean} |
| */ |
| retainingEdgesFilter() { |
| return null; |
| } |
| |
| /** |
| * @return {?function(!HeapSnapshotEdge):boolean} |
| */ |
| containmentEdgesFilter() { |
| return null; |
| } |
| |
| /** |
| * @param {number} nodeIndex |
| * @return {!HeapSnapshotEdgesProvider} |
| */ |
| createRetainingEdgesProvider(nodeIndex) { |
| const node = this.createNode(nodeIndex); |
| const filter = this.retainingEdgesFilter(); |
| const indexProvider = new HeapSnapshotRetainerEdgeIndexProvider(this); |
| return new HeapSnapshotEdgesProvider(this, filter, node.retainers(), indexProvider); |
| } |
| |
| /** |
| * @param {string} baseSnapshotId |
| * @param {string} className |
| * @return {!HeapSnapshotNodesProvider} |
| */ |
| createAddedNodesProvider(baseSnapshotId, className) { |
| const snapshotDiff = this._snapshotDiffs[baseSnapshotId]; |
| const diffForClass = snapshotDiff[className]; |
| return new HeapSnapshotNodesProvider(this, diffForClass.addedIndexes); |
| } |
| |
| /** |
| * @param {!Array.<number>} nodeIndexes |
| * @return {!HeapSnapshotNodesProvider} |
| */ |
| createDeletedNodesProvider(nodeIndexes) { |
| return new HeapSnapshotNodesProvider(this, nodeIndexes); |
| } |
| |
| /** |
| * @param {string} className |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.NodeFilter} nodeFilter |
| * @return {!HeapSnapshotNodesProvider} |
| */ |
| createNodesProviderForClass(className, nodeFilter) { |
| return new HeapSnapshotNodesProvider(this, this.aggregatesWithFilter(nodeFilter)[className].idxs); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _maxJsNodeId() { |
| const nodeFieldCount = this._nodeFieldCount; |
| const nodes = this.nodes; |
| const nodesLength = nodes.length; |
| let id = 0; |
| for (let nodeIndex = this._nodeIdOffset; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { |
| const nextId = nodes[nodeIndex]; |
| // JS objects have odd ids, skip native objects. |
| if (nextId % 2 === 0) { |
| continue; |
| } |
| if (id < nextId) { |
| id = nextId; |
| } |
| } |
| return id; |
| } |
| |
| /** |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.StaticData} |
| */ |
| updateStaticData() { |
| return new HeapSnapshotModel.HeapSnapshotModel.StaticData( |
| this.nodeCount, this._rootNodeIndex, this.totalSize, this._maxJsNodeId()); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| const HeapSnapshotMetainfo = class { |
| constructor() { |
| // New format. |
| this.node_fields = []; |
| this.node_types = []; |
| this.edge_fields = []; |
| this.edge_types = []; |
| this.trace_function_info_fields = []; |
| this.trace_node_fields = []; |
| this.sample_fields = []; |
| this.type_strings = {}; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshotHeader { |
| constructor() { |
| // New format. |
| this.title = ''; |
| this.meta = new HeapSnapshotMetainfo(); |
| this.node_count = 0; |
| this.edge_count = 0; |
| this.trace_function_count = 0; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshotItemProvider { |
| /** |
| * @param {!HeapSnapshotItemIterator} iterator |
| * @param {!HeapSnapshotItemIndexProvider} indexProvider |
| */ |
| constructor(iterator, indexProvider) { |
| this._iterator = iterator; |
| this._indexProvider = indexProvider; |
| this._isEmpty = !iterator.hasNext(); |
| /** @type {?Array.<number>} */ |
| this._iterationOrder = null; |
| this._currentComparator = null; |
| this._sortedPrefixLength = 0; |
| this._sortedSuffixLength = 0; |
| } |
| |
| _createIterationOrder() { |
| if (this._iterationOrder) { |
| return; |
| } |
| this._iterationOrder = []; |
| for (let iterator = this._iterator; iterator.hasNext(); iterator.next()) { |
| this._iterationOrder.push(iterator.item().itemIndex()); |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isEmpty() { |
| return this._isEmpty; |
| } |
| |
| /** |
| * @param {number} begin |
| * @param {number} end |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.ItemsRange} |
| */ |
| serializeItemsRange(begin, end) { |
| this._createIterationOrder(); |
| if (begin > end) { |
| throw new Error('Start position > end position: ' + begin + ' > ' + end); |
| } |
| if (end > this._iterationOrder.length) { |
| end = this._iterationOrder.length; |
| } |
| if (this._sortedPrefixLength < end && begin < this._iterationOrder.length - this._sortedSuffixLength) { |
| this.sort( |
| this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1 - this._sortedSuffixLength, |
| begin, end - 1); |
| if (begin <= this._sortedPrefixLength) { |
| this._sortedPrefixLength = end; |
| } |
| if (end >= this._iterationOrder.length - this._sortedSuffixLength) { |
| this._sortedSuffixLength = this._iterationOrder.length - begin; |
| } |
| } |
| let position = begin; |
| const count = end - begin; |
| const result = new Array(count); |
| for (let i = 0; i < count; ++i) { |
| const itemIndex = this._iterationOrder[position++]; |
| const item = this._indexProvider.itemForIndex(itemIndex); |
| result[i] = item.serialize(); |
| } |
| return new HeapSnapshotModel.HeapSnapshotModel.ItemsRange(begin, end, this._iterationOrder.length, result); |
| } |
| |
| sortAndRewind(comparator) { |
| this._currentComparator = comparator; |
| this._sortedPrefixLength = 0; |
| this._sortedSuffixLength = 0; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshotEdgesProvider extends HeapSnapshotItemProvider { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| * @param {?function(!HeapSnapshotEdge):boolean} filter |
| * @param {!HeapSnapshotEdgeIterator} edgesIter |
| * @param {!HeapSnapshotItemIndexProvider} indexProvider |
| */ |
| constructor(snapshot, filter, edgesIter, indexProvider) { |
| const iter = filter ? |
| new HeapSnapshotFilteredIterator(edgesIter, /** @type {function(!HeapSnapshotItem):boolean} */ (filter)) : |
| edgesIter; |
| super(iter, indexProvider); |
| this.snapshot = snapshot; |
| } |
| |
| /** |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} comparator |
| * @param {number} leftBound |
| * @param {number} rightBound |
| * @param {number} windowLeft |
| * @param {number} windowRight |
| */ |
| sort(comparator, leftBound, rightBound, windowLeft, windowRight) { |
| const fieldName1 = comparator.fieldName1; |
| const fieldName2 = comparator.fieldName2; |
| const ascending1 = comparator.ascending1; |
| const ascending2 = comparator.ascending2; |
| |
| const edgeA = /** @type {!HeapSnapshotEdge} */ (this._iterator.item()).clone(); |
| const edgeB = edgeA.clone(); |
| const nodeA = this.snapshot.createNode(); |
| const nodeB = this.snapshot.createNode(); |
| |
| function compareEdgeFieldName(ascending, indexA, indexB) { |
| edgeA.edgeIndex = indexA; |
| edgeB.edgeIndex = indexB; |
| if (edgeB.name() === '__proto__') { |
| return -1; |
| } |
| if (edgeA.name() === '__proto__') { |
| return 1; |
| } |
| const result = edgeA.hasStringName() === edgeB.hasStringName() ? |
| (edgeA.name() < edgeB.name() ? -1 : (edgeA.name() > edgeB.name() ? 1 : 0)) : |
| (edgeA.hasStringName() ? -1 : 1); |
| return ascending ? result : -result; |
| } |
| |
| function compareNodeField(fieldName, ascending, indexA, indexB) { |
| edgeA.edgeIndex = indexA; |
| nodeA.nodeIndex = edgeA.nodeIndex(); |
| const valueA = nodeA[fieldName](); |
| |
| edgeB.edgeIndex = indexB; |
| nodeB.nodeIndex = edgeB.nodeIndex(); |
| const valueB = nodeB[fieldName](); |
| |
| const result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0); |
| return ascending ? result : -result; |
| } |
| |
| function compareEdgeAndNode(indexA, indexB) { |
| let result = compareEdgeFieldName(ascending1, indexA, indexB); |
| if (result === 0) { |
| result = compareNodeField(fieldName2, ascending2, indexA, indexB); |
| } |
| if (result === 0) { |
| return indexA - indexB; |
| } |
| return result; |
| } |
| |
| function compareNodeAndEdge(indexA, indexB) { |
| let result = compareNodeField(fieldName1, ascending1, indexA, indexB); |
| if (result === 0) { |
| result = compareEdgeFieldName(ascending2, indexA, indexB); |
| } |
| if (result === 0) { |
| return indexA - indexB; |
| } |
| return result; |
| } |
| |
| function compareNodeAndNode(indexA, indexB) { |
| let result = compareNodeField(fieldName1, ascending1, indexA, indexB); |
| if (result === 0) { |
| result = compareNodeField(fieldName2, ascending2, indexA, indexB); |
| } |
| if (result === 0) { |
| return indexA - indexB; |
| } |
| return result; |
| } |
| |
| if (fieldName1 === '!edgeName') { |
| this._iterationOrder.sortRange(compareEdgeAndNode, leftBound, rightBound, windowLeft, windowRight); |
| } else if (fieldName2 === '!edgeName') { |
| this._iterationOrder.sortRange(compareNodeAndEdge, leftBound, rightBound, windowLeft, windowRight); |
| } else { |
| this._iterationOrder.sortRange(compareNodeAndNode, leftBound, rightBound, windowLeft, windowRight); |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class HeapSnapshotNodesProvider extends HeapSnapshotItemProvider { |
| /** |
| * @param {!HeapSnapshot} snapshot |
| * @param {!Array<number>|!Uint32Array} nodeIndexes |
| */ |
| constructor(snapshot, nodeIndexes) { |
| const indexProvider = new HeapSnapshotNodeIndexProvider(snapshot); |
| const it = new HeapSnapshotIndexRangeIterator(indexProvider, nodeIndexes); |
| super(it, indexProvider); |
| this.snapshot = snapshot; |
| } |
| |
| /** |
| * @param {string} snapshotObjectId |
| * @return {number} |
| */ |
| nodePosition(snapshotObjectId) { |
| this._createIterationOrder(); |
| const node = this.snapshot.createNode(); |
| let i = 0; |
| for (; i < this._iterationOrder.length; i++) { |
| node.nodeIndex = this._iterationOrder[i]; |
| if (node.id() === snapshotObjectId) { |
| break; |
| } |
| } |
| if (i === this._iterationOrder.length) { |
| return -1; |
| } |
| const targetNodeIndex = this._iterationOrder[i]; |
| let smallerCount = 0; |
| const compare = this._buildCompareFunction(this._currentComparator); |
| for (let i = 0; i < this._iterationOrder.length; i++) { |
| if (compare(this._iterationOrder[i], targetNodeIndex) < 0) { |
| ++smallerCount; |
| } |
| } |
| return smallerCount; |
| } |
| |
| /** |
| * @return {function(number,number):number} |
| */ |
| _buildCompareFunction(comparator) { |
| const nodeA = this.snapshot.createNode(); |
| const nodeB = this.snapshot.createNode(); |
| const fieldAccessor1 = nodeA[comparator.fieldName1]; |
| const fieldAccessor2 = nodeA[comparator.fieldName2]; |
| const ascending1 = comparator.ascending1 ? 1 : -1; |
| const ascending2 = comparator.ascending2 ? 1 : -1; |
| |
| /** |
| * @param {function():*} fieldAccessor |
| * @param {number} ascending |
| * @return {number} |
| */ |
| function sortByNodeField(fieldAccessor, ascending) { |
| const valueA = fieldAccessor.call(nodeA); |
| const valueB = fieldAccessor.call(nodeB); |
| return valueA < valueB ? -ascending : (valueA > valueB ? ascending : 0); |
| } |
| |
| /** |
| * @param {number} indexA |
| * @param {number} indexB |
| * @return {number} |
| */ |
| function sortByComparator(indexA, indexB) { |
| nodeA.nodeIndex = indexA; |
| nodeB.nodeIndex = indexB; |
| let result = sortByNodeField(fieldAccessor1, ascending1); |
| if (result === 0) { |
| result = sortByNodeField(fieldAccessor2, ascending2); |
| } |
| return result || indexA - indexB; |
| } |
| |
| return sortByComparator; |
| } |
| |
| /** |
| * @param {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} comparator |
| * @param {number} leftBound |
| * @param {number} rightBound |
| * @param {number} windowLeft |
| * @param {number} windowRight |
| */ |
| sort(comparator, leftBound, rightBound, windowLeft, windowRight) { |
| this._iterationOrder.sortRange( |
| this._buildCompareFunction(comparator), leftBound, rightBound, windowLeft, windowRight); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class JSHeapSnapshot extends HeapSnapshot { |
| /** |
| * @param {!Object} profile |
| * @param {!HeapSnapshotProgress} progress |
| */ |
| constructor(profile, progress) { |
| super(profile, progress); |
| this._nodeFlags = { |
| // bit flags |
| canBeQueried: 1, |
| detachedDOMTreeNode: 2, |
| pageObject: 4 // The idea is to track separately the objects owned by the page and the objects owned by debugger. |
| }; |
| this._lazyStringCache = {}; |
| this.initialize(); |
| } |
| |
| /** |
| * @override |
| * @param {number=} nodeIndex |
| * @return {!JSHeapSnapshotNode} |
| */ |
| createNode(nodeIndex) { |
| return new JSHeapSnapshotNode(this, nodeIndex === undefined ? -1 : nodeIndex); |
| } |
| |
| /** |
| * @override |
| * @param {number} edgeIndex |
| * @return {!JSHeapSnapshotEdge} |
| */ |
| createEdge(edgeIndex) { |
| return new JSHeapSnapshotEdge(this, edgeIndex); |
| } |
| |
| /** |
| * @override |
| * @param {number} retainerIndex |
| * @return {!JSHeapSnapshotRetainerEdge} |
| */ |
| createRetainingEdge(retainerIndex) { |
| return new JSHeapSnapshotRetainerEdge(this, retainerIndex); |
| } |
| |
| /** |
| * @override |
| * @return {function(!HeapSnapshotEdge):boolean} |
| */ |
| containmentEdgesFilter() { |
| return edge => !edge.isInvisible(); |
| } |
| |
| /** |
| * @override |
| * @return {function(!HeapSnapshotEdge):boolean} |
| */ |
| retainingEdgesFilter() { |
| const containmentEdgesFilter = this.containmentEdgesFilter(); |
| function filter(edge) { |
| return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak(); |
| } |
| return filter; |
| } |
| |
| /** |
| * @override |
| */ |
| calculateFlags() { |
| this._flags = new Uint32Array(this.nodeCount); |
| this._markDetachedDOMTreeNodes(); |
| this._markQueriableHeapObjects(); |
| this._markPageOwnedNodes(); |
| } |
| |
| /** |
| * @override |
| */ |
| calculateDistances() { |
| /** |
| * @param {!HeapSnapshotNode} node |
| * @param {!HeapSnapshotEdge} edge |
| * @return {boolean} |
| */ |
| function filter(node, edge) { |
| if (node.isHidden()) { |
| return edge.name() !== 'sloppy_function_map' || node.rawName() !== 'system / NativeContext'; |
| } |
| if (node.isArray()) { |
| // DescriptorArrays are fixed arrays used to hold instance descriptors. |
| // The format of the these objects is: |
| // [0]: Number of descriptors |
| // [1]: Either Smi(0) if uninitialized, or a pointer to small fixed array: |
| // [0]: pointer to fixed array with enum cache |
| // [1]: either Smi(0) or pointer to fixed array with indices |
| // [i*3+2]: i-th key |
| // [i*3+3]: i-th type |
| // [i*3+4]: i-th descriptor |
| // As long as maps may share descriptor arrays some of the descriptor |
| // links may not be valid for all the maps. We just skip |
| // all the descriptor links when calculating distances. |
| // For more details see http://crbug.com/413608 |
| if (node.rawName() !== '(map descriptors)') { |
| return true; |
| } |
| const index = edge.name(); |
| return index < 2 || (index % 3) !== 1; |
| } |
| return true; |
| } |
| super.calculateDistances(filter); |
| } |
| |
| /** |
| * @override |
| * @protected |
| * @param {!HeapSnapshotNode} node |
| * @return {boolean} |
| */ |
| isUserRoot(node) { |
| return node.isUserRoot() || node.isDocumentDOMTreesRoot(); |
| } |
| |
| /** |
| * @override |
| * @return {?{map: !Uint32Array, flag: number}} |
| */ |
| userObjectsMapAndFlag() { |
| return {map: this._flags, flag: this._nodeFlags.pageObject}; |
| } |
| |
| /** |
| * @param {!HeapSnapshotNode} node |
| * @return {number} |
| */ |
| _flagsOfNode(node) { |
| return this._flags[node.nodeIndex / this._nodeFieldCount]; |
| } |
| |
| _markDetachedDOMTreeNodes() { |
| const nodes = this.nodes; |
| const nodesLength = nodes.length; |
| const nodeFieldCount = this._nodeFieldCount; |
| const nodeNativeType = this._nodeNativeType; |
| const nodeTypeOffset = this._nodeTypeOffset; |
| const flag = this._nodeFlags.detachedDOMTreeNode; |
| const node = this.rootNode(); |
| for (let nodeIndex = 0, ordinal = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount, ordinal++) { |
| const nodeType = nodes[nodeIndex + nodeTypeOffset]; |
| if (nodeType !== nodeNativeType) { |
| continue; |
| } |
| node.nodeIndex = nodeIndex; |
| if (node.name().startsWith('Detached ')) { |
| this._flags[ordinal] |= flag; |
| } |
| } |
| } |
| |
| _markQueriableHeapObjects() { |
| // Allow runtime properties query for objects accessible from Window objects |
| // via regular properties, and for DOM wrappers. Trying to access random objects |
| // can cause a crash due to insonsistent state of internal properties of wrappers. |
| const flag = this._nodeFlags.canBeQueried; |
| const hiddenEdgeType = this._edgeHiddenType; |
| const internalEdgeType = this._edgeInternalType; |
| const invisibleEdgeType = this._edgeInvisibleType; |
| const weakEdgeType = this._edgeWeakType; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const containmentEdges = this.containmentEdges; |
| const nodeFieldCount = this._nodeFieldCount; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| |
| const flags = this._flags; |
| const list = []; |
| |
| for (let iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { |
| if (iter.edge.node().isUserRoot()) { |
| list.push(iter.edge.node().nodeIndex / nodeFieldCount); |
| } |
| } |
| |
| while (list.length) { |
| const nodeOrdinal = list.pop(); |
| if (flags[nodeOrdinal] & flag) { |
| continue; |
| } |
| flags[nodeOrdinal] |= flag; |
| const beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; |
| const endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; |
| for (let edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { |
| const childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| const childNodeOrdinal = childNodeIndex / nodeFieldCount; |
| if (flags[childNodeOrdinal] & flag) { |
| continue; |
| } |
| const type = containmentEdges[edgeIndex + edgeTypeOffset]; |
| if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || |
| type === weakEdgeType) { |
| continue; |
| } |
| list.push(childNodeOrdinal); |
| } |
| } |
| } |
| |
| _markPageOwnedNodes() { |
| const edgeShortcutType = this._edgeShortcutType; |
| const edgeElementType = this._edgeElementType; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const edgeWeakType = this._edgeWeakType; |
| const firstEdgeIndexes = this._firstEdgeIndexes; |
| const containmentEdges = this.containmentEdges; |
| const nodeFieldCount = this._nodeFieldCount; |
| const nodesCount = this.nodeCount; |
| |
| const flags = this._flags; |
| const pageObjectFlag = this._nodeFlags.pageObject; |
| |
| const nodesToVisit = new Uint32Array(nodesCount); |
| let nodesToVisitLength = 0; |
| |
| const rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; |
| const node = this.rootNode(); |
| |
| // Populate the entry points. They are Window objects and DOM Tree Roots. |
| for (let edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1]; |
| edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { |
| const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; |
| const nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| if (edgeType === edgeElementType) { |
| node.nodeIndex = nodeIndex; |
| if (!node.isDocumentDOMTreesRoot()) { |
| continue; |
| } |
| } else if (edgeType !== edgeShortcutType) { |
| continue; |
| } |
| const nodeOrdinal = nodeIndex / nodeFieldCount; |
| nodesToVisit[nodesToVisitLength++] = nodeOrdinal; |
| flags[nodeOrdinal] |= pageObjectFlag; |
| } |
| |
| // Mark everything reachable with the pageObject flag. |
| while (nodesToVisitLength) { |
| const nodeOrdinal = nodesToVisit[--nodesToVisitLength]; |
| const beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; |
| const endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; |
| for (let edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { |
| const childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| const childNodeOrdinal = childNodeIndex / nodeFieldCount; |
| if (flags[childNodeOrdinal] & pageObjectFlag) { |
| continue; |
| } |
| const type = containmentEdges[edgeIndex + edgeTypeOffset]; |
| if (type === edgeWeakType) { |
| continue; |
| } |
| nodesToVisit[nodesToVisitLength++] = childNodeOrdinal; |
| flags[childNodeOrdinal] |= pageObjectFlag; |
| } |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| calculateStatistics() { |
| const nodeFieldCount = this._nodeFieldCount; |
| const nodes = this.nodes; |
| const nodesLength = nodes.length; |
| const nodeTypeOffset = this._nodeTypeOffset; |
| const nodeSizeOffset = this._nodeSelfSizeOffset; |
| const nodeNativeType = this._nodeNativeType; |
| const nodeCodeType = this._nodeCodeType; |
| const nodeConsStringType = this._nodeConsStringType; |
| const nodeSlicedStringType = this._nodeSlicedStringType; |
| const distances = this._nodeDistances; |
| let sizeNative = 0; |
| let sizeCode = 0; |
| let sizeStrings = 0; |
| let sizeJSArrays = 0; |
| let sizeSystem = 0; |
| const node = this.rootNode(); |
| for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { |
| const nodeSize = nodes[nodeIndex + nodeSizeOffset]; |
| const ordinal = nodeIndex / nodeFieldCount; |
| if (distances[ordinal] >= HeapSnapshotModel.HeapSnapshotModel.baseSystemDistance) { |
| sizeSystem += nodeSize; |
| continue; |
| } |
| const nodeType = nodes[nodeIndex + nodeTypeOffset]; |
| node.nodeIndex = nodeIndex; |
| if (nodeType === nodeNativeType) { |
| sizeNative += nodeSize; |
| } else if (nodeType === nodeCodeType) { |
| sizeCode += nodeSize; |
| } else if (nodeType === nodeConsStringType || nodeType === nodeSlicedStringType || node.type() === 'string') { |
| sizeStrings += nodeSize; |
| } else if (node.name() === 'Array') { |
| sizeJSArrays += this._calculateArraySize(node); |
| } |
| } |
| this._statistics = new HeapSnapshotModel.HeapSnapshotModel.Statistics(); |
| this._statistics.total = this.totalSize; |
| this._statistics.v8heap = this.totalSize - sizeNative; |
| this._statistics.native = sizeNative; |
| this._statistics.code = sizeCode; |
| this._statistics.jsArrays = sizeJSArrays; |
| this._statistics.strings = sizeStrings; |
| this._statistics.system = sizeSystem; |
| } |
| |
| /** |
| * @param {!HeapSnapshotNode} node |
| * @return {number} |
| */ |
| _calculateArraySize(node) { |
| let size = node.selfSize(); |
| const beginEdgeIndex = node.edgeIndexesStart(); |
| const endEdgeIndex = node.edgeIndexesEnd(); |
| const containmentEdges = this.containmentEdges; |
| const strings = this.strings; |
| const edgeToNodeOffset = this._edgeToNodeOffset; |
| const edgeTypeOffset = this._edgeTypeOffset; |
| const edgeNameOffset = this._edgeNameOffset; |
| const edgeFieldsCount = this._edgeFieldsCount; |
| const edgeInternalType = this._edgeInternalType; |
| for (let edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { |
| const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; |
| if (edgeType !== edgeInternalType) { |
| continue; |
| } |
| const edgeName = strings[containmentEdges[edgeIndex + edgeNameOffset]]; |
| if (edgeName !== 'elements') { |
| continue; |
| } |
| const elementsNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; |
| node.nodeIndex = elementsNodeIndex; |
| if (node.retainersCount() === 1) { |
| size += node.selfSize(); |
| } |
| break; |
| } |
| return size; |
| } |
| |
| /** |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.Statistics} |
| */ |
| getStatistics() { |
| return this._statistics; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class JSHeapSnapshotNode extends HeapSnapshotNode { |
| /** |
| * @param {!JSHeapSnapshot} snapshot |
| * @param {number=} nodeIndex |
| */ |
| constructor(snapshot, nodeIndex) { |
| super(snapshot, nodeIndex); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| canBeQueried() { |
| const flags = this._snapshot._flagsOfNode(this); |
| return !!(flags & this._snapshot._nodeFlags.canBeQueried); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| rawName() { |
| return super.name(); |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| name() { |
| const snapshot = this._snapshot; |
| if (this.rawType() === snapshot._nodeConsStringType) { |
| let string = snapshot._lazyStringCache[this.nodeIndex]; |
| if (typeof string === 'undefined') { |
| string = this._consStringName(); |
| snapshot._lazyStringCache[this.nodeIndex] = string; |
| } |
| return string; |
| } |
| return this.rawName(); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| _consStringName() { |
| const snapshot = this._snapshot; |
| const consStringType = snapshot._nodeConsStringType; |
| const edgeInternalType = snapshot._edgeInternalType; |
| const edgeFieldsCount = snapshot._edgeFieldsCount; |
| const edgeToNodeOffset = snapshot._edgeToNodeOffset; |
| const edgeTypeOffset = snapshot._edgeTypeOffset; |
| const edgeNameOffset = snapshot._edgeNameOffset; |
| const strings = snapshot.strings; |
| const edges = snapshot.containmentEdges; |
| const firstEdgeIndexes = snapshot._firstEdgeIndexes; |
| const nodeFieldCount = snapshot._nodeFieldCount; |
| const nodeTypeOffset = snapshot._nodeTypeOffset; |
| const nodeNameOffset = snapshot._nodeNameOffset; |
| const nodes = snapshot.nodes; |
| const nodesStack = []; |
| nodesStack.push(this.nodeIndex); |
| let name = ''; |
| |
| while (nodesStack.length && name.length < 1024) { |
| const nodeIndex = nodesStack.pop(); |
| if (nodes[nodeIndex + nodeTypeOffset] !== consStringType) { |
| name += strings[nodes[nodeIndex + nodeNameOffset]]; |
| continue; |
| } |
| const nodeOrdinal = nodeIndex / nodeFieldCount; |
| const beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; |
| const endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; |
| let firstNodeIndex = 0; |
| let secondNodeIndex = 0; |
| for (let edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex && (!firstNodeIndex || !secondNodeIndex); |
| edgeIndex += edgeFieldsCount) { |
| const edgeType = edges[edgeIndex + edgeTypeOffset]; |
| if (edgeType === edgeInternalType) { |
| const edgeName = strings[edges[edgeIndex + edgeNameOffset]]; |
| if (edgeName === 'first') { |
| firstNodeIndex = edges[edgeIndex + edgeToNodeOffset]; |
| } else if (edgeName === 'second') { |
| secondNodeIndex = edges[edgeIndex + edgeToNodeOffset]; |
| } |
| } |
| } |
| nodesStack.push(secondNodeIndex); |
| nodesStack.push(firstNodeIndex); |
| } |
| return name; |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| className() { |
| const type = this.type(); |
| switch (type) { |
| case 'hidden': |
| return '(system)'; |
| case 'object': |
| case 'native': |
| return this.name(); |
| case 'code': |
| return '(compiled code)'; |
| default: |
| return '(' + type + ')'; |
| } |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| classIndex() { |
| const snapshot = this._snapshot; |
| const nodes = snapshot.nodes; |
| const type = nodes[this.nodeIndex + snapshot._nodeTypeOffset]; |
| if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType) { |
| return nodes[this.nodeIndex + snapshot._nodeNameOffset]; |
| } |
| return -1 - type; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| id() { |
| const snapshot = this._snapshot; |
| return snapshot.nodes[this.nodeIndex + snapshot._nodeIdOffset]; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isHidden() { |
| return this.rawType() === this._snapshot._nodeHiddenType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isArray() { |
| return this.rawType() === this._snapshot._nodeArrayType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isSynthetic() { |
| return this.rawType() === this._snapshot._nodeSyntheticType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isUserRoot() { |
| return !this.isSynthetic(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isDocumentDOMTreesRoot() { |
| return this.isSynthetic() && this.name() === '(Document DOM trees)'; |
| } |
| |
| /** |
| * @override |
| * @return {!HeapSnapshotModel.HeapSnapshotModel.Node} |
| */ |
| serialize() { |
| const result = super.serialize(); |
| const flags = this._snapshot._flagsOfNode(this); |
| if (flags & this._snapshot._nodeFlags.canBeQueried) { |
| result.canBeQueried = true; |
| } |
| if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode) { |
| result.detachedDOMTreeNode = true; |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class JSHeapSnapshotEdge extends HeapSnapshotEdge { |
| /** |
| * @param {!JSHeapSnapshot} snapshot |
| * @param {number=} edgeIndex |
| */ |
| constructor(snapshot, edgeIndex) { |
| super(snapshot, edgeIndex); |
| } |
| |
| /** |
| * @override |
| * @return {!JSHeapSnapshotEdge} |
| */ |
| clone() { |
| const snapshot = /** @type {!JSHeapSnapshot} */ (this._snapshot); |
| return new JSHeapSnapshotEdge(snapshot, this.edgeIndex); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasStringName() { |
| if (!this.isShortcut()) { |
| return this._hasStringName(); |
| } |
| return isNaN(parseInt(this._name(), 10)); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isElement() { |
| return this.rawType() === this._snapshot._edgeElementType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isHidden() { |
| return this.rawType() === this._snapshot._edgeHiddenType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isWeak() { |
| return this.rawType() === this._snapshot._edgeWeakType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isInternal() { |
| return this.rawType() === this._snapshot._edgeInternalType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isInvisible() { |
| return this.rawType() === this._snapshot._edgeInvisibleType; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isShortcut() { |
| return this.rawType() === this._snapshot._edgeShortcutType; |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| name() { |
| const name = this._name(); |
| if (!this.isShortcut()) { |
| return String(name); |
| } |
| const numName = parseInt(name, 10); |
| return String(isNaN(numName) ? name : numName); |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| toString() { |
| const name = this.name(); |
| switch (this.type()) { |
| case 'context': |
| return '->' + name; |
| case 'element': |
| return '[' + name + ']'; |
| case 'weak': |
| return '[[' + name + ']]'; |
| case 'property': |
| return name.indexOf(' ') === -1 ? '.' + name : '["' + name + '"]'; |
| case 'shortcut': |
| if (typeof name === 'string') { |
| return name.indexOf(' ') === -1 ? '.' + name : '["' + name + '"]'; |
| } |
| return '[' + name + ']'; |
| case 'internal': |
| case 'hidden': |
| case 'invisible': |
| return '{' + name + '}'; |
| } |
| return '?' + name + '?'; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| _hasStringName() { |
| const type = this.rawType(); |
| const snapshot = this._snapshot; |
| return type !== snapshot._edgeElementType && type !== snapshot._edgeHiddenType; |
| } |
| |
| /** |
| * @return {string|number} |
| */ |
| _name() { |
| return this._hasStringName() ? this._snapshot.strings[this._nameOrIndex()] : this._nameOrIndex(); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _nameOrIndex() { |
| return this._edges[this.edgeIndex + this._snapshot._edgeNameOffset]; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| rawType() { |
| return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset]; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class JSHeapSnapshotRetainerEdge extends HeapSnapshotRetainerEdge { |
| /** |
| * @param {!JSHeapSnapshot} snapshot |
| * @param {number} retainerIndex |
| */ |
| constructor(snapshot, retainerIndex) { |
| super(snapshot, retainerIndex); |
| } |
| |
| /** |
| * @override |
| * @return {!JSHeapSnapshotRetainerEdge} |
| */ |
| clone() { |
| const snapshot = /** @type {!JSHeapSnapshot} */ (this._snapshot); |
| return new JSHeapSnapshotRetainerEdge(snapshot, this.retainerIndex()); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isHidden() { |
| return this._edge().isHidden(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isInternal() { |
| return this._edge().isInternal(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isInvisible() { |
| return this._edge().isInvisible(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isShortcut() { |
| return this._edge().isShortcut(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isWeak() { |
| return this._edge().isWeak(); |
| } |
| } |
| |
| (function disableLoggingForTest() { |
| // Runtime doesn't exist because this file is loaded as a one-off |
| // file in some inspector-protocol tests. |
| if (self.Root && self.Root.Runtime && Root.Runtime.queryParam('test')) { |
| console.warn = () => undefined; |
| } |
| })(); |