/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement'; import { ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, IProfilerSessionTemplate, PROFILER_SETTINGS, IProfilerSettings, EngineType } from './interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput'; import { ProfilerColumnEditorDialog } from 'sql/workbench/parts/profiler/browser/profilerColumnEditorDialog'; import * as azdata from 'azdata'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Memento } from 'vs/workbench/common/memento'; import { ProfilerFilterDialog } from 'sql/workbench/parts/profiler/browser/profilerFilterDialog'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; class TwoWayMap { private forwardMap: Map; private reverseMap: Map; constructor() { this.forwardMap = new Map(); this.reverseMap = new Map(); } get(input: T): K { return this.forwardMap.get(input); } reverseGet(input: K): T { return this.reverseMap.get(input); } set(input: T, input2: K): TwoWayMap { this.forwardMap.set(input, input2); this.reverseMap.set(input2, input); return this; } } export class ProfilerService implements IProfilerService { private static readonly PROFILER_SERVICE_UI_STATE_STORAGE_KEY = 'profileservice.uiState'; public _serviceBrand: any; private _providers = new Map(); private _idMap = new TwoWayMap(); private _sessionMap = new Map(); private _connectionMap = new Map(); private _editColumnDialog: ProfilerColumnEditorDialog; private _memento: any; private _context: Memento; constructor( @IConnectionManagementService private _connectionService: IConnectionManagementService, @IConfigurationService public _configurationService: IConfigurationService, @IInstantiationService private _instantiationService: IInstantiationService, @INotificationService private _notificationService: INotificationService, @ICommandService private _commandService: ICommandService, @IStorageService private _storageService: IStorageService ) { this._context = new Memento('ProfilerEditor', this._storageService); this._memento = this._context.getMemento(StorageScope.GLOBAL); } public registerProvider(providerId: string, provider: azdata.ProfilerProvider): void { this._providers.set(providerId, provider); } public async registerSession(uri: string, connectionProfile: IConnectionProfile, session: IProfilerSession): Promise { let options: IConnectionCompletionOptions = { params: { connectionType: ConnectionType.default, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: undefined }, saveTheConnection: false, showDashboard: false, showConnectionDialogOnError: false, showFirewallRuleOnError: true }; try { await this._connectionService.connect(connectionProfile, uri, options); } catch (connectionError) { } this._sessionMap.set(uri, session); this._connectionMap.set(uri, connectionProfile); this._idMap.set(uri, uri); return Promise.resolve(uri); } public onMoreRows(params: azdata.ProfilerSessionEvents): void { this._sessionMap.get(this._idMap.reverseGet(params.sessionId)).onMoreRows(params); } public onSessionStopped(params: azdata.ProfilerSessionStoppedParams): void { this._sessionMap.get(this._idMap.reverseGet(params.ownerUri)).onSessionStopped(params); } public onProfilerSessionCreated(params: azdata.ProfilerSessionCreatedParams): void { this._sessionMap.get(this._idMap.reverseGet(params.ownerUri)).onProfilerSessionCreated(params); this.updateMemento(params.ownerUri, { previousSessionName: params.sessionName }); } public connectSession(id: ProfilerSessionID): Thenable { return this._runAction(id, provider => provider.connectSession(this._idMap.get(id))); } public disconnectSession(id: ProfilerSessionID): Thenable { return this._runAction(id, provider => provider.disconnectSession(this._idMap.get(id))); } public createSession(id: string, createStatement: string, template: azdata.ProfilerSessionTemplate): Thenable { return this._runAction(id, provider => provider.createSession(this._idMap.get(id), createStatement, template)).then(() => { this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false }); return true; }, (reason) => { this._notificationService.error(reason.message); }); } public startSession(id: ProfilerSessionID, sessionName: string): Thenable { this.updateMemento(id, { previousSessionName: sessionName }); return this._runAction(id, provider => provider.startSession(this._idMap.get(id), sessionName)).then(() => { this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false }); return true; }, (reason) => { this._notificationService.error(reason.message); }); } public pauseSession(id: ProfilerSessionID): Thenable { return this._runAction(id, provider => provider.pauseSession(this._idMap.get(id))); } public stopSession(id: ProfilerSessionID): Thenable { return this._runAction(id, provider => provider.stopSession(this._idMap.get(id))).then(() => { this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false }); return true; }, (reason) => { // The error won't be actionable to the user, so only log it to console. // In case of error, the state of the UI is not usable, makes more sense to // set it to stopped so that user can restart it or pick a different session this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false }); }); } public getXEventSessions(id: ProfilerSessionID): Thenable { return this._runAction(id, provider => provider.getXEventSessions(this._idMap.get(id))).then((r) => { return r; }, (reason) => { this._notificationService.error(reason.message); }); } private _runAction(id: ProfilerSessionID, action: (handler: azdata.ProfilerProvider) => Thenable): Thenable { let handler = this._providers.get(mssqlProviderName); if (handler) { return action(handler); } else { return Promise.reject(new Error('No Handler Registered')); } } public getViewTemplates(provider?: string): Array { let config = this._configurationService.getValue(PROFILER_SETTINGS); if (provider) { return config.viewTemplates; } else { return config.viewTemplates; } } public getSessionTemplates(provider?: string): Array { let config = this._configurationService.getValue(PROFILER_SETTINGS); if (provider) { return config.sessionTemplates; } else { return config.sessionTemplates; } } public getSessionViewState(ownerUri: string): any { let mementoKey = this.getMementoKey(ownerUri); let uiStateMap = this._memento[ProfilerService.PROFILER_SERVICE_UI_STATE_STORAGE_KEY]; if (uiStateMap && mementoKey) { return uiStateMap[mementoKey]; } return undefined; } private getMementoKey(ownerUri: string): string { let mementoKey = undefined; let connectionProfile: IConnectionProfile = this._connectionMap.get(ownerUri); if (connectionProfile) { mementoKey = connectionProfile.serverName; } return mementoKey; } private updateMemento(ownerUri: string, uiState: any) { // update persisted session state let mementoKey = this.getMementoKey(ownerUri); let uiStateMap = this._memento[ProfilerService.PROFILER_SERVICE_UI_STATE_STORAGE_KEY]; if (uiStateMap && mementoKey) { uiStateMap[mementoKey] = uiState; this._memento[ProfilerService.PROFILER_SERVICE_UI_STATE_STORAGE_KEY] = uiStateMap; this._context.saveMemento(); } } public launchColumnEditor(input?: ProfilerInput): Thenable { if (!this._editColumnDialog) { this._editColumnDialog = this._instantiationService.createInstance(ProfilerColumnEditorDialog); this._editColumnDialog.render(); } this._editColumnDialog.open(input); return Promise.resolve(null); } public launchCreateSessionDialog(input: ProfilerInput): Thenable { const serverInfo = this._connectionService.getConnectionInfo(input.id).serverInfo; let templates = this.getSessionTemplates(); if (serverInfo) { const engineType = serverInfo.isCloud ? EngineType.AzureSQLDB : EngineType.Standalone; // only use the templates that matches the following criteria: // 1. the template doesn't have any engine types specified - for backward compatibility (user with custom templates) or the templates applicable to both AzureSQLDB and standalone server // 2. the template supports the current engine type templates = templates.filter(template => !template.engineTypes || template.engineTypes.length === 0 || template.engineTypes.includes(engineType)); } return this._commandService.executeCommand('profiler.openCreateSessionDialog', input.id, input.providerType, templates); } public launchFilterSessionDialog(input: ProfilerInput): void { let dialog = this._instantiationService.createInstance(ProfilerFilterDialog); dialog.open(input); } }