| /* |
| * 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. |
| */ |
| /** |
| * @implements {Profiler.ProfileType.DataDisplayDelegate} |
| * @implements {UI.Searchable} |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView = class extends UI.SimpleView { |
| /** |
| * @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate |
| * @param {!Profiler.HeapProfileHeader} profile |
| */ |
| constructor(dataDisplayDelegate, profile) { |
| super(Common.UIString('Heap Snapshot')); |
| |
| this.element.classList.add('heap-snapshot-view'); |
| |
| profile.profileType().addEventListener( |
| Profiler.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this); |
| profile.profileType().addEventListener( |
| Profiler.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this); |
| |
| var isHeapTimeline = profile.profileType().id === Profiler.TrackingHeapSnapshotProfileType.TypeId; |
| if (isHeapTimeline) { |
| this._trackingOverviewGrid = new Profiler.HeapTrackingOverviewGrid(profile); |
| this._trackingOverviewGrid.addEventListener( |
| Profiler.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this)); |
| } |
| |
| this._parentDataDisplayDelegate = dataDisplayDelegate; |
| |
| this._searchableView = new UI.SearchableView(this); |
| this._searchableView.show(this.element); |
| |
| this._splitWidget = new UI.SplitWidget(false, true, 'heapSnapshotSplitViewState', 200, 200); |
| this._splitWidget.show(this._searchableView.element); |
| |
| this._containmentDataGrid = new Profiler.HeapSnapshotContainmentDataGrid(this); |
| this._containmentDataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| this._containmentWidget = this._containmentDataGrid.asWidget(); |
| this._containmentWidget.setMinimumSize(50, 25); |
| |
| this._statisticsView = new Profiler.HeapSnapshotStatisticsView(); |
| |
| this._constructorsDataGrid = new Profiler.HeapSnapshotConstructorsDataGrid(this); |
| this._constructorsDataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| this._constructorsWidget = this._constructorsDataGrid.asWidget(); |
| this._constructorsWidget.setMinimumSize(50, 25); |
| |
| this._diffDataGrid = new Profiler.HeapSnapshotDiffDataGrid(this); |
| this._diffDataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| this._diffWidget = this._diffDataGrid.asWidget(); |
| this._diffWidget.setMinimumSize(50, 25); |
| |
| if (isHeapTimeline && Common.moduleSetting('recordAllocationStacks').get()) { |
| this._allocationDataGrid = new Profiler.AllocationDataGrid(profile.target(), this); |
| this._allocationDataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._onSelectAllocationNode, this); |
| this._allocationWidget = this._allocationDataGrid.asWidget(); |
| this._allocationWidget.setMinimumSize(50, 25); |
| |
| this._allocationStackView = new Profiler.HeapAllocationStackView(profile.target()); |
| this._allocationStackView.setMinimumSize(50, 25); |
| |
| this._tabbedPane = new UI.TabbedPane(); |
| } |
| |
| this._retainmentDataGrid = new Profiler.HeapSnapshotRetainmentDataGrid(this); |
| this._retainmentWidget = this._retainmentDataGrid.asWidget(); |
| this._retainmentWidget.setMinimumSize(50, 21); |
| this._retainmentWidget.element.classList.add('retaining-paths-view'); |
| |
| var splitWidgetResizer; |
| if (this._allocationStackView) { |
| this._tabbedPane = new UI.TabbedPane(); |
| |
| this._tabbedPane.appendTab('retainers', Common.UIString('Retainers'), this._retainmentWidget); |
| this._tabbedPane.appendTab('allocation-stack', Common.UIString('Allocation stack'), this._allocationStackView); |
| |
| splitWidgetResizer = this._tabbedPane.headerElement(); |
| this._objectDetailsView = this._tabbedPane; |
| } else { |
| var retainmentViewHeader = createElementWithClass('div', 'heap-snapshot-view-resizer'); |
| var retainingPathsTitleDiv = retainmentViewHeader.createChild('div', 'title'); |
| var retainingPathsTitle = retainingPathsTitleDiv.createChild('span'); |
| retainingPathsTitle.textContent = Common.UIString('Retainers'); |
| |
| splitWidgetResizer = retainmentViewHeader; |
| this._objectDetailsView = new UI.VBox(); |
| this._objectDetailsView.element.appendChild(retainmentViewHeader); |
| this._retainmentWidget.show(this._objectDetailsView.element); |
| } |
| this._splitWidget.hideDefaultResizer(); |
| this._splitWidget.installResizer(splitWidgetResizer); |
| |
| this._retainmentDataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this); |
| this._retainmentDataGrid.reset(); |
| |
| this._perspectives = []; |
| this._perspectives.push(new Profiler.HeapSnapshotView.SummaryPerspective()); |
| if (profile.profileType() !== Profiler.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) |
| this._perspectives.push(new Profiler.HeapSnapshotView.ComparisonPerspective()); |
| this._perspectives.push(new Profiler.HeapSnapshotView.ContainmentPerspective()); |
| if (this._allocationWidget) |
| this._perspectives.push(new Profiler.HeapSnapshotView.AllocationPerspective()); |
| this._perspectives.push(new Profiler.HeapSnapshotView.StatisticsPerspective()); |
| |
| this._perspectiveSelect = new UI.ToolbarComboBox(this._onSelectedPerspectiveChanged.bind(this)); |
| for (var i = 0; i < this._perspectives.length; ++i) |
| this._perspectiveSelect.createOption(this._perspectives[i].title()); |
| |
| this._profile = profile; |
| |
| this._baseSelect = new UI.ToolbarComboBox(this._changeBase.bind(this)); |
| this._baseSelect.setVisible(false); |
| this._updateBaseOptions(); |
| |
| this._filterSelect = new UI.ToolbarComboBox(this._changeFilter.bind(this)); |
| this._filterSelect.setVisible(false); |
| this._updateFilterOptions(); |
| |
| this._classNameFilter = new UI.ToolbarInput('Class filter'); |
| this._classNameFilter.setVisible(false); |
| this._constructorsDataGrid.setNameFilter(this._classNameFilter); |
| this._diffDataGrid.setNameFilter(this._classNameFilter); |
| |
| this._selectedSizeText = new UI.ToolbarText(); |
| |
| this._popoverHelper = new Components.ObjectPopoverHelper( |
| this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); |
| |
| this._currentPerspectiveIndex = 0; |
| this._currentPerspective = this._perspectives[0]; |
| this._currentPerspective.activate(this); |
| this._dataGrid = this._currentPerspective.masterGrid(this); |
| |
| this._populate(); |
| this._searchThrottler = new Common.Throttler(0); |
| } |
| |
| /** |
| * @return {!UI.SearchableView} |
| */ |
| searchableView() { |
| return this._searchableView; |
| } |
| |
| /** |
| * @override |
| * @param {?Profiler.ProfileHeader} profile |
| * @return {?UI.Widget} |
| */ |
| showProfile(profile) { |
| return this._parentDataDisplayDelegate.showProfile(profile); |
| } |
| |
| /** |
| * @override |
| * @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} snapshotObjectId |
| * @param {string} perspectiveName |
| */ |
| showObject(snapshotObjectId, perspectiveName) { |
| if (snapshotObjectId <= this._profile.maxJSObjectId) |
| this.selectLiveObject(perspectiveName, snapshotObjectId); |
| else |
| this._parentDataDisplayDelegate.showObject(snapshotObjectId, perspectiveName); |
| } |
| |
| _populate() { |
| this._profile._loadPromise |
| .then(heapSnapshotProxy => { |
| heapSnapshotProxy.getStatistics().then(this._gotStatistics.bind(this)); |
| this._dataGrid.setDataSource(heapSnapshotProxy); |
| if (this._profile.profileType().id === Profiler.TrackingHeapSnapshotProfileType.TypeId && |
| this._profile.fromFile()) |
| return heapSnapshotProxy.getSamples().then(samples => this._trackingOverviewGrid._setSamples(samples)); |
| }) |
| .then(_ => { |
| var list = this._profiles(); |
| var profileIndex = list.indexOf(this._profile); |
| this._baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1)); |
| if (this._trackingOverviewGrid) |
| this._trackingOverviewGrid._updateGrid(); |
| }); |
| } |
| |
| /** |
| * @param {!Profiler.HeapSnapshotCommon.Statistics} statistics |
| */ |
| _gotStatistics(statistics) { |
| this._statisticsView.setTotal(statistics.total); |
| this._statisticsView.addRecord(statistics.code, Common.UIString('Code'), '#f77'); |
| this._statisticsView.addRecord(statistics.strings, Common.UIString('Strings'), '#5e5'); |
| this._statisticsView.addRecord(statistics.jsArrays, Common.UIString('JS Arrays'), '#7af'); |
| this._statisticsView.addRecord(statistics.native, Common.UIString('Typed Arrays'), '#fc5'); |
| this._statisticsView.addRecord(statistics.system, Common.UIString('System Objects'), '#98f'); |
| this._statisticsView.addRecord(statistics.total, Common.UIString('Total')); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onIdsRangeChanged(event) { |
| var minId = event.data.minId; |
| var maxId = event.data.maxId; |
| this._selectedSizeText.setText(Common.UIString('Selected size: %s', Number.bytesToString(event.data.size))); |
| if (this._constructorsDataGrid.snapshot) |
| this._constructorsDataGrid.setSelectionRange(minId, maxId); |
| } |
| |
| /** |
| * @override |
| * @return {!Array.<!UI.ToolbarItem>} |
| */ |
| syncToolbarItems() { |
| var result = [this._perspectiveSelect, this._classNameFilter]; |
| if (this._profile.profileType() !== Profiler.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) |
| result.push(this._baseSelect, this._filterSelect); |
| result.push(this._selectedSizeText); |
| return result; |
| } |
| |
| /** |
| * @override |
| */ |
| wasShown() { |
| this._profile._loadPromise.then(this._profile._wasShown.bind(this._profile)); |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| this._currentSearchResultIndex = -1; |
| this._popoverHelper.hidePopover(); |
| if (this.helpPopover && this.helpPopover.isShowing()) |
| this.helpPopover.hide(); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| supportsCaseSensitiveSearch() { |
| return true; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| supportsRegexSearch() { |
| return false; |
| } |
| |
| /** |
| * @override |
| */ |
| searchCanceled() { |
| this._currentSearchResultIndex = -1; |
| this._searchResults = []; |
| } |
| |
| /** |
| * @param {?Profiler.HeapSnapshotGridNode} node |
| */ |
| _selectRevealedNode(node) { |
| if (node) |
| node.select(); |
| } |
| |
| /** |
| * @override |
| * @param {!UI.SearchableView.SearchConfig} searchConfig |
| * @param {boolean} shouldJump |
| * @param {boolean=} jumpBackwards |
| */ |
| performSearch(searchConfig, shouldJump, jumpBackwards) { |
| var nextQuery = new Profiler.HeapSnapshotCommon.SearchConfig( |
| searchConfig.query.trim(), searchConfig.caseSensitive, searchConfig.isRegex, shouldJump, |
| jumpBackwards || false); |
| |
| this._searchThrottler.schedule(this._performSearch.bind(this, nextQuery)); |
| } |
| |
| /** |
| * @param {!Profiler.HeapSnapshotCommon.SearchConfig} nextQuery |
| * @return {!Promise<?>} |
| */ |
| _performSearch(nextQuery) { |
| // Call searchCanceled since it will reset everything we need before doing a new search. |
| this.searchCanceled(); |
| |
| if (!this._currentPerspective.supportsSearch()) |
| return Promise.resolve(); |
| |
| this.currentQuery = nextQuery; |
| var query = nextQuery.query.trim(); |
| |
| if (!query) |
| return Promise.resolve(); |
| |
| if (query.charAt(0) === '@') { |
| var snapshotNodeId = parseInt(query.substring(1), 10); |
| if (isNaN(snapshotNodeId)) |
| return Promise.resolve(); |
| return this._dataGrid.revealObjectByHeapSnapshotId(String(snapshotNodeId)) |
| .then(this._selectRevealedNode.bind(this)); |
| } |
| |
| /** |
| * @param {!Array<number>} entryIds |
| * @return {!Promise<?>} |
| * @this {Profiler.HeapSnapshotView} |
| */ |
| function didSearch(entryIds) { |
| this._searchResults = entryIds; |
| this._searchableView.updateSearchMatchesCount(this._searchResults.length); |
| if (this._searchResults.length) |
| this._currentSearchResultIndex = nextQuery.jumpBackwards ? this._searchResults.length - 1 : 0; |
| return this._jumpToSearchResult(this._currentSearchResultIndex); |
| } |
| |
| return this._profile._snapshotProxy.search(this.currentQuery, this._dataGrid.nodeFilter()) |
| .then(didSearch.bind(this)); |
| } |
| |
| /** |
| * @override |
| */ |
| jumpToNextSearchResult() { |
| if (!this._searchResults.length) |
| return; |
| this._currentSearchResultIndex = (this._currentSearchResultIndex + 1) % this._searchResults.length; |
| this._searchThrottler.schedule(this._jumpToSearchResult.bind(this, this._currentSearchResultIndex)); |
| } |
| |
| /** |
| * @override |
| */ |
| jumpToPreviousSearchResult() { |
| if (!this._searchResults.length) |
| return; |
| this._currentSearchResultIndex = |
| (this._currentSearchResultIndex + this._searchResults.length - 1) % this._searchResults.length; |
| this._searchThrottler.schedule(this._jumpToSearchResult.bind(this, this._currentSearchResultIndex)); |
| } |
| |
| /** |
| * @param {number} searchResultIndex |
| * @return {!Promise<undefined>} |
| */ |
| _jumpToSearchResult(searchResultIndex) { |
| this._searchableView.updateCurrentMatchIndex(searchResultIndex); |
| return this._dataGrid.revealObjectByHeapSnapshotId(String(this._searchResults[searchResultIndex])) |
| .then(this._selectRevealedNode.bind(this)); |
| } |
| |
| refreshVisibleData() { |
| if (!this._dataGrid) |
| return; |
| var child = this._dataGrid.rootNode().children[0]; |
| while (child) { |
| child.refresh(); |
| child = child.traverseNextNode(false, null, true); |
| } |
| } |
| |
| _changeBase() { |
| if (this._baseProfile === this._profiles()[this._baseSelect.selectedIndex()]) |
| return; |
| |
| this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()]; |
| var dataGrid = /** @type {!Profiler.HeapSnapshotDiffDataGrid} */ (this._dataGrid); |
| // Change set base data source only if main data source is already set. |
| if (dataGrid.snapshot) |
| this._baseProfile._loadPromise.then(dataGrid.setBaseDataSource.bind(dataGrid)); |
| |
| if (!this.currentQuery || !this._searchResults) |
| return; |
| |
| // The current search needs to be performed again. First negate out previous match |
| // count by calling the search finished callback with a negative number of matches. |
| // Then perform the search again with the same query and callback. |
| this.performSearch(this.currentQuery, false); |
| } |
| |
| _changeFilter() { |
| var profileIndex = this._filterSelect.selectedIndex() - 1; |
| this._dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex); |
| |
| if (!this.currentQuery || !this._searchResults) |
| return; |
| |
| // The current search needs to be performed again. First negate out previous match |
| // count by calling the search finished callback with a negative number of matches. |
| // Then perform the search again with the same query and callback. |
| this.performSearch(this.currentQuery, false); |
| } |
| |
| /** |
| * @return {!Array.<!Profiler.ProfileHeader>} |
| */ |
| _profiles() { |
| return this._profile.profileType().getProfiles(); |
| } |
| |
| /** |
| * @param {!UI.ContextMenu} contextMenu |
| * @param {!Event} event |
| */ |
| populateContextMenu(contextMenu, event) { |
| if (this._dataGrid) |
| this._dataGrid.populateContextMenu(contextMenu, event); |
| } |
| |
| _selectionChanged(event) { |
| var selectedNode = event.target.selectedNode; |
| this._setSelectedNodeForDetailsView(selectedNode); |
| this._inspectedObjectChanged(event); |
| } |
| |
| _onSelectAllocationNode(event) { |
| var selectedNode = event.target.selectedNode; |
| this._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId()); |
| this._setSelectedNodeForDetailsView(null); |
| } |
| |
| _inspectedObjectChanged(event) { |
| var selectedNode = event.target.selectedNode; |
| var target = this._profile.target(); |
| if (target && selectedNode instanceof Profiler.HeapSnapshotGenericObjectNode) |
| target.heapProfilerAgent().addInspectedHeapObject(String(selectedNode.snapshotNodeId)); |
| } |
| |
| /** |
| * @param {?Profiler.HeapSnapshotGridNode} nodeItem |
| */ |
| _setSelectedNodeForDetailsView(nodeItem) { |
| var dataSource = nodeItem && nodeItem.retainersDataSource(); |
| if (dataSource) { |
| this._retainmentDataGrid.setDataSource(dataSource.snapshot, dataSource.snapshotNodeIndex); |
| if (this._allocationStackView) |
| this._allocationStackView.setAllocatedObject(dataSource.snapshot, dataSource.snapshotNodeIndex); |
| } else { |
| if (this._allocationStackView) |
| this._allocationStackView.clear(); |
| this._retainmentDataGrid.reset(); |
| } |
| } |
| |
| /** |
| * @param {string} perspectiveTitle |
| * @param {function()} callback |
| */ |
| _changePerspectiveAndWait(perspectiveTitle, callback) { |
| var perspectiveIndex = null; |
| for (var i = 0; i < this._perspectives.length; ++i) { |
| if (this._perspectives[i].title() === perspectiveTitle) { |
| perspectiveIndex = i; |
| break; |
| } |
| } |
| if (this._currentPerspectiveIndex === perspectiveIndex || perspectiveIndex === null) { |
| setTimeout(callback, 0); |
| return; |
| } |
| |
| /** |
| * @this {Profiler.HeapSnapshotView} |
| */ |
| function dataGridContentShown(event) { |
| var dataGrid = event.data; |
| dataGrid.removeEventListener( |
| Profiler.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); |
| if (dataGrid === this._dataGrid) |
| callback(); |
| } |
| this._perspectives[perspectiveIndex].masterGrid(this).addEventListener( |
| Profiler.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); |
| |
| this._perspectiveSelect.setSelectedIndex(perspectiveIndex); |
| this._changePerspective(perspectiveIndex); |
| } |
| |
| _updateDataSourceAndView() { |
| var dataGrid = this._dataGrid; |
| if (!dataGrid || dataGrid.snapshot) |
| return; |
| |
| this._profile._loadPromise.then(didLoadSnapshot.bind(this)); |
| |
| /** |
| * @this {Profiler.HeapSnapshotView} |
| */ |
| function didLoadSnapshot(snapshotProxy) { |
| if (this._dataGrid !== dataGrid) |
| return; |
| if (dataGrid.snapshot !== snapshotProxy) |
| dataGrid.setDataSource(snapshotProxy); |
| if (dataGrid === this._diffDataGrid) { |
| if (!this._baseProfile) |
| this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()]; |
| this._baseProfile._loadPromise.then(didLoadBaseSnapshot.bind(this)); |
| } |
| } |
| |
| /** |
| * @this {Profiler.HeapSnapshotView} |
| */ |
| function didLoadBaseSnapshot(baseSnapshotProxy) { |
| if (this._diffDataGrid.baseSnapshot !== baseSnapshotProxy) |
| this._diffDataGrid.setBaseDataSource(baseSnapshotProxy); |
| } |
| } |
| |
| _onSelectedPerspectiveChanged(event) { |
| this._changePerspective(event.target.selectedIndex); |
| } |
| |
| /** |
| * @param {number} selectedIndex |
| */ |
| _changePerspective(selectedIndex) { |
| if (selectedIndex === this._currentPerspectiveIndex) |
| return; |
| |
| this._currentPerspectiveIndex = selectedIndex; |
| |
| this._currentPerspective.deactivate(this); |
| var perspective = this._perspectives[selectedIndex]; |
| this._currentPerspective = perspective; |
| this._dataGrid = perspective.masterGrid(this); |
| perspective.activate(this); |
| |
| this.refreshVisibleData(); |
| if (this._dataGrid) |
| this._dataGrid.updateWidths(); |
| |
| this._updateDataSourceAndView(); |
| |
| if (!this.currentQuery || !this._searchResults) |
| return; |
| |
| // The current search needs to be performed again. First negate out previous match |
| // count by calling the search finished callback with a negative number of matches. |
| // Then perform the search again the with same query and callback. |
| this.performSearch(this.currentQuery, false); |
| } |
| |
| /** |
| * @param {string} perspectiveName |
| * @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} snapshotObjectId |
| */ |
| selectLiveObject(perspectiveName, snapshotObjectId) { |
| this._changePerspectiveAndWait(perspectiveName, didChangePerspective.bind(this)); |
| |
| /** |
| * @this {Profiler.HeapSnapshotView} |
| */ |
| function didChangePerspective() { |
| this._dataGrid.revealObjectByHeapSnapshotId(snapshotObjectId, didRevealObject); |
| } |
| |
| /** |
| * @param {?Profiler.HeapSnapshotGridNode} node |
| */ |
| function didRevealObject(node) { |
| if (node) |
| node.select(); |
| else |
| Common.console.error('Cannot find corresponding heap snapshot node'); |
| } |
| } |
| |
| _getHoverAnchor(target) { |
| var span = target.enclosingNodeOrSelfWithNodeName('span'); |
| if (!span) |
| return; |
| var row = target.enclosingNodeOrSelfWithNodeName('tr'); |
| if (!row) |
| return; |
| span.node = row._dataGridNode; |
| return span; |
| } |
| |
| _resolveObjectForPopover(element, showCallback, objectGroupName) { |
| if (!this._profile.target()) |
| return; |
| if (!element.node) |
| return; |
| element.node.queryObjectContent(this._profile.target(), showCallback, objectGroupName); |
| } |
| |
| _updateBaseOptions() { |
| var list = this._profiles(); |
| // We're assuming that snapshots can only be added. |
| if (this._baseSelect.size() === list.length) |
| return; |
| |
| for (var i = this._baseSelect.size(), n = list.length; i < n; ++i) { |
| var title = list[i].title; |
| this._baseSelect.createOption(title); |
| } |
| } |
| |
| _updateFilterOptions() { |
| var list = this._profiles(); |
| // We're assuming that snapshots can only be added. |
| if (this._filterSelect.size() - 1 === list.length) |
| return; |
| |
| if (!this._filterSelect.size()) |
| this._filterSelect.createOption(Common.UIString('All objects')); |
| |
| for (var i = this._filterSelect.size() - 1, n = list.length; i < n; ++i) { |
| var title = list[i].title; |
| if (!i) |
| title = Common.UIString('Objects allocated before %s', title); |
| else |
| title = Common.UIString('Objects allocated between %s and %s', list[i - 1].title, title); |
| this._filterSelect.createOption(title); |
| } |
| } |
| |
| _updateControls() { |
| this._updateBaseOptions(); |
| this._updateFilterOptions(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onReceiveSnapshot(event) { |
| this._updateControls(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onProfileHeaderRemoved(event) { |
| var profile = event.data; |
| if (this._profile === profile) { |
| this.detach(); |
| this._profile.profileType().removeEventListener( |
| Profiler.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this); |
| this._profile.profileType().removeEventListener( |
| Profiler.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this); |
| this.dispose(); |
| } else { |
| this._updateControls(); |
| } |
| } |
| |
| dispose() { |
| if (this._allocationStackView) { |
| this._allocationStackView.clear(); |
| this._allocationDataGrid.dispose(); |
| } |
| if (this._trackingOverviewGrid) |
| this._trackingOverviewGrid.dispose(); |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView.Perspective = class { |
| /** |
| * @param {string} title |
| */ |
| constructor(title) { |
| this._title = title; |
| } |
| |
| /** |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| activate(heapSnapshotView) { |
| } |
| |
| /** |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| deactivate(heapSnapshotView) { |
| heapSnapshotView._baseSelect.setVisible(false); |
| heapSnapshotView._filterSelect.setVisible(false); |
| heapSnapshotView._classNameFilter.setVisible(false); |
| if (heapSnapshotView._trackingOverviewGrid) |
| heapSnapshotView._trackingOverviewGrid.detach(); |
| if (heapSnapshotView._allocationWidget) |
| heapSnapshotView._allocationWidget.detach(); |
| if (heapSnapshotView._statisticsView) |
| heapSnapshotView._statisticsView.detach(); |
| |
| heapSnapshotView._splitWidget.detach(); |
| heapSnapshotView._splitWidget.detachChildWidgets(); |
| } |
| |
| /** |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| * @return {?UI.DataGrid} |
| */ |
| masterGrid(heapSnapshotView) { |
| return null; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| title() { |
| return this._title; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| supportsSearch() { |
| return false; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView.SummaryPerspective = class extends Profiler.HeapSnapshotView.Perspective { |
| constructor() { |
| super(Common.UIString('Summary')); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| activate(heapSnapshotView) { |
| heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._constructorsWidget); |
| heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView); |
| heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element); |
| heapSnapshotView._filterSelect.setVisible(true); |
| heapSnapshotView._classNameFilter.setVisible(true); |
| if (heapSnapshotView._trackingOverviewGrid) { |
| heapSnapshotView._trackingOverviewGrid.show( |
| heapSnapshotView._searchableView.element, heapSnapshotView._splitWidget.element); |
| heapSnapshotView._trackingOverviewGrid.update(); |
| heapSnapshotView._trackingOverviewGrid._updateGrid(); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| * @return {?UI.DataGrid} |
| */ |
| masterGrid(heapSnapshotView) { |
| return heapSnapshotView._constructorsDataGrid; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| supportsSearch() { |
| return true; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView.ComparisonPerspective = class extends Profiler.HeapSnapshotView.Perspective { |
| constructor() { |
| super(Common.UIString('Comparison')); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| activate(heapSnapshotView) { |
| heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._diffWidget); |
| heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView); |
| heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element); |
| heapSnapshotView._baseSelect.setVisible(true); |
| heapSnapshotView._classNameFilter.setVisible(true); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| * @return {?UI.DataGrid} |
| */ |
| masterGrid(heapSnapshotView) { |
| return heapSnapshotView._diffDataGrid; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| supportsSearch() { |
| return true; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView.ContainmentPerspective = class extends Profiler.HeapSnapshotView.Perspective { |
| constructor() { |
| super(Common.UIString('Containment')); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| activate(heapSnapshotView) { |
| heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._containmentWidget); |
| heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView); |
| heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| * @return {?UI.DataGrid} |
| */ |
| masterGrid(heapSnapshotView) { |
| return heapSnapshotView._containmentDataGrid; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView.AllocationPerspective = class extends Profiler.HeapSnapshotView.Perspective { |
| constructor() { |
| super(Common.UIString('Allocation')); |
| this._allocationSplitWidget = new UI.SplitWidget(false, true, 'heapSnapshotAllocationSplitViewState', 200, 200); |
| this._allocationSplitWidget.setSidebarWidget(new UI.VBox()); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| activate(heapSnapshotView) { |
| this._allocationSplitWidget.setMainWidget(heapSnapshotView._allocationWidget); |
| heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._constructorsWidget); |
| heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView); |
| |
| var allocatedObjectsView = new UI.VBox(); |
| var resizer = createElementWithClass('div', 'heap-snapshot-view-resizer'); |
| var title = resizer.createChild('div', 'title').createChild('span'); |
| title.textContent = Common.UIString('Live objects'); |
| this._allocationSplitWidget.hideDefaultResizer(); |
| this._allocationSplitWidget.installResizer(resizer); |
| allocatedObjectsView.element.appendChild(resizer); |
| heapSnapshotView._splitWidget.show(allocatedObjectsView.element); |
| this._allocationSplitWidget.setSidebarWidget(allocatedObjectsView); |
| |
| this._allocationSplitWidget.show(heapSnapshotView._searchableView.element); |
| |
| heapSnapshotView._constructorsDataGrid.clear(); |
| var selectedNode = heapSnapshotView._allocationDataGrid.selectedNode; |
| if (selectedNode) |
| heapSnapshotView._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId()); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| deactivate(heapSnapshotView) { |
| this._allocationSplitWidget.detach(); |
| super.deactivate(heapSnapshotView); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| * @return {?UI.DataGrid} |
| */ |
| masterGrid(heapSnapshotView) { |
| return heapSnapshotView._allocationDataGrid; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotView.StatisticsPerspective = class extends Profiler.HeapSnapshotView.Perspective { |
| constructor() { |
| super(Common.UIString('Statistics')); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| */ |
| activate(heapSnapshotView) { |
| heapSnapshotView._statisticsView.show(heapSnapshotView._searchableView.element); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.HeapSnapshotView} heapSnapshotView |
| * @return {?UI.DataGrid} |
| */ |
| masterGrid(heapSnapshotView) { |
| return null; |
| } |
| }; |
| |
| /** |
| * @implements {SDK.TargetManager.Observer} |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotProfileType = class extends Profiler.ProfileType { |
| /** |
| * @param {string=} id |
| * @param {string=} title |
| */ |
| constructor(id, title) { |
| super(id || Profiler.HeapSnapshotProfileType.TypeId, title || Common.UIString('Take Heap Snapshot')); |
| SDK.targetManager.observeTargets(this); |
| SDK.targetManager.addModelListener( |
| SDK.HeapProfilerModel, SDK.HeapProfilerModel.Events.ResetProfiles, this._resetProfiles, this); |
| SDK.targetManager.addModelListener( |
| SDK.HeapProfilerModel, SDK.HeapProfilerModel.Events.AddHeapSnapshotChunk, this._addHeapSnapshotChunk, this); |
| SDK.targetManager.addModelListener( |
| SDK.HeapProfilerModel, SDK.HeapProfilerModel.Events.ReportHeapSnapshotProgress, |
| this._reportHeapSnapshotProgress, this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.Target} target |
| */ |
| targetAdded(target) { |
| target.heapProfilerModel.enable(); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.Target} target |
| */ |
| targetRemoved(target) { |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| fileExtension() { |
| return '.heapsnapshot'; |
| } |
| |
| get buttonTooltip() { |
| return Common.UIString('Take heap snapshot'); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isInstantProfile() { |
| return true; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| buttonClicked() { |
| this._takeHeapSnapshot(function() {}); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.ProfilesHeapProfileTaken); |
| return false; |
| } |
| |
| get treeItemTitle() { |
| return Common.UIString('HEAP SNAPSHOTS'); |
| } |
| |
| get description() { |
| return Common.UIString( |
| 'Heap snapshot profiles show memory distribution among your page\'s JavaScript objects and related DOM nodes.'); |
| } |
| |
| /** |
| * @override |
| * @param {string} title |
| * @return {!Profiler.ProfileHeader} |
| */ |
| createProfileLoadedFromFile(title) { |
| return new Profiler.HeapProfileHeader(null, this, title); |
| } |
| |
| _takeHeapSnapshot(callback) { |
| if (this.profileBeingRecorded()) |
| return; |
| var target = /** @type {!SDK.Target} */ (UI.context.flavor(SDK.Target)); |
| var profile = new Profiler.HeapProfileHeader(target, this); |
| this.setProfileBeingRecorded(profile); |
| this.addProfile(profile); |
| profile.updateStatus(Common.UIString('Snapshotting\u2026')); |
| |
| /** |
| * @param {?string} error |
| * @this {Profiler.HeapSnapshotProfileType} |
| */ |
| function didTakeHeapSnapshot(error) { |
| var profile = this._profileBeingRecorded; |
| profile.title = Common.UIString('Snapshot %d', profile.uid); |
| profile._finishLoad(); |
| this.setProfileBeingRecorded(null); |
| this.dispatchEventToListeners(Profiler.ProfileType.Events.ProfileComplete, profile); |
| callback(); |
| } |
| target.heapProfilerAgent().takeHeapSnapshot(true, didTakeHeapSnapshot.bind(this)); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _addHeapSnapshotChunk(event) { |
| if (!this.profileBeingRecorded()) |
| return; |
| var chunk = /** @type {string} */ (event.data); |
| this.profileBeingRecorded().transferChunk(chunk); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _reportHeapSnapshotProgress(event) { |
| var profile = this.profileBeingRecorded(); |
| if (!profile) |
| return; |
| var data = /** @type {{done: number, total: number, finished: boolean}} */ (event.data); |
| profile.updateStatus(Common.UIString('%.0f%%', (data.done / data.total) * 100), true); |
| if (data.finished) |
| profile._prepareToLoad(); |
| } |
| |
| _resetProfiles() { |
| this._reset(); |
| } |
| |
| _snapshotReceived(profile) { |
| if (this._profileBeingRecorded === profile) |
| this.setProfileBeingRecorded(null); |
| this.dispatchEventToListeners(Profiler.HeapSnapshotProfileType.SnapshotReceived, profile); |
| } |
| }; |
| |
| Profiler.HeapSnapshotProfileType.TypeId = 'HEAP'; |
| Profiler.HeapSnapshotProfileType.SnapshotReceived = 'SnapshotReceived'; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.TrackingHeapSnapshotProfileType = class extends Profiler.HeapSnapshotProfileType { |
| constructor() { |
| super(Profiler.TrackingHeapSnapshotProfileType.TypeId, Common.UIString('Record Allocation Timeline')); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.Target} target |
| */ |
| targetAdded(target) { |
| super.targetAdded(target); |
| target.heapProfilerModel.addEventListener( |
| SDK.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this); |
| target.heapProfilerModel.addEventListener( |
| SDK.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.Target} target |
| */ |
| targetRemoved(target) { |
| super.targetRemoved(target); |
| target.heapProfilerModel.removeEventListener( |
| SDK.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this); |
| target.heapProfilerModel.removeEventListener( |
| SDK.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _heapStatsUpdate(event) { |
| if (!this._profileSamples) |
| return; |
| var samples = /** @type {!Array.<number>} */ (event.data); |
| var index; |
| for (var i = 0; i < samples.length; i += 3) { |
| index = samples[i]; |
| var size = samples[i + 2]; |
| this._profileSamples.sizes[index] = size; |
| if (!this._profileSamples.max[index]) |
| this._profileSamples.max[index] = size; |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _lastSeenObjectId(event) { |
| var profileSamples = this._profileSamples; |
| if (!profileSamples) |
| return; |
| var data = /** @type {{lastSeenObjectId: number, timestamp: number}} */ (event.data); |
| var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1); |
| profileSamples.ids[currentIndex] = data.lastSeenObjectId; |
| if (!profileSamples.max[currentIndex]) { |
| profileSamples.max[currentIndex] = 0; |
| profileSamples.sizes[currentIndex] = 0; |
| } |
| profileSamples.timestamps[currentIndex] = data.timestamp; |
| if (profileSamples.totalTime < data.timestamp - profileSamples.timestamps[0]) |
| profileSamples.totalTime *= 2; |
| this.dispatchEventToListeners(Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples); |
| this._profileBeingRecorded.updateStatus(null, true); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasTemporaryView() { |
| return true; |
| } |
| |
| get buttonTooltip() { |
| return this._recording ? Common.UIString('Stop recording heap profile') : |
| Common.UIString('Start recording heap profile'); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isInstantProfile() { |
| return false; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| buttonClicked() { |
| return this._toggleRecording(); |
| } |
| |
| _startRecordingProfile() { |
| if (this.profileBeingRecorded()) |
| return; |
| this._addNewProfile(); |
| var recordAllocationStacks = Common.moduleSetting('recordAllocationStacks').get(); |
| this.profileBeingRecorded().target().heapProfilerAgent().startTrackingHeapObjects(recordAllocationStacks); |
| } |
| |
| _addNewProfile() { |
| var target = UI.context.flavor(SDK.Target); |
| this.setProfileBeingRecorded(new Profiler.HeapProfileHeader(target, this, undefined)); |
| this._profileSamples = new Profiler.TrackingHeapSnapshotProfileType.Samples(); |
| this._profileBeingRecorded._profileSamples = this._profileSamples; |
| this._recording = true; |
| this.addProfile(this._profileBeingRecorded); |
| this._profileBeingRecorded.updateStatus(Common.UIString('Recording\u2026')); |
| this.dispatchEventToListeners(Profiler.TrackingHeapSnapshotProfileType.TrackingStarted); |
| } |
| |
| _stopRecordingProfile() { |
| this._profileBeingRecorded.updateStatus(Common.UIString('Snapshotting\u2026')); |
| /** |
| * @param {?string} error |
| * @this {Profiler.HeapSnapshotProfileType} |
| */ |
| function didTakeHeapSnapshot(error) { |
| var profile = this.profileBeingRecorded(); |
| if (!profile) |
| return; |
| profile._finishLoad(); |
| this._profileSamples = null; |
| this.setProfileBeingRecorded(null); |
| this.dispatchEventToListeners(Profiler.ProfileType.Events.ProfileComplete, profile); |
| } |
| |
| this._profileBeingRecorded.target().heapProfilerAgent().stopTrackingHeapObjects( |
| true, didTakeHeapSnapshot.bind(this)); |
| this._recording = false; |
| this.dispatchEventToListeners(Profiler.TrackingHeapSnapshotProfileType.TrackingStopped); |
| } |
| |
| _toggleRecording() { |
| if (this._recording) |
| this._stopRecordingProfile(); |
| else |
| this._startRecordingProfile(); |
| return this._recording; |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| fileExtension() { |
| return '.heaptimeline'; |
| } |
| |
| get treeItemTitle() { |
| return Common.UIString('ALLOCATION TIMELINES'); |
| } |
| |
| get description() { |
| return Common.UIString( |
| 'Allocation timelines show memory allocations from your heap over time. Use this profile type to isolate memory leaks.'); |
| } |
| |
| /** |
| * @override |
| */ |
| _resetProfiles() { |
| var wasRecording = this._recording; |
| // Clear current profile to avoid stopping backend. |
| this.setProfileBeingRecorded(null); |
| super._resetProfiles(); |
| this._profileSamples = null; |
| if (wasRecording) |
| this._addNewProfile(); |
| } |
| |
| /** |
| * @override |
| */ |
| profileBeingRecordedRemoved() { |
| this._stopRecordingProfile(); |
| this._profileSamples = null; |
| } |
| }; |
| |
| Profiler.TrackingHeapSnapshotProfileType.TypeId = 'HEAP-RECORD'; |
| |
| Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate = 'HeapStatsUpdate'; |
| Profiler.TrackingHeapSnapshotProfileType.TrackingStarted = 'TrackingStarted'; |
| Profiler.TrackingHeapSnapshotProfileType.TrackingStopped = 'TrackingStopped'; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.TrackingHeapSnapshotProfileType.Samples = class { |
| constructor() { |
| /** @type {!Array.<number>} */ |
| this.sizes = []; |
| /** @type {!Array.<number>} */ |
| this.ids = []; |
| /** @type {!Array.<number>} */ |
| this.timestamps = []; |
| /** @type {!Array.<number>} */ |
| this.max = []; |
| /** @type {number} */ |
| this.totalTime = 30000; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapProfileHeader = class extends Profiler.ProfileHeader { |
| /** |
| * @param {?SDK.Target} target |
| * @param {!Profiler.HeapSnapshotProfileType} type |
| * @param {string=} title |
| */ |
| constructor(target, type, title) { |
| super(target, type, title || Common.UIString('Snapshot %d', type.nextProfileUid())); |
| this.maxJSObjectId = -1; |
| /** |
| * @type {?Profiler.HeapSnapshotWorkerProxy} |
| */ |
| this._workerProxy = null; |
| /** |
| * @type {?Common.OutputStream} |
| */ |
| this._receiver = null; |
| /** |
| * @type {?Profiler.HeapSnapshotProxy} |
| */ |
| this._snapshotProxy = null; |
| /** |
| * @type {!Promise.<!Profiler.HeapSnapshotProxy>} |
| */ |
| this._loadPromise = new Promise(loadResolver.bind(this)); |
| this._totalNumberOfChunks = 0; |
| this._bufferedWriter = null; |
| |
| /** |
| * @param {function(!Profiler.HeapSnapshotProxy)} fulfill |
| * @this {Profiler.HeapProfileHeader} |
| */ |
| function loadResolver(fulfill) { |
| this._fulfillLoad = fulfill; |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate |
| * @return {!Profiler.ProfileSidebarTreeElement} |
| */ |
| createSidebarTreeElement(dataDisplayDelegate) { |
| return new Profiler.ProfileSidebarTreeElement(dataDisplayDelegate, this, 'heap-snapshot-sidebar-tree-item'); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate |
| * @return {!Profiler.HeapSnapshotView} |
| */ |
| createView(dataDisplayDelegate) { |
| return new Profiler.HeapSnapshotView(dataDisplayDelegate, this); |
| } |
| |
| _prepareToLoad() { |
| console.assert(!this._receiver, 'Already loading'); |
| this._setupWorker(); |
| this.updateStatus(Common.UIString('Loading\u2026'), true); |
| } |
| |
| _finishLoad() { |
| if (!this._wasDisposed) |
| this._receiver.close(); |
| if (this._bufferedWriter) { |
| this._bufferedWriter.finishWriting(this._didWriteToTempFile.bind(this)); |
| this._bufferedWriter = null; |
| } |
| } |
| |
| _didWriteToTempFile(tempFile) { |
| if (this._wasDisposed) { |
| if (tempFile) |
| tempFile.remove(); |
| return; |
| } |
| this._tempFile = tempFile; |
| if (!tempFile) |
| this._failedToCreateTempFile = true; |
| if (this._onTempFileReady) { |
| this._onTempFileReady(); |
| this._onTempFileReady = null; |
| } |
| } |
| |
| _setupWorker() { |
| /** |
| * @this {Profiler.HeapProfileHeader} |
| */ |
| function setProfileWait(event) { |
| this.updateStatus(null, event.data); |
| } |
| console.assert(!this._workerProxy, 'HeapSnapshotWorkerProxy already exists'); |
| this._workerProxy = new Profiler.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this)); |
| this._workerProxy.addEventListener('wait', setProfileWait, this); |
| this._receiver = this._workerProxy.createLoader(this.uid, this._snapshotReceived.bind(this)); |
| } |
| |
| /** |
| * @param {string} eventName |
| * @param {*} data |
| */ |
| _handleWorkerEvent(eventName, data) { |
| if (Profiler.HeapSnapshotProgressEvent.BrokenSnapshot === eventName) { |
| var error = /** @type {string} */ (data); |
| Common.console.error(error); |
| return; |
| } |
| |
| if (Profiler.HeapSnapshotProgressEvent.Update !== eventName) |
| return; |
| var subtitle = /** @type {string} */ (data); |
| this.updateStatus(subtitle); |
| } |
| |
| /** |
| * @override |
| */ |
| dispose() { |
| if (this._workerProxy) |
| this._workerProxy.dispose(); |
| this.removeTempFile(); |
| this._wasDisposed = true; |
| } |
| |
| _didCompleteSnapshotTransfer() { |
| if (!this._snapshotProxy) |
| return; |
| this.updateStatus(Number.bytesToString(this._snapshotProxy.totalSize), false); |
| } |
| |
| /** |
| * @param {string} chunk |
| */ |
| transferChunk(chunk) { |
| if (!this._bufferedWriter) |
| this._bufferedWriter = new Bindings.DeferredTempFile('heap-profiler', String(this.uid)); |
| this._bufferedWriter.write([chunk]); |
| |
| ++this._totalNumberOfChunks; |
| this._receiver.write(chunk); |
| } |
| |
| _snapshotReceived(snapshotProxy) { |
| if (this._wasDisposed) |
| return; |
| this._receiver = null; |
| this._snapshotProxy = snapshotProxy; |
| this.maxJSObjectId = snapshotProxy.maxJSObjectId(); |
| this._didCompleteSnapshotTransfer(); |
| this._workerProxy.startCheckingForLongRunningCalls(); |
| this.notifySnapshotReceived(); |
| } |
| |
| notifySnapshotReceived() { |
| this._fulfillLoad(this._snapshotProxy); |
| this._profileType._snapshotReceived(this); |
| if (this.canSaveToFile()) |
| this.dispatchEventToListeners(Profiler.ProfileHeader.Events.ProfileReceived); |
| } |
| |
| // Hook point for tests. |
| _wasShown() { |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| canSaveToFile() { |
| return !this.fromFile() && !!this._snapshotProxy; |
| } |
| |
| /** |
| * @override |
| */ |
| saveToFile() { |
| var fileOutputStream = new Bindings.FileOutputStream(); |
| |
| /** |
| * @param {boolean} accepted |
| * @this {Profiler.HeapProfileHeader} |
| */ |
| function onOpen(accepted) { |
| if (!accepted) |
| return; |
| if (this._failedToCreateTempFile) { |
| Common.console.error('Failed to open temp file with heap snapshot'); |
| fileOutputStream.close(); |
| } else if (this._tempFile) { |
| var delegate = new Profiler.SaveSnapshotOutputStreamDelegate(this); |
| this._tempFile.copyToOutputStream(fileOutputStream, delegate); |
| } else { |
| this._onTempFileReady = onOpen.bind(this, accepted); |
| this._updateSaveProgress(0, 1); |
| } |
| } |
| this._fileName = this._fileName || 'Heap-' + new Date().toISO8601Compact() + this._profileType.fileExtension(); |
| fileOutputStream.open(this._fileName, onOpen.bind(this)); |
| } |
| |
| _updateSaveProgress(value, total) { |
| var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); |
| this.updateStatus(Common.UIString('Saving\u2026 %d%%', percentValue)); |
| } |
| |
| /** |
| * @override |
| * @param {!File} file |
| */ |
| loadFromFile(file) { |
| this.updateStatus(Common.UIString('Loading\u2026'), true); |
| this._setupWorker(); |
| var delegate = new Profiler.HeapSnapshotLoadFromFileDelegate(this); |
| var fileReader = this._createFileReader(file, delegate); |
| fileReader.start(this._receiver); |
| } |
| |
| _createFileReader(file, delegate) { |
| return new Bindings.ChunkedFileReader(file, 10000000, delegate); |
| } |
| }; |
| |
| /** |
| * @implements {Bindings.OutputStreamDelegate} |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotLoadFromFileDelegate = class { |
| constructor(snapshotHeader) { |
| this._snapshotHeader = snapshotHeader; |
| } |
| |
| /** |
| * @override |
| */ |
| onTransferStarted() { |
| } |
| |
| /** |
| * @override |
| * @param {!Bindings.ChunkedReader} reader |
| */ |
| onChunkTransferred(reader) { |
| } |
| |
| /** |
| * @override |
| */ |
| onTransferFinished() { |
| } |
| |
| /** |
| * @override |
| * @param {!Bindings.ChunkedReader} reader |
| * @param {!Event} e |
| */ |
| onError(reader, e) { |
| var subtitle; |
| switch (e.target.error.code) { |
| case e.target.error.NOT_FOUND_ERR: |
| subtitle = Common.UIString('\'%s\' not found.', reader.fileName()); |
| break; |
| case e.target.error.NOT_READABLE_ERR: |
| subtitle = Common.UIString('\'%s\' is not readable', reader.fileName()); |
| break; |
| case e.target.error.ABORT_ERR: |
| return; |
| default: |
| subtitle = Common.UIString('\'%s\' error %d', reader.fileName(), e.target.error.code); |
| } |
| this._snapshotHeader.updateStatus(subtitle); |
| } |
| }; |
| |
| /** |
| * @implements {Bindings.OutputStreamDelegate} |
| * @unrestricted |
| */ |
| Profiler.SaveSnapshotOutputStreamDelegate = class { |
| /** |
| * @param {!Profiler.HeapProfileHeader} profileHeader |
| */ |
| constructor(profileHeader) { |
| this._profileHeader = profileHeader; |
| } |
| |
| /** |
| * @override |
| */ |
| onTransferStarted() { |
| this._profileHeader._updateSaveProgress(0, 1); |
| } |
| |
| /** |
| * @override |
| */ |
| onTransferFinished() { |
| this._profileHeader._didCompleteSnapshotTransfer(); |
| } |
| |
| /** |
| * @override |
| * @param {!Bindings.ChunkedReader} reader |
| */ |
| onChunkTransferred(reader) { |
| this._profileHeader._updateSaveProgress(reader.loadedSize(), reader.fileSize()); |
| } |
| |
| /** |
| * @override |
| * @param {!Bindings.ChunkedReader} reader |
| * @param {!Event} event |
| */ |
| onError(reader, event) { |
| Common.console.error('Failed to read heap snapshot from temp file: ' + /** @type {!ErrorEvent} */ (event).message); |
| this.onTransferFinished(); |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapTrackingOverviewGrid = class extends UI.VBox { |
| /** |
| * @param {!Profiler.HeapProfileHeader} heapProfileHeader |
| */ |
| constructor(heapProfileHeader) { |
| super(); |
| this.element.id = 'heap-recording-view'; |
| this.element.classList.add('heap-tracking-overview'); |
| |
| this._overviewContainer = this.element.createChild('div', 'heap-overview-container'); |
| this._overviewGrid = new UI.OverviewGrid('heap-recording'); |
| this._overviewGrid.element.classList.add('fill'); |
| |
| this._overviewCanvas = this._overviewContainer.createChild('canvas', 'heap-recording-overview-canvas'); |
| this._overviewContainer.appendChild(this._overviewGrid.element); |
| this._overviewCalculator = new Profiler.HeapTrackingOverviewGrid.OverviewCalculator(); |
| this._overviewGrid.addEventListener(UI.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); |
| |
| this._profileSamples = heapProfileHeader.fromFile() ? new Profiler.TrackingHeapSnapshotProfileType.Samples() : |
| heapProfileHeader._profileSamples; |
| this._profileType = heapProfileHeader.profileType(); |
| if (!heapProfileHeader.fromFile() && heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) { |
| this._profileType.addEventListener( |
| Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); |
| this._profileType.addEventListener( |
| Profiler.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); |
| } |
| this._windowLeft = 0.0; |
| this._windowRight = 1.0; |
| this._overviewGrid.setWindow(this._windowLeft, this._windowRight); |
| this._yScale = new Profiler.HeapTrackingOverviewGrid.SmoothScale(); |
| this._xScale = new Profiler.HeapTrackingOverviewGrid.SmoothScale(); |
| } |
| |
| dispose() { |
| this._onStopTracking(); |
| } |
| |
| _onStopTracking() { |
| this._profileType.removeEventListener( |
| Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); |
| this._profileType.removeEventListener( |
| Profiler.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); |
| } |
| |
| _onHeapStatsUpdate(event) { |
| this._profileSamples = event.data; |
| this._scheduleUpdate(); |
| } |
| |
| /** |
| * @param {?Profiler.HeapSnapshotCommon.Samples} samples |
| */ |
| _setSamples(samples) { |
| if (!samples) |
| return; |
| console.assert(!this._profileSamples.timestamps.length, 'Should only call this method when loading from file.'); |
| console.assert(samples.timestamps.length); |
| this._profileSamples = new Profiler.TrackingHeapSnapshotProfileType.Samples(); |
| this._profileSamples.sizes = samples.sizes; |
| this._profileSamples.ids = samples.lastAssignedIds; |
| this._profileSamples.timestamps = samples.timestamps; |
| this._profileSamples.max = samples.sizes; |
| this._profileSamples.totalTime = /** @type{number} */ (samples.timestamps.peekLast()); |
| this.update(); |
| } |
| |
| /** |
| * @param {number} width |
| * @param {number} height |
| */ |
| _drawOverviewCanvas(width, height) { |
| if (!this._profileSamples) |
| return; |
| var profileSamples = this._profileSamples; |
| var sizes = profileSamples.sizes; |
| var topSizes = profileSamples.max; |
| var timestamps = profileSamples.timestamps; |
| var startTime = timestamps[0]; |
| var endTime = timestamps[timestamps.length - 1]; |
| |
| var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime); |
| var maxSize = 0; |
| /** |
| * @param {!Array.<number>} sizes |
| * @param {function(number, number):void} callback |
| */ |
| function aggregateAndCall(sizes, callback) { |
| var size = 0; |
| var currentX = 0; |
| for (var i = 1; i < timestamps.length; ++i) { |
| var x = Math.floor((timestamps[i] - startTime) * scaleFactor); |
| if (x !== currentX) { |
| if (size) |
| callback(currentX, size); |
| size = 0; |
| currentX = x; |
| } |
| size += sizes[i]; |
| } |
| callback(currentX, size); |
| } |
| |
| /** |
| * @param {number} x |
| * @param {number} size |
| */ |
| function maxSizeCallback(x, size) { |
| maxSize = Math.max(maxSize, size); |
| } |
| |
| aggregateAndCall(sizes, maxSizeCallback); |
| |
| var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0); |
| |
| this._overviewCanvas.width = width * window.devicePixelRatio; |
| this._overviewCanvas.height = height * window.devicePixelRatio; |
| this._overviewCanvas.style.width = width + 'px'; |
| this._overviewCanvas.style.height = height + 'px'; |
| |
| var context = this._overviewCanvas.getContext('2d'); |
| context.scale(window.devicePixelRatio, window.devicePixelRatio); |
| |
| context.beginPath(); |
| context.lineWidth = 2; |
| context.strokeStyle = 'rgba(192, 192, 192, 0.6)'; |
| var currentX = (endTime - startTime) * scaleFactor; |
| context.moveTo(currentX, height - 1); |
| context.lineTo(currentX, 0); |
| context.stroke(); |
| context.closePath(); |
| |
| var gridY; |
| var gridValue; |
| var gridLabelHeight = 14; |
| if (yScaleFactor) { |
| const maxGridValue = (height - gridLabelHeight) / yScaleFactor; |
| // The round value calculation is a bit tricky, because |
| // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer, |
| // e.g. a round value 10KB is 10240 bytes. |
| gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024))); |
| gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10)); |
| if (gridValue * 5 <= maxGridValue) |
| gridValue *= 5; |
| gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5; |
| context.beginPath(); |
| context.lineWidth = 1; |
| context.strokeStyle = 'rgba(0, 0, 0, 0.2)'; |
| context.moveTo(0, gridY); |
| context.lineTo(width, gridY); |
| context.stroke(); |
| context.closePath(); |
| } |
| |
| /** |
| * @param {number} x |
| * @param {number} size |
| */ |
| function drawBarCallback(x, size) { |
| context.moveTo(x, height - 1); |
| context.lineTo(x, Math.round(height - size * yScaleFactor - 1)); |
| } |
| |
| context.beginPath(); |
| context.lineWidth = 2; |
| context.strokeStyle = 'rgba(192, 192, 192, 0.6)'; |
| aggregateAndCall(topSizes, drawBarCallback); |
| context.stroke(); |
| context.closePath(); |
| |
| context.beginPath(); |
| context.lineWidth = 2; |
| context.strokeStyle = 'rgba(0, 0, 192, 0.8)'; |
| aggregateAndCall(sizes, drawBarCallback); |
| context.stroke(); |
| context.closePath(); |
| |
| if (gridValue) { |
| var label = Number.bytesToString(gridValue); |
| var labelPadding = 4; |
| var labelX = 0; |
| var labelY = gridY - 0.5; |
| var labelWidth = 2 * labelPadding + context.measureText(label).width; |
| context.beginPath(); |
| context.textBaseline = 'bottom'; |
| context.font = '10px ' + window.getComputedStyle(this.element, null).getPropertyValue('font-family'); |
| context.fillStyle = 'rgba(255, 255, 255, 0.75)'; |
| context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight); |
| context.fillStyle = 'rgb(64, 64, 64)'; |
| context.fillText(label, labelX + labelPadding, labelY); |
| context.fill(); |
| context.closePath(); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| onResize() { |
| this._updateOverviewCanvas = true; |
| this._scheduleUpdate(); |
| } |
| |
| _onWindowChanged() { |
| if (!this._updateGridTimerId) |
| this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10); |
| } |
| |
| _scheduleUpdate() { |
| if (this._updateTimerId) |
| return; |
| this._updateTimerId = setTimeout(this.update.bind(this), 10); |
| } |
| |
| _updateBoundaries() { |
| this._windowLeft = this._overviewGrid.windowLeft(); |
| this._windowRight = this._overviewGrid.windowRight(); |
| this._windowWidth = this._windowRight - this._windowLeft; |
| } |
| |
| update() { |
| this._updateTimerId = null; |
| if (!this.isShowing()) |
| return; |
| this._updateBoundaries(); |
| this._overviewCalculator._updateBoundaries(this); |
| this._overviewGrid.updateDividers(this._overviewCalculator); |
| this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20); |
| } |
| |
| _updateGrid() { |
| this._updateGridTimerId = 0; |
| this._updateBoundaries(); |
| var ids = this._profileSamples.ids; |
| var timestamps = this._profileSamples.timestamps; |
| var sizes = this._profileSamples.sizes; |
| var startTime = timestamps[0]; |
| var totalTime = this._profileSamples.totalTime; |
| var timeLeft = startTime + totalTime * this._windowLeft; |
| var timeRight = startTime + totalTime * this._windowRight; |
| var minId = 0; |
| var maxId = ids[ids.length - 1] + 1; |
| var size = 0; |
| for (var i = 0; i < timestamps.length; ++i) { |
| if (!timestamps[i]) |
| continue; |
| if (timestamps[i] > timeRight) |
| break; |
| maxId = ids[i]; |
| if (timestamps[i] < timeLeft) { |
| minId = ids[i]; |
| continue; |
| } |
| size += sizes[i]; |
| } |
| |
| this.dispatchEventToListeners( |
| Profiler.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size}); |
| } |
| }; |
| |
| Profiler.HeapTrackingOverviewGrid.IdsRangeChanged = 'IdsRangeChanged'; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapTrackingOverviewGrid.SmoothScale = class { |
| constructor() { |
| this._lastUpdate = 0; |
| this._currentScale = 0.0; |
| } |
| |
| /** |
| * @param {number} target |
| * @return {number} |
| */ |
| nextScale(target) { |
| target = target || this._currentScale; |
| if (this._currentScale) { |
| var now = Date.now(); |
| var timeDeltaMs = now - this._lastUpdate; |
| this._lastUpdate = now; |
| var maxChangePerSec = 20; |
| var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000); |
| var scaleChange = target / this._currentScale; |
| this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta); |
| } else { |
| this._currentScale = target; |
| } |
| return this._currentScale; |
| } |
| }; |
| |
| /** |
| * @implements {UI.TimelineGrid.Calculator} |
| * @unrestricted |
| */ |
| Profiler.HeapTrackingOverviewGrid.OverviewCalculator = class { |
| /** |
| * @override |
| * @return {number} |
| */ |
| paddingLeft() { |
| return 0; |
| } |
| |
| /** |
| * @param {!Profiler.HeapTrackingOverviewGrid} chart |
| */ |
| _updateBoundaries(chart) { |
| this._minimumBoundaries = 0; |
| this._maximumBoundaries = chart._profileSamples.totalTime; |
| this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries; |
| } |
| |
| /** |
| * @override |
| * @param {number} time |
| * @return {number} |
| */ |
| computePosition(time) { |
| return (time - this._minimumBoundaries) * this._xScaleFactor; |
| } |
| |
| /** |
| * @override |
| * @param {number} value |
| * @param {number=} precision |
| * @return {string} |
| */ |
| formatValue(value, precision) { |
| return Number.secondsToString(value / 1000, !!precision); |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| maximumBoundary() { |
| return this._maximumBoundaries; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| minimumBoundary() { |
| return this._minimumBoundaries; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| zeroTime() { |
| return this._minimumBoundaries; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| boundarySpan() { |
| return this._maximumBoundaries - this._minimumBoundaries; |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapSnapshotStatisticsView = class extends UI.VBox { |
| constructor() { |
| super(); |
| this.setMinimumSize(50, 25); |
| this._pieChart = new UI.PieChart(150, Profiler.HeapSnapshotStatisticsView._valueFormatter, true); |
| this._pieChart.element.classList.add('heap-snapshot-stats-pie-chart'); |
| this.element.appendChild(this._pieChart.element); |
| this._labels = this.element.createChild('div', 'heap-snapshot-stats-legend'); |
| } |
| |
| /** |
| * @param {number} value |
| * @return {string} |
| */ |
| static _valueFormatter(value) { |
| return Common.UIString('%s KB', Number.withThousandsSeparator(Math.round(value / 1024))); |
| } |
| |
| /** |
| * @param {number} value |
| */ |
| setTotal(value) { |
| this._pieChart.setTotal(value); |
| } |
| |
| /** |
| * @param {number} value |
| * @param {string} name |
| * @param {string=} color |
| */ |
| addRecord(value, name, color) { |
| if (color) |
| this._pieChart.addSlice(value, color); |
| |
| var node = this._labels.createChild('div'); |
| var swatchDiv = node.createChild('div', 'heap-snapshot-stats-swatch'); |
| var nameDiv = node.createChild('div', 'heap-snapshot-stats-name'); |
| var sizeDiv = node.createChild('div', 'heap-snapshot-stats-size'); |
| if (color) |
| swatchDiv.style.backgroundColor = color; |
| else |
| swatchDiv.classList.add('heap-snapshot-stats-empty-swatch'); |
| nameDiv.textContent = name; |
| sizeDiv.textContent = Profiler.HeapSnapshotStatisticsView._valueFormatter(value); |
| } |
| }; |
| |
| |
| /** |
| * @unrestricted |
| */ |
| Profiler.HeapAllocationStackView = class extends UI.Widget { |
| /** |
| * @param {?SDK.Target} target |
| */ |
| constructor(target) { |
| super(); |
| this._target = target; |
| this._linkifier = new Components.Linkifier(); |
| } |
| |
| /** |
| * @param {!Profiler.HeapSnapshotProxy} snapshot |
| * @param {number} snapshotNodeIndex |
| */ |
| setAllocatedObject(snapshot, snapshotNodeIndex) { |
| this.clear(); |
| snapshot.allocationStack(snapshotNodeIndex, this._didReceiveAllocationStack.bind(this)); |
| } |
| |
| clear() { |
| this.element.removeChildren(); |
| this._linkifier.reset(); |
| } |
| |
| /** |
| * @param {?Array.<!Profiler.HeapSnapshotCommon.AllocationStackFrame>} frames |
| */ |
| _didReceiveAllocationStack(frames) { |
| if (!frames) { |
| var stackDiv = this.element.createChild('div', 'no-heap-allocation-stack'); |
| stackDiv.createTextChild(Common.UIString( |
| 'Stack was not recorded for this object because it had been allocated before this profile recording started.')); |
| return; |
| } |
| |
| var stackDiv = this.element.createChild('div', 'heap-allocation-stack'); |
| for (var i = 0; i < frames.length; i++) { |
| var frame = frames[i]; |
| var frameDiv = stackDiv.createChild('div', 'stack-frame'); |
| var name = frameDiv.createChild('div'); |
| name.textContent = UI.beautifyFunctionName(frame.functionName); |
| if (frame.scriptId) { |
| var urlElement = this._linkifier.linkifyScriptLocation( |
| this._target, String(frame.scriptId), frame.scriptName, frame.line - 1, frame.column - 1); |
| frameDiv.appendChild(urlElement); |
| } |
| } |
| } |
| }; |