ADS changes for opening XEL files (#23666)

* Initial set of changes for opening XEL files in ADS

* Code cleanup and update STS version

* Fix runtime errors

* Address comments

* Address comments and update Start button to be disabled for file session

* Code cleanup
This commit is contained in:
Sakshi Sharma
2023-07-09 10:22:12 -07:00
committed by GitHub
parent 3f19d0026e
commit 2fd2b79611
16 changed files with 136 additions and 27 deletions

View File

@@ -1298,9 +1298,14 @@ export interface StartProfilingParams {
ownerUri: string; ownerUri: string;
/** /**
* Session name * Session name or full path of XEL file to open
*/ */
sessionName: string; sessionName: string;
/**
* Identifies which type of target session name identifies
*/
sessionType: azdata.ProfilingSessionType;
} }
export interface StartProfilingResponse { } export interface StartProfilingResponse { }

View File

@@ -1001,10 +1001,11 @@ export class ProfilerFeature extends SqlOpsFeature<undefined> {
); );
}; };
let startSession = (ownerUri: string, sessionName: string): Thenable<boolean> => { let startSession = (ownerUri: string, sessionName: string, sessionType: azdata.ProfilingSessionType = azdata.ProfilingSessionType.RemoteSession): Thenable<boolean> => {
let params: contracts.StartProfilingParams = { let params: contracts.StartProfilingParams = {
ownerUri, ownerUri,
sessionName sessionName,
sessionType
}; };
return client.sendRequest(contracts.StartProfilingRequest.type, params).then( return client.sendRequest(contracts.StartProfilingRequest.type, params).then(

View File

@@ -51,6 +51,11 @@
"command": "profiler.openCreateSessionDialog", "command": "profiler.openCreateSessionDialog",
"title": "profiler.contributes.title.openCreateSessionDialog", "title": "profiler.contributes.title.openCreateSessionDialog",
"category": "%profiler.category%" "category": "%profiler.category%"
},
{
"command": "profiler.openFile",
"title": "%profiler.contributes.title.openXELFile%",
"category": "%profiler.category%"
} }
], ],
"menus": { "menus": {

View File

@@ -5,5 +5,6 @@
"profiler.contributes.title.start": "Start", "profiler.contributes.title.start": "Start",
"profiler.contributes.title.stop": "Stop", "profiler.contributes.title.stop": "Stop",
"profiler.contributes.title.openCreateSessionDialog": "Create Profiler Season", "profiler.contributes.title.openCreateSessionDialog": "Create Profiler Season",
"profiler.category": "Profiler" "profiler.category": "Profiler",
"profiler.contributes.title.openXELFile": "Open XEL File"
} }

View File

@@ -2028,4 +2028,13 @@ declare module 'azdata' {
*/ */
setActiveCell(row: number, column: number): void; setActiveCell(row: number, column: number): void;
} }
export interface ProfilerProvider {
startSession(sessionId: string, sessionName: string, sessionType?: ProfilingSessionType): Thenable<boolean>;
}
export enum ProfilingSessionType {
RemoteSession = 0,
LocalFile = 1
}
} }

View File

@@ -359,8 +359,8 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
createSession(sessionId: string, createStatement: string, template: azdata.ProfilerSessionTemplate): Thenable<boolean> { createSession(sessionId: string, createStatement: string, template: azdata.ProfilerSessionTemplate): Thenable<boolean> {
return self._proxy.$createSession(handle, sessionId, createStatement, template); return self._proxy.$createSession(handle, sessionId, createStatement, template);
}, },
startSession(sessionId: string, sessionName: string): Thenable<boolean> { startSession(sessionId: string, sessionName: string, sessionType?: azdata.ProfilingSessionType): Thenable<boolean> {
return self._proxy.$startSession(handle, sessionId, sessionName); return self._proxy.$startSession(handle, sessionId, sessionName, sessionType);
}, },
stopSession(sessionId: string): Thenable<boolean> { stopSession(sessionId: string): Thenable<boolean> {
return self._proxy.$stopSession(handle, sessionId); return self._proxy.$stopSession(handle, sessionId);

View File

@@ -686,8 +686,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
/** /**
* Start a profiler session * Start a profiler session
*/ */
public override $startSession(handle: number, sessionId: string, sessionName: string): Thenable<boolean> { public override $startSession(handle: number, sessionId: string, sessionName: string, sessionType?: azdata.ProfilingSessionType): Thenable<boolean> {
return this._resolveProvider<azdata.ProfilerProvider>(handle).startSession(sessionId, sessionName); return this._resolveProvider<azdata.ProfilerProvider>(handle).startSession(sessionId, sessionName, sessionType);
} }
/** /**
@@ -739,7 +739,6 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
this._proxy.$onProfilerSessionCreated(handle, response); this._proxy.$onProfilerSessionCreated(handle, response);
} }
/** /**
* Agent Job Provider methods * Agent Job Provider methods
*/ */

View File

@@ -687,7 +687,8 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
designers: designers, designers: designers,
executionPlan: executionPlan, executionPlan: executionPlan,
diagnostics: diagnostics, diagnostics: diagnostics,
env env,
ProfilingSessionType: sqlExtHostTypes.ProfilingSessionType
}; };
} }
}; };

View File

@@ -385,7 +385,7 @@ export abstract class ExtHostDataProtocolShape {
/** /**
* Start a profiler session * Start a profiler session
*/ */
$startSession(handle: number, sessionId: string, sessionName: string): Thenable<boolean> { throw ni(); } $startSession(handle: number, sessionId: string, sessionName: string, sessionType?: azdata.ProfilingSessionType): Thenable<boolean> { throw ni(); }
/** /**
* Stop a profiler session * Stop a profiler session

View File

@@ -219,6 +219,11 @@ export enum StepCompletionAction {
GoToStep = 4 GoToStep = 4
} }
export enum ProfilingSessionType {
RemoteSession = 0,
LocalFile = 1
}
export interface CheckBoxInfo { export interface CheckBoxInfo {
row: number; row: number;
columnName: string; columnName: string;

View File

@@ -44,7 +44,8 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
private _filter: ProfilerFilter = { clauses: [] }; private _filter: ProfilerFilter = { clauses: [] };
constructor( constructor(
public connection: IConnectionProfile, public connection: IConnectionProfile | undefined,
public fileURI: URI | undefined,
@IProfilerService private _profilerService: IProfilerService, @IProfilerService private _profilerService: IProfilerService,
@INotificationService private _notificationService: INotificationService @INotificationService private _notificationService: INotificationService
) { ) {
@@ -63,6 +64,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
this._id = id; this._id = id;
this.state.change({ isConnected: true }); this.state.change({ isConnected: true });
}); });
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => { let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
let ret = new Array<number>(); let ret = new Array<number>();
for (let i = 0; i < this._columns.length; i++) { for (let i = 0; i < this._columns.length; i++) {
@@ -156,6 +158,16 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
} }
} }
public get isFileSession(): boolean {
return !!this.fileURI;
}
public setConnectionState(isConnected: boolean): void {
this.state.change({
isConnected: isConnected
});
}
public setColumns(columns: Array<string>) { public setColumns(columns: Array<string>) {
this._columns = columns; this._columns = columns;
this._onColumnsChanged.fire(this.columns); this._onColumnsChanged.fire(this.columns);
@@ -193,7 +205,9 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
} }
public onSessionStopped(notification: azdata.ProfilerSessionStoppedParams) { public onSessionStopped(notification: azdata.ProfilerSessionStoppedParams) {
if (!this.isFileSession) { // File session do not have serverName, so ignore notification error based off of server
this._notificationService.error(nls.localize("profiler.sessionStopped", "XEvent Profiler Session stopped unexpectedly on the server {0}.", this.connection.serverName)); this._notificationService.error(nls.localize("profiler.sessionStopped", "XEvent Profiler Session stopped unexpectedly on the server {0}.", this.connection.serverName));
}
this.state.change({ this.state.change({
isStopped: true, isStopped: true,

View File

@@ -9,7 +9,7 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput'; import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput';
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities'; import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
import { IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces'; import { IProfilerService, ProfilingSessionType } from 'sql/workbench/services/profiler/browser/interfaces';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor'; import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
@@ -18,6 +18,7 @@ import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({
id: 'profiler.newProfiler', id: 'profiler.newProfiler',
@@ -59,7 +60,7 @@ CommandsRegistry.registerCommand({
} }
if (connectionProfile && connectionProfile.providerName === mssqlProviderName) { if (connectionProfile && connectionProfile.providerName === mssqlProviderName) {
let profilerInput = instantiationService.createInstance(ProfilerInput, connectionProfile); let profilerInput = instantiationService.createInstance(ProfilerInput, connectionProfile, undefined);
editorService.openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP).then(() => Promise.resolve(true)); editorService.openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP).then(() => Promise.resolve(true));
} }
}); });
@@ -93,9 +94,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
} else { } else {
// clear data when profiler is started // clear data when profiler is started
profilerInput.data.clear(); profilerInput.data.clear();
return profilerService.startSession(profilerInput.id, profilerInput.sessionName); return profilerService.startSession(profilerInput.id, profilerInput.sessionName, ProfilingSessionType.RemoteSession);
} }
} }
return Promise.resolve(false); return Promise.resolve(false);
} }
}); });
CommandsRegistry.registerCommand({
id: 'profiler.openFile',
handler: async (accessor: ServicesAccessor, ...args: any[]) => {
const editorService: IEditorService = accessor.get(IEditorService);
const fileDialogService: IFileDialogService = accessor.get(IFileDialogService);
const profilerService: IProfilerService = accessor.get(IProfilerService);
const instantiationService: IInstantiationService = accessor.get(IInstantiationService)
const result = await profilerService.openFile(fileDialogService, editorService, instantiationService);
return result;
}
});

View File

@@ -275,7 +275,7 @@ export class NewProfilerAction extends Task {
} }
public async runTask(accessor: ServicesAccessor, profile: IConnectionProfile): Promise<void> { public async runTask(accessor: ServicesAccessor, profile: IConnectionProfile): Promise<void> {
let profilerInput = accessor.get<IInstantiationService>(IInstantiationService).createInstance(ProfilerInput, profile); let profilerInput = accessor.get<IInstantiationService>(IInstantiationService).createInstance(ProfilerInput, profile, undefined);
await accessor.get<IEditorService>(IEditorService).openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP); await accessor.get<IEditorService>(IEditorService).openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP);
let options: IConnectionCompletionOptions = { let options: IConnectionCompletionOptions = {
saveTheConnection: false, saveTheConnection: false,

View File

@@ -549,11 +549,13 @@ export class ProfilerEditor extends EditorPane {
// Launch the create session dialog if openning a new window. // Launch the create session dialog if openning a new window.
let uiState = this._profilerService.getSessionViewState(this.input.id); let uiState = this._profilerService.getSessionViewState(this.input.id);
let previousSessionName = uiState && uiState.previousSessionName; let previousSessionName = uiState && uiState.previousSessionName;
if (!this.input.sessionName && !previousSessionName) { if (!this.input.sessionName && !previousSessionName && !this.input.isFileSession) {
this._profilerService.launchCreateSessionDialog(this.input); this._profilerService.launchCreateSessionDialog(this.input);
} }
if (previousSessionName) { // skip updating session selector if there is no previous session name
this._updateSessionSelector(previousSessionName); this._updateSessionSelector(previousSessionName);
}
} else { } else {
this._startAction.enabled = false; this._startAction.enabled = false;
this._stopAction.enabled = false; this._stopAction.enabled = false;
@@ -579,10 +581,12 @@ export class ProfilerEditor extends EditorPane {
} }
if (this.input.state.isStopped) { if (this.input.state.isStopped) {
this._updateToolbar(); this._updateToolbar();
if (!this.input.isFileSession) { // skip updating session selector for File sessions
this._updateSessionSelector(); this._updateSessionSelector();
} }
} }
} }
}
private _updateSessionSelector(previousSessionName: string = undefined) { private _updateSessionSelector(previousSessionName: string = undefined) {
this._sessionSelector.enable(); this._sessionSelector.enable();

View File

@@ -6,9 +6,11 @@
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput'; import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { INewProfilerState } from 'sql/workbench/common/editor/profiler/profilerState'; import { INewProfilerState } from 'sql/workbench/common/editor/profiler/profilerState';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
const PROFILER_SERVICE_ID = 'profilerService'; const PROFILER_SERVICE_ID = 'profilerService';
export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID); export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID);
@@ -69,9 +71,9 @@ export interface IProfilerService {
*/ */
createSession(id: string, createStatement: string, template: azdata.ProfilerSessionTemplate): Thenable<boolean>; createSession(id: string, createStatement: string, template: azdata.ProfilerSessionTemplate): Thenable<boolean>;
/** /**
* Starts the session specified by the id * Starts the session specified by the id or a session for opening file
*/ */
startSession(sessionId: ProfilerSessionID, sessionName: string): Thenable<boolean>; startSession(sessionId: ProfilerSessionID, sessionName: string, sessionType?: ProfilingSessionType): Thenable<boolean>;
/** /**
* Pauses the session specified by the id * Pauses the session specified by the id
*/ */
@@ -140,6 +142,18 @@ export interface IProfilerService {
* @param filter filter object * @param filter filter object
*/ */
saveFilter(filter: ProfilerFilter): Promise<void>; saveFilter(filter: ProfilerFilter): Promise<void>;
/**
* Launches the dialog for picking a file to open in Profiler extension
* @param fileDialogService service to open file dialog
* @param editorService service to open profiler editor
* @param instantiationService service to create profiler instance
*/
openFile(fileDialogService: IFileDialogService, editorService: IEditorService, instantiationService: IInstantiationService): Promise<boolean>;
}
export enum ProfilingSessionType {
RemoteSession = 0,
LocalFile = 1
} }
export interface IProfilerSettings { export interface IProfilerSettings {

View File

@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, IProfilerSessionTemplate, PROFILER_SETTINGS, IProfilerSettings, EngineType, ProfilerFilter, PROFILER_FILTER_SETTINGS } from './interfaces'; import { ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, IProfilerSessionTemplate, PROFILER_SETTINGS, IProfilerSettings, EngineType, ProfilerFilter, PROFILER_FILTER_SETTINGS, ProfilingSessionType } from './interfaces';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput'; import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput';
import { ProfilerColumnEditorDialog } from 'sql/workbench/services/profiler/browser/profilerColumnEditorDialog'; import { ProfilerColumnEditorDialog } from 'sql/workbench/services/profiler/browser/profilerColumnEditorDialog';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as nls from 'vs/nls';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -19,6 +20,8 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { Memento } from 'vs/workbench/common/memento'; import { Memento } from 'vs/workbench/common/memento';
import { ProfilerFilterDialog } from 'sql/workbench/services/profiler/browser/profilerFilterDialog'; import { ProfilerFilterDialog } from 'sql/workbench/services/profiler/browser/profilerFilterDialog';
import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
class TwoWayMap<T, K> { class TwoWayMap<T, K> {
private forwardMap: Map<T, K>; private forwardMap: Map<T, K>;
@@ -145,12 +148,21 @@ export class ProfilerService implements IProfilerService {
return false; return false;
} }
public async startSession(id: ProfilerSessionID, sessionName: string): Promise<boolean> { /**
* Starts the session specified by the id or a session for opening file
* @param id session ID
* @param sessionName session name or file path to start session with
* @param sessionType distinguisher between remote session and local file
* @returns state of the run as success or failure
*/
public async startSession(id: ProfilerSessionID, sessionName: string, sessionType: ProfilingSessionType): Promise<boolean> {
if (this._idMap.has(id)) { if (this._idMap.has(id)) {
this.updateMemento(id, { previousSessionName: sessionName }); this.updateMemento(id, { previousSessionName: sessionName });
try { try {
await this._runAction(id, provider => provider.startSession(this._idMap.get(id)!, sessionName)); await this._runAction(id, provider => provider.startSession(this._idMap.get(id)!, sessionName, sessionType));
this._sessionMap.get(this._idMap.reverseGet(id)!)!.onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false }); let isRunning = sessionType === ProfilingSessionType.RemoteSession ? true : false; // Reading session stops when the file reading completes
this._sessionMap.get(this._idMap.reverseGet(id)!)!.onSessionStateChanged({ isRunning: isRunning, isStopped: false, isPaused: false });
return true; return true;
} catch (reason) { } catch (reason) {
this._notificationService.error(reason.message); this._notificationService.error(reason.message);
@@ -290,4 +302,28 @@ export class ProfilerService implements IProfilerService {
const config = [filter]; const config = [filter];
await this._configurationService.updateValue(PROFILER_FILTER_SETTINGS, config, ConfigurationTarget.USER); await this._configurationService.updateValue(PROFILER_FILTER_SETTINGS, config, ConfigurationTarget.USER);
} }
public async openFile(fileDialogService: IFileDialogService, editorService: IEditorService, instantiationService: IInstantiationService): Promise<boolean> {
const fileURIs = await fileDialogService.showOpenDialog({
filters: [
{
extensions: ['xel'],
name: nls.localize('FileFilterDescription', "XEL Files")
}
],
canSelectMany: false
});
if (fileURIs?.length === 1) {
const fileURI = fileURIs[0];
let profilerInput: ProfilerInput = instantiationService.createInstance(ProfilerInput, undefined, fileURI);
await editorService.openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP);
profilerInput.setConnectionState(false); // Reset connection to be not connected for File session, so that "Start" is not enabled.
const result = await this.startSession(profilerInput.id, profilerInput.fileURI.fsPath, ProfilingSessionType.LocalFile);
return result;
}
return true;
}
} }