UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

535 lines (461 loc) 15.3 kB
// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @implements {UI.Searchable} * @unrestricted */ Profiler.ProfileView = class extends UI.SimpleView { constructor() { super(Common.UIString('Profile')); this._searchableView = new UI.SearchableView(this); this._searchableView.setPlaceholder(Common.UIString('Find by cost (>50ms), name or file')); this._searchableView.show(this.element); const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([]); columns.push({ id: 'self', title: this.columnHeader('self'), width: '120px', fixedWidth: true, sortable: true, sort: DataGrid.DataGrid.Order.Descending }); columns.push({id: 'total', title: this.columnHeader('total'), width: '120px', fixedWidth: true, sortable: true}); columns.push({id: 'function', title: Common.UIString('Function'), disclosure: true, sortable: true}); this.dataGrid = new DataGrid.DataGrid(columns); this.dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._sortProfile, this); this.dataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, this._nodeSelected.bind(this, true)); this.dataGrid.addEventListener(DataGrid.DataGrid.Events.DeselectedNode, this._nodeSelected.bind(this, false)); this.viewSelectComboBox = new UI.ToolbarComboBox(this._changeView.bind(this)); this.focusButton = new UI.ToolbarButton(Common.UIString('Focus selected function'), 'largeicon-visibility'); this.focusButton.setEnabled(false); this.focusButton.addEventListener(UI.ToolbarButton.Events.Click, this._focusClicked, this); this.excludeButton = new UI.ToolbarButton(Common.UIString('Exclude selected function'), 'largeicon-delete'); this.excludeButton.setEnabled(false); this.excludeButton.addEventListener(UI.ToolbarButton.Events.Click, this._excludeClicked, this); this.resetButton = new UI.ToolbarButton(Common.UIString('Restore all functions'), 'largeicon-refresh'); this.resetButton.setEnabled(false); this.resetButton.addEventListener(UI.ToolbarButton.Events.Click, this._resetClicked, this); this._linkifier = new Components.Linkifier(Profiler.ProfileView._maxLinkLength); } /** * @param {!Array<!{title: string, value: string}>} entryInfo * @return {!Element} */ static buildPopoverTable(entryInfo) { const table = createElement('table'); for (const entry of entryInfo) { const row = table.createChild('tr'); row.createChild('td').textContent = entry.title; row.createChild('td').textContent = entry.value; } return table; } /** * @param {!Profiler.ProfileDataGridNode.Formatter} nodeFormatter * @param {!Array<string>=} viewTypes * @protected */ initialize(nodeFormatter, viewTypes) { this._nodeFormatter = nodeFormatter; this._viewType = Common.settings.createSetting('profileView', Profiler.ProfileView.ViewTypes.Heavy); viewTypes = viewTypes || [ Profiler.ProfileView.ViewTypes.Flame, Profiler.ProfileView.ViewTypes.Heavy, Profiler.ProfileView.ViewTypes.Tree ]; const optionNames = new Map([ [Profiler.ProfileView.ViewTypes.Flame, ls`Chart`], [Profiler.ProfileView.ViewTypes.Heavy, ls`Heavy (Bottom Up)`], [Profiler.ProfileView.ViewTypes.Tree, ls`Tree (Top Down)`], [Profiler.ProfileView.ViewTypes.Text, ls`Text (Top Down)`], ]); const options = new Map(viewTypes.map(type => [type, this.viewSelectComboBox.createOption(optionNames.get(type), '', type)])); const optionName = this._viewType.get() || viewTypes[0]; const option = options.get(optionName) || options.get(viewTypes[0]); this.viewSelectComboBox.select(option); this._changeView(); if (this._flameChart) this._flameChart.update(); } /** * @override */ focus() { if (this._flameChart) this._flameChart.focus(); else super.focus(); } /** * @param {string} columnId * @return {string} */ columnHeader(columnId) { throw 'Not implemented'; } /** * @param {number} timeLeft * @param {number} timeRight */ selectRange(timeLeft, timeRight) { if (!this._flameChart) return; this._flameChart.selectRange(timeLeft, timeRight); } /** * @override * @return {!Array.<!UI.ToolbarItem>} */ syncToolbarItems() { return [this.viewSelectComboBox, this.focusButton, this.excludeButton, this.resetButton]; } /** * @return {!Profiler.ProfileDataGridTree} */ _getBottomUpProfileDataGridTree() { if (!this._bottomUpProfileDataGridTree) { this._bottomUpProfileDataGridTree = new Profiler.BottomUpProfileDataGridTree( this._nodeFormatter, this._searchableView, this.profile.root, this.adjustedTotal); } return this._bottomUpProfileDataGridTree; } /** * @return {!Profiler.ProfileDataGridTree} */ _getTopDownProfileDataGridTree() { if (!this._topDownProfileDataGridTree) { this._topDownProfileDataGridTree = new Profiler.TopDownProfileDataGridTree( this._nodeFormatter, this._searchableView, this.profile.root, this.adjustedTotal); } return this._topDownProfileDataGridTree; } /** * @override */ willHide() { this._currentSearchResultIndex = -1; } refresh() { const selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; this.dataGrid.rootNode().removeChildren(); const children = this.profileDataGridTree.children; const count = children.length; for (let index = 0; index < count; ++index) this.dataGrid.rootNode().appendChild(children[index]); if (selectedProfileNode) selectedProfileNode.selected = true; } refreshVisibleData() { let child = this.dataGrid.rootNode().children[0]; while (child) { child.refresh(); child = child.traverseNextNode(false, null, true); } } /** * @return {!UI.SearchableView} */ searchableView() { return this._searchableView; } /** * @override * @return {boolean} */ supportsCaseSensitiveSearch() { return true; } /** * @override * @return {boolean} */ supportsRegexSearch() { return false; } /** * @override */ searchCanceled() { this._searchableElement.searchCanceled(); } /** * @override * @param {!UI.SearchableView.SearchConfig} searchConfig * @param {boolean} shouldJump * @param {boolean=} jumpBackwards */ performSearch(searchConfig, shouldJump, jumpBackwards) { this._searchableElement.performSearch(searchConfig, shouldJump, jumpBackwards); } /** * @override */ jumpToNextSearchResult() { this._searchableElement.jumpToNextSearchResult(); } /** * @override */ jumpToPreviousSearchResult() { this._searchableElement.jumpToPreviousSearchResult(); } /** * @return {!Components.Linkifier} */ linkifier() { return this._linkifier; } _ensureTextViewCreated() { if (this._textView) return; this._textView = new UI.SimpleView(ls`Call tree`); this._textView.registerRequiredCSS('profiler/profilesPanel.css'); this.populateTextView(this._textView); } /** * @param {!UI.SimpleView} view */ populateTextView(view) { } /** * @return {!PerfUI.FlameChartDataProvider} */ createFlameChartDataProvider() { throw 'Not implemented'; } _ensureFlameChartCreated() { if (this._flameChart) return; this._dataProvider = this.createFlameChartDataProvider(); this._flameChart = new Profiler.CPUProfileFlameChart(this._searchableView, this._dataProvider); this._flameChart.addEventListener(PerfUI.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this)); } /** * @param {!Common.Event} event */ _onEntrySelected(event) { const entryIndex = event.data; const node = this._dataProvider._entryNodes[entryIndex]; const debuggerModel = this._profileHeader._debuggerModel; if (!node || !node.scriptId || !debuggerModel) return; const script = debuggerModel.scriptForId(node.scriptId); if (!script) return; const location = /** @type {!SDK.DebuggerModel.Location} */ ( debuggerModel.createRawLocation(script, node.lineNumber, node.columnNumber)); Common.Revealer.reveal(Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(location)); } _changeView() { if (!this.profile) return; this._searchableView.closeSearch(); if (this._visibleView) this._visibleView.detach(); this._viewType.set(this.viewSelectComboBox.selectedOption().value); switch (this._viewType.get()) { case Profiler.ProfileView.ViewTypes.Flame: this._ensureFlameChartCreated(); this._visibleView = this._flameChart; this._searchableElement = this._flameChart; break; case Profiler.ProfileView.ViewTypes.Tree: this.profileDataGridTree = this._getTopDownProfileDataGridTree(); this._sortProfile(); this._visibleView = this.dataGrid.asWidget(); this._searchableElement = this.profileDataGridTree; break; case Profiler.ProfileView.ViewTypes.Heavy: this.profileDataGridTree = this._getBottomUpProfileDataGridTree(); this._sortProfile(); this._visibleView = this.dataGrid.asWidget(); this._searchableElement = this.profileDataGridTree; break; case Profiler.ProfileView.ViewTypes.Text: this._ensureTextViewCreated(); this._visibleView = this._textView; this._searchableElement = this._textView; break; } const isFlame = this._viewType.get() === Profiler.ProfileView.ViewTypes.Flame; this.focusButton.setVisible(!isFlame); this.excludeButton.setVisible(!isFlame); this.resetButton.setVisible(!isFlame); this._visibleView.show(this._searchableView.element); } /** * @param {boolean} selected */ _nodeSelected(selected) { this.focusButton.setEnabled(selected); this.excludeButton.setEnabled(selected); } /** * @param {!Common.Event} event */ _focusClicked(event) { if (!this.dataGrid.selectedNode) return; this.resetButton.setEnabled(true); this.profileDataGridTree.focus(this.dataGrid.selectedNode); this.refresh(); this.refreshVisibleData(); Host.userMetrics.actionTaken(Host.UserMetrics.Action.CpuProfileNodeFocused); } /** * @param {!Common.Event} event */ _excludeClicked(event) { const selectedNode = this.dataGrid.selectedNode; if (!selectedNode) return; selectedNode.deselect(); this.resetButton.setEnabled(true); this.profileDataGridTree.exclude(selectedNode); this.refresh(); this.refreshVisibleData(); Host.userMetrics.actionTaken(Host.UserMetrics.Action.CpuProfileNodeExcluded); } /** * @param {!Common.Event} event */ _resetClicked(event) { this.resetButton.setEnabled(false); this.profileDataGridTree.restore(); this._linkifier.reset(); this.refresh(); this.refreshVisibleData(); } _sortProfile() { const sortAscending = this.dataGrid.isSortOrderAscending(); const sortColumnId = this.dataGrid.sortColumnId(); const sortProperty = sortColumnId === 'function' ? 'functionName' : sortColumnId || ''; this.profileDataGridTree.sort(Profiler.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); this.refresh(); } }; Profiler.ProfileView._maxLinkLength = 30; /** @enum {string} */ Profiler.ProfileView.ViewTypes = { Flame: 'Flame', Tree: 'Tree', Heavy: 'Heavy', Text: 'Text' }; /** * @implements {Common.OutputStream} * @unrestricted */ Profiler.WritableProfileHeader = class extends Profiler.ProfileHeader { /** * @param {?SDK.DebuggerModel} debuggerModel * @param {!Profiler.ProfileType} type * @param {string=} title */ constructor(debuggerModel, type, title) { super(type, title || Common.UIString('Profile %d', type.nextProfileUid())); this._debuggerModel = debuggerModel; this._tempFile = null; } /** * @param {!Bindings.ChunkedReader} reader */ _onChunkTransferred(reader) { this.updateStatus(Common.UIString('Loading\u2026 %d%%', Number.bytesToString(this._jsonifiedProfile.length))); } /** * @param {!Bindings.ChunkedReader} reader */ _onError(reader) { this.updateStatus(Common.UIString(`File '%s' read error: %s`, reader.fileName(), reader.error().message)); } /** * @override * @param {string} text * @return {!Promise} */ async write(text) { this._jsonifiedProfile += text; } /** * @override */ close() { } /** * @override */ dispose() { this.removeTempFile(); } /** * @override * @param {!Profiler.ProfileType.DataDisplayDelegate} panel * @return {!Profiler.ProfileSidebarTreeElement} */ createSidebarTreeElement(panel) { return new Profiler.ProfileSidebarTreeElement(panel, this, 'profile-sidebar-tree-item'); } /** * @override * @return {boolean} */ canSaveToFile() { return !this.fromFile() && this._protocolProfile; } /** * @override */ async saveToFile() { const fileOutputStream = new Bindings.FileOutputStream(); this._fileName = this._fileName || `${this.profileType().typeName()}-${new Date().toISO8601Compact()}${this.profileType().fileExtension()}`; const accepted = await fileOutputStream.open(this._fileName); if (!accepted || !this._tempFile) return; const data = await this._tempFile.read(); if (data) await fileOutputStream.write(data); fileOutputStream.close(); } /** * @override * @param {!File} file * @return {!Promise<?Error>} */ async loadFromFile(file) { this.updateStatus(Common.UIString('Loading\u2026'), true); const fileReader = new Bindings.ChunkedFileReader(file, 10000000, this._onChunkTransferred.bind(this)); this._jsonifiedProfile = ''; const success = await fileReader.read(this); if (!success) { this._onError(fileReader); return new Error(Common.UIString('Failed to read file')); } this.updateStatus(Common.UIString('Parsing\u2026'), true); let error = null; try { this._profile = /** @type {!Protocol.Profiler.Profile} */ (JSON.parse(this._jsonifiedProfile)); this.setProfile(this._profile); this.updateStatus(Common.UIString('Loaded'), false); } catch (e) { error = e; this.profileType().removeProfile(this); } this._jsonifiedProfile = null; if (this.profileType().profileBeingRecorded() === this) this.profileType().setProfileBeingRecorded(null); return error; } /** * @param {!Protocol.Profiler.Profile} profile */ setProtocolProfile(profile) { this.setProfile(profile); this._protocolProfile = profile; this._tempFile = new Bindings.TempFile(); this._tempFile.write([JSON.stringify(profile)]); if (this.canSaveToFile()) this.dispatchEventToListeners(Profiler.ProfileHeader.Events.ProfileReceived); } };