From 13271200244973ff1356133f3c5918a059fc43bc Mon Sep 17 00:00:00 2001 From: Madeline MacDonald Date: Fri, 13 Jul 2018 14:24:49 -0700 Subject: [PATCH] Profiler view templates (#1915) * Initial view template framework * Removing some templates, reordering drop down * Fixing comments and formatting * Adding issue reference for commented code --- .../profiler/contrib/profiler.contribution.ts | 245 +++++++++++++----- .../dialog/profilerColumnEditorDialog.ts | 8 + .../parts/profiler/editor/profilerEditor.ts | 34 ++- .../parts/profiler/editor/profilerInput.ts | 80 +++--- src/sql/parts/profiler/service/interfaces.ts | 25 +- .../parts/profiler/service/profilerService.ts | 8 +- 6 files changed, 255 insertions(+), 145 deletions(-) diff --git a/src/sql/parts/profiler/contrib/profiler.contribution.ts b/src/sql/parts/profiler/contrib/profiler.contribution.ts index c77b21d962..316585da86 100644 --- a/src/sql/parts/profiler/contrib/profiler.contribution.ts +++ b/src/sql/parts/profiler/contrib/profiler.contribution.ts @@ -13,7 +13,7 @@ import * as nls from 'vs/nls'; import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput'; import { ProfilerEditor } from 'sql/parts/profiler/editor/profilerEditor'; -import { PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces'; +import { PROFILER_VIEW_TEMPLATE_SETTINGS, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces'; const profilerDescriptor = new EditorDescriptor( ProfilerEditor, @@ -24,84 +24,213 @@ const profilerDescriptor = new EditorDescriptor( Registry.as(EditorExtensions.Editors) .registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]); -const profilerSessionTemplateSchema: IJSONSchema = { - description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"), - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string' - } - } - }, - default: >[ - { - name: 'Standard', - events: [ - { - name: 'Audit Login', - optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime', 'BinaryData'] - }, - { - name: 'Audit Logout', - optionalColumns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime'] - }, - { - name: 'ExistingConnection', - optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] - }, - { - name: 'RPC:Completed', - optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] - }, - { - name: 'SQL:BatchCompleted', - optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] - }, - { - name: 'SQL:BatchStarting', - optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] +const profilerViewTemplateSchema: IJSONSchema = { + description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"), + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' } - ], - view: { - events: [ + } + }, + default: >[ + { + name: 'Standard View', + columns: [ { - name: 'Audit Login', - columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] + name: 'EventClass', + eventsMapped: ['name'] }, { - name: 'Audit Logout', - columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime'] + name: 'TextData', + eventsMapped: ['options_text', 'batch_text'] }, { - name: 'ExistingConnection', - columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] + name: 'ApplicationName', + width: '1', + eventsMapped: ['client_app_name'] }, { - name: 'RPC:Completed', - columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] + name: 'NTUserName', + eventsMapped: ['nt_username'] }, { - name: 'SQL:BatchCompleted', - columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] + name: 'LoginName', + eventsMapped: ['server_principal_name'] }, { - name: 'SQL:BatchStarting', - columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] + name: 'ClientProcessID', + eventsMapped: ['client_pid'] + }, + { + name: 'SPID', + eventsMapped: ['session_id'] + }, + { + name: 'StartTime', + eventsMapped: ['timestamp'] + }, + { + name: 'CPU', + eventsMapped: ['cpu_time'] + }, + { + name: 'Reads', + eventsMapped: ['logical_reads'] + }, + { + name: 'Writes', + eventsMapped: ['writes'] + }, + { + name: 'Duration', + eventsMapped: ['duration'] + } + ] + }, + { + name: 'TSQL View', + columns: [ + { + name: 'EventClass', + eventsMapped: ['name'] + }, + { + name: 'TextData', + eventsMapped: ['options_text', 'batch_text'] + }, + { + name: 'SPID', + eventsMapped: ['session_id'] + }, + { + name: 'StartTime', + eventsMapped: ['timestamp'] + } + ] + }, + { + name: 'Tuning View', + columns: [ + { + name: 'EventClass', + eventsMapped: ['name'] + }, + { + name: 'TextData', + eventsMapped: ['options_text', 'batch_text'] + }, + { + name: 'Duration', + eventsMapped: ['duration'] + }, + { + name: 'SPID', + eventsMapped: ['session_id'] + }, + { + name: 'DatabaseID', + eventsMapped: ['database_id'] + }, + { + name: 'DatabaseName', + eventsMapped: ['database_name'] + }, + { + name: 'ObjectType', + eventsMapped: ['object_type'] + }, + { + name: 'LoginName', + eventsMapped: ['server_principal_name'] + } + ] + }, + { + name: 'TSQL_Locks View', + columns: [ + { + name: 'EventClass', + eventsMapped: ['name'] + }, + { + name: 'TextData', + eventsMapped: ['options_text', 'batch_text'] + }, + { + name: 'ApplicationName', + eventsMapped: ['client_app_name'] + }, + { + name: 'NTUserName', + eventsMapped: ['nt_username'] + }, + { + name: 'LoginName', + eventsMapped: ['server_principal_name'] + }, + { + name: 'ClientProcessID', + eventsMapped: ['client_pid'] + }, + { + name: 'SPID', + eventsMapped: ['session_id'] + }, + { + name: 'StartTime', + eventsMapped: ['timestamp'] + }, + { + name: 'CPU', + eventsMapped: ['cpu_time'] + }, + { + name: 'Reads', + eventsMapped: ['logical_reads'] + }, + { + name: 'Writes', + eventsMapped: ['writes'] + }, + { + name: 'Duration', + eventsMapped: ['duration'] + } + ] + }, + { + name: 'TSQL_Duration View', + columns: [ + { + name: 'EventClass', + eventsMapped: ['name'] + }, + { + name: 'Duration', + eventsMapped: ['duration'] + }, + { + name: 'TextData', + eventsMapped: ['options_text', 'batch_text'] + }, + { + name: 'SPID', + eventsMapped: ['session_id'] } ] } - } - ] -}; + ] + }; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); const dashboardConfig: IConfigurationNode = { id: 'Profiler', type: 'object', properties: { - [PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema + [PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema } }; diff --git a/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts b/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts index 8724558c87..2027170208 100644 --- a/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts +++ b/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts @@ -360,7 +360,10 @@ export class ProfilerColumnEditorDialog extends Modal { super.onAccept(e); } + // currently not used, this dialog is a work in progress + // tracked in issue #1545 https://github.com/Microsoft/sqlopsstudio/issues/1545 private _updateInput(): void { + /* this._element.getUnsortedChildren().forEach(e => { let origEvent = this._input.sessionTemplate.view.events.find(i => i.name === e.id); if (e.indeterminate) { @@ -387,9 +390,13 @@ export class ProfilerColumnEditorDialog extends Modal { }, []); newColumns.unshift('EventClass'); this._input.setColumns(newColumns); + */ } + // currently not used, this dialog is a work in progress + // tracked in issue #1545 https://github.com/Microsoft/sqlopsstudio/issues/1545 private _updateList(): void { + /* this._element = new SessionItem(this._input.sessionTemplate.name, this._selectedValue === 0 ? 'event' : 'column'); this._input.sessionTemplate.events.forEach(item => { let event = new EventItem(item.name, this._element); @@ -402,6 +409,7 @@ export class ProfilerColumnEditorDialog extends Modal { }); this._tree.setInput(this._element); this._tree.layout(DOM.getTotalHeight(this._treeContainer)); + */ } protected layout(height?: number): void { diff --git a/src/sql/parts/profiler/editor/profilerEditor.ts b/src/sql/parts/profiler/editor/profilerEditor.ts index 3112b0d8cd..472b53b811 100644 --- a/src/sql/parts/profiler/editor/profilerEditor.ts +++ b/src/sql/parts/profiler/editor/profilerEditor.ts @@ -8,7 +8,7 @@ import { ProfilerInput } from './profilerInput'; import { TabbedPanel } from 'sql/base/browser/ui/panel/panel'; import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; -import { IProfilerService, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces'; +import { IProfilerService, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { attachTableStyler } from 'sql/common/theme/styler'; import { IProfilerStateChangedEvent } from './profilerState'; @@ -114,8 +114,8 @@ export class ProfilerEditor extends BaseEditor { private _profilerEditorContextKey: IContextKey; - private _sessionTemplateSelector: SelectBox; - private _sessionTemplates: Array; + private _viewTemplateSelector: SelectBox; + private _viewTemplates: Array; // Actions private _connectAction: Actions.ProfilerConnect; @@ -125,6 +125,7 @@ export class ProfilerEditor extends BaseEditor { private _autoscrollAction: Actions.ProfilerAutoScroll; private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction; + constructor( @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @@ -189,27 +190,27 @@ export class ProfilerEditor extends BaseEditor { this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL); this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL); - this._sessionTemplates = this._profilerService.getSessionTemplates(); - this._sessionTemplateSelector = new SelectBox(this._sessionTemplates.map(i => i.name), 'Standard', this._contextViewService); - this._register(this._sessionTemplateSelector.onDidSelect(e => { + this._viewTemplates = this._profilerService.getViewTemplates(); + this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService); + this._register(this._viewTemplateSelector.onDidSelect(e => { if (this.input) { - this.input.sessionTemplate = this._sessionTemplates.find(i => i.name === e.selected); + this.input.viewTemplate = this._viewTemplates.find(i => i.name === e.selected); } })); let dropdownContainer = document.createElement('div'); dropdownContainer.style.width = '150px'; - this._sessionTemplateSelector.render(dropdownContainer); + this._viewTemplateSelector.render(dropdownContainer); - this._register(attachSelectBoxStyler(this._sessionTemplateSelector, this.themeService)); + this._register(attachSelectBoxStyler(this._viewTemplateSelector, this.themeService)); this._actionBar.setContent([ { action: this._startAction }, { action: this._stopAction }, - { element: dropdownContainer }, { element: Taskbar.createTaskbarSeparator() }, { action: this._pauseAction }, { action: this._autoscrollAction }, - { action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) } + { action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) }, + { element: dropdownContainer }, ]); } @@ -340,10 +341,10 @@ export class ProfilerEditor extends BaseEditor { return super.setInput(input, options).then(() => { this._profilerTableEditor.setInput(input); - if (input.sessionTemplate) { - this._sessionTemplateSelector.selectWithOptionName(input.sessionTemplate.name); + if (input.viewTemplate) { + this._viewTemplateSelector.selectWithOptionName(input.viewTemplate.name); } else { - input.sessionTemplate = this._sessionTemplates.find(i => i.name === 'Standard'); + input.viewTemplate = this._viewTemplates.find(i => i.name === 'Standard View'); } this._actionBar.context = input; @@ -401,10 +402,7 @@ export class ProfilerEditor extends BaseEditor { if (e.isConnected) { this._connectAction.connected = this.input.state.isConnected; - if (this.input.state.isConnected) { - this._sessionTemplateSelector.disable(); - } else { - this._sessionTemplateSelector.enable(); + if (!this.input.state.isConnected) { this._startAction.enabled = this.input.state.isConnected; this._stopAction.enabled = false; this._pauseAction.enabled = false; diff --git a/src/sql/parts/profiler/editor/profilerInput.ts b/src/sql/parts/profiler/editor/profilerInput.ts index c8fc4e2513..e5ac07f8a2 100644 --- a/src/sql/parts/profiler/editor/profilerInput.ts +++ b/src/sql/parts/profiler/editor/profilerInput.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; -import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces'; +import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces'; import { ProfilerState } from './profilerState'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; @@ -28,7 +28,10 @@ export class ProfilerInput extends EditorInput implements IProfilerSession { private _id: ProfilerSessionID; private _state: ProfilerState; private _columns: string[] = []; - private _sessionTemplate: IProfilerSessionTemplate; + private _viewTemplate: IProfilerViewTemplate; + // mapping of event categories to what column they display under + // used for coallescing multiple events with different names to the same column + private _columnMapping: { [event: string]: string } = {}; private _onColumnsChanged = new Emitter[]>(); public onColumnsChanged: Event[]> = this._onColumnsChanged.event; @@ -63,22 +66,26 @@ export class ProfilerInput extends EditorInput implements IProfilerSession { this._data = new TableDataView(undefined, searchFn); } - public set sessionTemplate(template: IProfilerSessionTemplate) { - this._sessionTemplate = template; - let newColumns = this.sessionTemplate.view.events.reduce>((p, e) => { - e.columns.forEach(c => { - if (!p.includes(c)) { - p.push(c); - } - }); + public set viewTemplate(template: IProfilerViewTemplate) { + this._data.clear(); + this._viewTemplate = template; + + let newColumns = this._viewTemplate.columns.reduce>((p, e) => { + p.push(e.name); return p; }, []); - newColumns.unshift('EventClass'); - this.setColumns(newColumns); + + let newMapping: { [event: string]: string } = {}; + this._viewTemplate.columns.forEach(c => { + c.eventsMapped.forEach(e => { + newMapping[e] = c.name; + }); + }); + this.setColumnMapping(newColumns, newMapping); } - public get sessionTemplate(): IProfilerSessionTemplate { - return this._sessionTemplate; + public get viewTemplate(): IProfilerViewTemplate { + return this._viewTemplate; } public getTypeId(): string { @@ -117,6 +124,12 @@ export class ProfilerInput extends EditorInput implements IProfilerSession { this._onColumnsChanged.fire(this.columns); } + public setColumnMapping(columns: Array, mapping: { [event: string]: string }) { + this._columns = columns; + this._columnMapping = mapping; + this._onColumnsChanged.fire(this.columns); + } + public get id(): ProfilerSessionID { return this._id; } @@ -140,51 +153,22 @@ export class ProfilerInput extends EditorInput implements IProfilerSession { } public onMoreRows(eventMessage: sqlops.ProfilerSessionEvents) { - if (eventMessage.eventsLost){ + if (eventMessage.eventsLost) { this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this._connection.serverName)); } - for (let i: number = 0; i < eventMessage.events.length && i < 500; ++i) { + for (let i: number = 0; i < eventMessage.events.length && i < 500; ++i) { let e: sqlops.ProfilerEvent = eventMessage.events[i]; let data = {}; - data['EventClass'] = e.name; + data['EventClass'] = e.name; data['StartTime'] = e.timestamp; - const columns = [ - 'TextData', - 'ApplicationName', - 'NTUserName', - 'LoginName', - 'CPU', - 'Reads', - 'Writes', - 'Duration', - 'ClientProcessID', - 'SPID', - 'StartTime', - 'EndTime', - 'BinaryData' - ]; - - let columnNameMap: Map = new Map(); - columnNameMap['client_app_name'] = 'ApplicationName'; - columnNameMap['nt_username'] = 'NTUserName'; - columnNameMap['options_text'] = 'TextData'; - columnNameMap['server_principal_name'] = 'LoginName'; - columnNameMap['session_id'] = 'SPID'; - columnNameMap['batch_text'] = 'TextData'; - columnNameMap['cpu_time'] = 'CPU'; - columnNameMap['duration'] = 'Duration'; - columnNameMap['logical_reads'] = 'Reads'; - columnNameMap['event_sequence'] = 'EventSequence'; - columnNameMap['client_pid'] = 'ClientProcessID'; - columnNameMap['writes'] = 'Writes'; // Using ' ' instead of '' fixed the error where clicking through events // with empty text fields causes future text panes to be highlighted. - // This is a temporary fix, and should be changed before the July release + // This is a temporary fix data['TextData'] = ' '; for (let key in e.values) { - let columnName = columnNameMap[key]; + let columnName = this._columnMapping[key]; if (columnName) { let value = e.values[key]; data[columnName] = value; diff --git a/src/sql/parts/profiler/service/interfaces.ts b/src/sql/parts/profiler/service/interfaces.ts index a4249ac6e8..31e1f97f13 100644 --- a/src/sql/parts/profiler/service/interfaces.ts +++ b/src/sql/parts/profiler/service/interfaces.ts @@ -15,7 +15,7 @@ export const IProfilerService = createDecorator(PROFILER_SERVI export type ProfilerSessionID = string; -export const PROFILER_SESSION_TEMPLATE_SETTINGS = 'profiler.sessionTemplates'; +export const PROFILER_VIEW_TEMPLATE_SETTINGS = 'profiler.viewTemplates'; export const PROFILER_SETTINGS = 'profiler'; /** @@ -84,7 +84,7 @@ export interface IProfilerService { * @returns An array of session templates that match the provider passed, if passed, and generic ones (no provider specified), * otherwise returns all session templates */ - getSessionTemplates(providerId?: string): Array; + getViewTemplates(providerId?: string): Array; /** * Launches the dialog for editing the view columns of a profiler session template for the given input * @param input input object that contains the necessary information which will be modified based on used input @@ -93,25 +93,16 @@ export interface IProfilerService { } export interface IProfilerSettings { - sessionTemplates: Array; + viewTemplates: Array; } -export interface IEventTemplate { +export interface IColumnViewTemplate { name: string; - optionalColumns: Array; + width: string; + eventsMapped: Array; } -export interface IEventViewTemplate { +export interface IProfilerViewTemplate { name: string; - columns: Array; -} - -export interface ISessionViewTemplate { - events: Array; -} - -export interface IProfilerSessionTemplate { - name: string; - events: Array; - view: ISessionViewTemplate; + columns: Array; } diff --git a/src/sql/parts/profiler/service/profilerService.ts b/src/sql/parts/profiler/service/profilerService.ts index dd69f3f23b..5543b77514 100644 --- a/src/sql/parts/profiler/service/profilerService.ts +++ b/src/sql/parts/profiler/service/profilerService.ts @@ -5,7 +5,7 @@ import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement'; import { - ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerSessionTemplate, + ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, PROFILER_SETTINGS, IProfilerSettings } from './interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; @@ -128,13 +128,13 @@ export class ProfilerService implements IProfilerService { } } - public getSessionTemplates(provider?: string): Array { + public getViewTemplates(provider?: string): Array { let config = this._configurationService.getValue(PROFILER_SETTINGS); if (provider) { - return config.sessionTemplates; + return config.viewTemplates; } else { - return config.sessionTemplates; + return config.viewTemplates; } }