Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Action } from '../../../../base/common/actions.js';6import { VSBuffer } from '../../../../base/common/buffer.js';7import { Codicon } from '../../../../base/common/codicons.js';8import { Event } from '../../../../base/common/event.js';9import { Schemas } from '../../../../base/common/network.js';10import { joinPath } from '../../../../base/common/resources.js';11import { URI } from '../../../../base/common/uri.js';12import * as nls from '../../../../nls.js';13import { Action2, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';14import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';15import { ICommandService } from '../../../../platform/commands/common/commands.js';16import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';17import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';18import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';19import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';20import { IFileService } from '../../../../platform/files/common/files.js';21import { IHoverService } from '../../../../platform/hover/browser/hover.js';22import { IInstantiationService, ServicesAccessor, createDecorator } from '../../../../platform/instantiation/common/instantiation.js';23import { ILabelService } from '../../../../platform/label/common/label.js';24import { INotificationService } from '../../../../platform/notification/common/notification.js';25import { IV8Profile, Utils } from '../../../../platform/profiling/common/profiling.js';26import { IStorageService } from '../../../../platform/storage/common/storage.js';27import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';28import { IThemeService } from '../../../../platform/theme/common/themeService.js';29import { ActiveEditorContext } from '../../../common/contextkeys.js';30import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';31import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';32import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';33import { IExtensionFeaturesManagementService } from '../../../services/extensionManagement/common/extensionFeatures.js';34import { IExtensionHostProfile, IExtensionService } from '../../../services/extensions/common/extensions.js';35import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from '../browser/abstractRuntimeExtensionsEditor.js';36import { IExtensionsWorkbenchService } from '../common/extensions.js';37import { ReportExtensionIssueAction } from '../common/reportExtensionIssueAction.js';38import { SlowExtensionAction } from './extensionsSlowActions.js';3940export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');41export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');42export const CONTEXT_EXTENSION_HOST_PROFILE_RECORDED = new RawContextKey<boolean>('extensionHostProfileRecorded', false);4344export enum ProfileSessionState {45None = 0,46Starting = 1,47Running = 2,48Stopping = 349}5051export interface IExtensionHostProfileService {52readonly _serviceBrand: undefined;5354readonly onDidChangeState: Event<void>;55readonly onDidChangeLastProfile: Event<void>;5657readonly state: ProfileSessionState;58readonly lastProfile: IExtensionHostProfile | null;59lastProfileSavedTo: URI | undefined;6061startProfiling(): void;62stopProfiling(): void;6364getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined;65setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void;66}6768export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor {6970private _profileInfo: IExtensionHostProfile | null;71private _extensionsHostRecorded: IContextKey<boolean>;72private _profileSessionState: IContextKey<string>;7374constructor(75group: IEditorGroup,76@ITelemetryService telemetryService: ITelemetryService,77@IThemeService themeService: IThemeService,78@IContextKeyService contextKeyService: IContextKeyService,79@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,80@IExtensionService extensionService: IExtensionService,81@INotificationService notificationService: INotificationService,82@IContextMenuService contextMenuService: IContextMenuService,83@IInstantiationService instantiationService: IInstantiationService,84@IStorageService storageService: IStorageService,85@ILabelService labelService: ILabelService,86@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,87@IClipboardService clipboardService: IClipboardService,88@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,89@IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService,90@IHoverService hoverService: IHoverService,91@IMenuService menuService: IMenuService,92) {93super(group, telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService, extensionFeaturesManagementService, hoverService, menuService);94this._profileInfo = this._extensionHostProfileService.lastProfile;95this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService);96this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService);9798this._register(this._extensionHostProfileService.onDidChangeLastProfile(() => {99this._profileInfo = this._extensionHostProfileService.lastProfile;100this._extensionsHostRecorded.set(!!this._profileInfo);101this._updateExtensions();102}));103this._register(this._extensionHostProfileService.onDidChangeState(() => {104const state = this._extensionHostProfileService.state;105this._profileSessionState.set(ProfileSessionState[state].toLowerCase());106}));107}108109protected _getProfileInfo(): IExtensionHostProfile | null {110return this._profileInfo;111}112113protected _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {114return this._extensionHostProfileService.getUnresponsiveProfile(extensionId);115}116117protected _createSlowExtensionAction(element: IRuntimeExtension): Action | null {118if (element.unresponsiveProfile) {119return this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile);120}121return null;122}123124protected _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null {125if (element.marketplaceInfo) {126return this._instantiationService.createInstance(ReportExtensionIssueAction, element.description);127}128return null;129}130}131132export class StartExtensionHostProfileAction extends Action2 {133static readonly ID = 'workbench.extensions.action.extensionHostProfile';134static readonly LABEL = nls.localize('extensionHostProfileStart', "Start Extension Host Profile");135136constructor() {137super({138id: StartExtensionHostProfileAction.ID,139title: { value: StartExtensionHostProfileAction.LABEL, original: 'Start Extension Host Profile' },140precondition: CONTEXT_PROFILE_SESSION_STATE.isEqualTo('none'),141icon: Codicon.circleFilled,142menu: [{143id: MenuId.EditorTitle,144when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running')),145group: 'navigation',146}, {147id: MenuId.ExtensionEditorContextMenu,148when: CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running'),149group: 'profiling',150}]151});152}153154run(accessor: ServicesAccessor): Promise<any> {155const extensionHostProfileService = accessor.get(IExtensionHostProfileService);156extensionHostProfileService.startProfiling();157return Promise.resolve();158}159}160161export class StopExtensionHostProfileAction extends Action2 {162static readonly ID = 'workbench.extensions.action.stopExtensionHostProfile';163static readonly LABEL = nls.localize('stopExtensionHostProfileStart', "Stop Extension Host Profile");164165constructor() {166super({167id: StopExtensionHostProfileAction.ID,168title: { value: StopExtensionHostProfileAction.LABEL, original: 'Stop Extension Host Profile' },169icon: Codicon.debugStop,170menu: [{171id: MenuId.EditorTitle,172when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running')),173group: 'navigation',174}, {175id: MenuId.ExtensionEditorContextMenu,176when: CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running'),177group: 'profiling',178}]179});180}181182run(accessor: ServicesAccessor): Promise<any> {183const extensionHostProfileService = accessor.get(IExtensionHostProfileService);184extensionHostProfileService.stopProfiling();185return Promise.resolve();186}187}188189export class OpenExtensionHostProfileACtion extends Action2 {190static readonly LABEL = nls.localize('openExtensionHostProfile', "Open Extension Host Profile");191static readonly ID = 'workbench.extensions.action.openExtensionHostProfile';192193constructor() {194super({195id: OpenExtensionHostProfileACtion.ID,196title: { value: OpenExtensionHostProfileACtion.LABEL, original: 'Open Extension Host Profile' },197precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,198icon: Codicon.graph,199menu: [{200id: MenuId.EditorTitle,201when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)),202group: 'navigation',203}, {204id: MenuId.ExtensionEditorContextMenu,205when: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,206group: 'profiling',207}]208});209}210211async run(accessor: ServicesAccessor): Promise<void> {212const extensionHostProfileService = accessor.get(IExtensionHostProfileService);213const commandService = accessor.get(ICommandService);214const editorService = accessor.get(IEditorService);215if (!extensionHostProfileService.lastProfileSavedTo) {216await commandService.executeCommand(SaveExtensionHostProfileAction.ID);217}218if (!extensionHostProfileService.lastProfileSavedTo) {219return;220}221222await editorService.openEditor({223resource: extensionHostProfileService.lastProfileSavedTo,224options: {225revealIfOpened: true,226override: 'jsProfileVisualizer.cpuprofile.table',227},228}, SIDE_GROUP);229}230231}232233export class SaveExtensionHostProfileAction extends Action2 {234235static readonly LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile");236static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile';237238constructor() {239super({240id: SaveExtensionHostProfileAction.ID,241title: { value: SaveExtensionHostProfileAction.LABEL, original: 'Save Extension Host Profile' },242precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,243icon: Codicon.saveAll,244menu: [{245id: MenuId.EditorTitle,246when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)),247group: 'navigation',248}, {249id: MenuId.ExtensionEditorContextMenu,250when: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,251group: 'profiling',252}]253});254}255256run(accessor: ServicesAccessor): Promise<any> {257const environmentService = accessor.get(IWorkbenchEnvironmentService);258const extensionHostProfileService = accessor.get(IExtensionHostProfileService);259const fileService = accessor.get(IFileService);260const fileDialogService = accessor.get(IFileDialogService);261return this._asyncRun(environmentService, extensionHostProfileService, fileService, fileDialogService);262}263264private async _asyncRun(265environmentService: IWorkbenchEnvironmentService,266extensionHostProfileService: IExtensionHostProfileService,267fileService: IFileService,268fileDialogService: IFileDialogService269): Promise<any> {270const picked = await fileDialogService.showSaveDialog({271title: nls.localize('saveprofile.dialogTitle', "Save Extension Host Profile"),272availableFileSystems: [Schemas.file],273defaultUri: joinPath(await fileDialogService.defaultFilePath(), `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`),274filters: [{275name: 'CPU Profiles',276extensions: ['cpuprofile', 'txt']277}]278});279280if (!picked) {281return;282}283284const profileInfo = extensionHostProfileService.lastProfile;285let dataToWrite: object = profileInfo ? profileInfo.data : {};286287let savePath = picked.fsPath;288289if (environmentService.isBuilt) {290// when running from a not-development-build we remove291// absolute filenames because we don't want to reveal anything292// about users. We also append the `.txt` suffix to make it293// easier to attach these files to GH issues294dataToWrite = Utils.rewriteAbsolutePaths(dataToWrite as IV8Profile, 'piiRemoved');295296savePath = savePath + '.txt';297}298299const saveURI = URI.file(savePath);300extensionHostProfileService.lastProfileSavedTo = saveURI;301return fileService.writeFile(saveURI, VSBuffer.fromString(JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')));302}303}304305306307