Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.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 { disposableWindowInterval } from '../../../../base/browser/dom.js';6import { mainWindow } from '../../../../base/browser/window.js';7import { onUnexpectedError } from '../../../../base/common/errors.js';8import { Emitter, Event } from '../../../../base/common/event.js';9import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';10import { randomPort } from '../../../../base/common/ports.js';11import * as nls from '../../../../nls.js';12import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';13import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';14import { ExtensionIdentifier, ExtensionIdentifierMap } from '../../../../platform/extensions/common/extensions.js';15import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';16import { INativeHostService } from '../../../../platform/native/common/native.js';17import { IProductService } from '../../../../platform/product/common/productService.js';18import { RuntimeExtensionsInput } from '../common/runtimeExtensionsInput.js';19import { IExtensionHostProfileService, ProfileSessionState } from './runtimeExtensionsEditor.js';20import { IEditorService } from '../../../services/editor/common/editorService.js';21import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js';22import { IExtensionHostProfile, IExtensionService, ProfileSession } from '../../../services/extensions/common/extensions.js';23import { ExtensionHostProfiler } from '../../../services/extensions/electron-browser/extensionHostProfiler.js';24import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from '../../../services/statusbar/browser/statusbar.js';25import { URI } from '../../../../base/common/uri.js';2627export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService {2829declare readonly _serviceBrand: undefined;3031private readonly _onDidChangeState: Emitter<void> = this._register(new Emitter<void>());32public readonly onDidChangeState: Event<void> = this._onDidChangeState.event;3334private readonly _onDidChangeLastProfile: Emitter<void> = this._register(new Emitter<void>());35public readonly onDidChangeLastProfile: Event<void> = this._onDidChangeLastProfile.event;3637private readonly _unresponsiveProfiles = new ExtensionIdentifierMap<IExtensionHostProfile>();38private _profile: IExtensionHostProfile | null;39private _profileSession: ProfileSession | null;40private _state: ProfileSessionState = ProfileSessionState.None;4142private profilingStatusBarIndicator: IStatusbarEntryAccessor | undefined;43private readonly profilingStatusBarIndicatorLabelUpdater = this._register(new MutableDisposable());4445public lastProfileSavedTo: URI | undefined;46public get state() { return this._state; }47public get lastProfile() { return this._profile; }4849constructor(50@IExtensionService private readonly _extensionService: IExtensionService,51@IEditorService private readonly _editorService: IEditorService,52@IInstantiationService private readonly _instantiationService: IInstantiationService,53@INativeHostService private readonly _nativeHostService: INativeHostService,54@IDialogService private readonly _dialogService: IDialogService,55@IStatusbarService private readonly _statusbarService: IStatusbarService,56@IProductService private readonly _productService: IProductService57) {58super();59this._profile = null;60this._profileSession = null;61this._setState(ProfileSessionState.None);6263CommandsRegistry.registerCommand('workbench.action.extensionHostProfiler.stop', () => {64this.stopProfiling();65this._editorService.openEditor(RuntimeExtensionsInput.instance, { pinned: true });66});67}6869private _setState(state: ProfileSessionState): void {70if (this._state === state) {71return;72}73this._state = state;7475if (this._state === ProfileSessionState.Running) {76this.updateProfilingStatusBarIndicator(true);77} else if (this._state === ProfileSessionState.Stopping) {78this.updateProfilingStatusBarIndicator(false);79}8081this._onDidChangeState.fire(undefined);82}8384private updateProfilingStatusBarIndicator(visible: boolean): void {85this.profilingStatusBarIndicatorLabelUpdater.clear();8687if (visible) {88const indicator: IStatusbarEntry = {89name: nls.localize('status.profiler', "Extension Profiler"),90text: nls.localize('profilingExtensionHost', "Profiling Extension Host"),91showProgress: true,92ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"),93tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),94command: 'workbench.action.extensionHostProfiler.stop'95};9697const timeStarted = Date.now();98const handle = disposableWindowInterval(mainWindow, () => {99this.profilingStatusBarIndicator?.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });100}, 1000);101this.profilingStatusBarIndicatorLabelUpdater.value = handle;102103if (!this.profilingStatusBarIndicator) {104this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', StatusbarAlignment.RIGHT);105} else {106this.profilingStatusBarIndicator.update(indicator);107}108} else {109if (this.profilingStatusBarIndicator) {110this.profilingStatusBarIndicator.dispose();111this.profilingStatusBarIndicator = undefined;112}113}114}115116public async startProfiling(): Promise<any> {117if (this._state !== ProfileSessionState.None) {118return null;119}120121const inspectPorts = await this._extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, true);122123if (inspectPorts.length === 0) {124return this._dialogService.confirm({125type: 'info',126message: nls.localize('restart1', "Profile Extensions"),127detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this._productService.nameLong),128primaryButton: nls.localize({ key: 'restart3', comment: ['&& denotes a mnemonic'] }, "&&Restart")129}).then(res => {130if (res.confirmed) {131this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });132}133});134}135136if (inspectPorts.length > 1) {137// TODO138console.warn(`There are multiple extension hosts available for profiling. Picking the first one...`);139}140141this._setState(ProfileSessionState.Starting);142143return this._instantiationService.createInstance(ExtensionHostProfiler, inspectPorts[0].host, inspectPorts[0].port).start().then((value) => {144this._profileSession = value;145this._setState(ProfileSessionState.Running);146}, (err) => {147onUnexpectedError(err);148this._setState(ProfileSessionState.None);149});150}151152public stopProfiling(): void {153if (this._state !== ProfileSessionState.Running || !this._profileSession) {154return;155}156157this._setState(ProfileSessionState.Stopping);158this._profileSession.stop().then((result) => {159this._setLastProfile(result);160this._setState(ProfileSessionState.None);161}, (err) => {162onUnexpectedError(err);163this._setState(ProfileSessionState.None);164});165this._profileSession = null;166}167168private _setLastProfile(profile: IExtensionHostProfile) {169this._profile = profile;170this.lastProfileSavedTo = undefined;171this._onDidChangeLastProfile.fire(undefined);172}173174getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {175return this._unresponsiveProfiles.get(extensionId);176}177178setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void {179this._unresponsiveProfiles.set(extensionId, profile);180this._setLastProfile(profile);181}182183}184185186