Profiler view templates (#1915)

* Initial view template framework

* Removing some templates, reordering drop down

* Fixing comments and formatting

* Adding issue reference for commented code
This commit is contained in:
Madeline MacDonald
2018-07-13 14:24:49 -07:00
committed by GitHub
parent d2b5043972
commit 1327120024
6 changed files with 255 additions and 145 deletions

View File

@@ -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<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
const profilerSessionTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
}
},
default: <Array<IProfilerSessionTemplate>>[
{
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: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
],
view: {
events: [
}
},
default: <Array<IProfilerViewTemplate>>[
{
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<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const dashboardConfig: IConfigurationNode = {
id: 'Profiler',
type: 'object',
properties: {
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema
}
};

View File

@@ -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 {

View File

@@ -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<boolean>;
private _sessionTemplateSelector: SelectBox;
private _sessionTemplates: Array<IProfilerSessionTemplate>;
private _viewTemplateSelector: SelectBox;
private _viewTemplates: Array<IProfilerViewTemplate>;
// 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;

View File

@@ -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<Slick.Column<Slick.SlickData>[]>();
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
@@ -63,22 +66,26 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn);
}
public set sessionTemplate(template: IProfilerSessionTemplate) {
this._sessionTemplate = template;
let newColumns = this.sessionTemplate.view.events.reduce<Array<string>>((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<Array<string>>((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<string>, 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<string, string> = new Map<string, string>();
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;

View File

@@ -15,7 +15,7 @@ export const IProfilerService = createDecorator<IProfilerService>(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<IProfilerSessionTemplate>;
getViewTemplates(providerId?: string): Array<IProfilerViewTemplate>;
/**
* 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<IProfilerSessionTemplate>;
viewTemplates: Array<IProfilerViewTemplate>;
}
export interface IEventTemplate {
export interface IColumnViewTemplate {
name: string;
optionalColumns: Array<string>;
width: string;
eventsMapped: Array<string>;
}
export interface IEventViewTemplate {
export interface IProfilerViewTemplate {
name: string;
columns: Array<string>;
}
export interface ISessionViewTemplate {
events: Array<IEventViewTemplate>;
}
export interface IProfilerSessionTemplate {
name: string;
events: Array<IEventTemplate>;
view: ISessionViewTemplate;
columns: Array<IColumnViewTemplate>;
}

View File

@@ -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<IProfilerSessionTemplate> {
public getViewTemplates(provider?: string): Array<IProfilerViewTemplate> {
let config = <IProfilerSettings>this._configurationService.getValue(PROFILER_SETTINGS);
if (provider) {
return config.sessionTemplates;
return config.viewTemplates;
} else {
return config.sessionTemplates;
return config.viewTemplates;
}
}