Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Rieken <johannes.rieken@gmail.com>2022-01-03 19:45:26 +0300
committerJohannes Rieken <johannes.rieken@gmail.com>2022-01-03 19:45:40 +0300
commit4636c22e55a7d4ac49740257d72d2de060f5809a (patch)
tree424dcf216489b608606431017a45e6c5d48f83fa /src/vs/workbench/contrib/extensions/electron-sandbox
parentf5d5c6863e72e4b61018eeeff9e525f625f42645 (diff)
move things to electron-sandbox, https://github.com/microsoft/vscode/issues/111211
Diffstat (limited to 'src/vs/workbench/contrib/extensions/electron-sandbox')
-rw-r--r--src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts175
-rw-r--r--src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts10
-rw-r--r--src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts199
3 files changed, 382 insertions, 2 deletions
diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts
new file mode 100644
index 00000000000..c6efc4ff567
--- /dev/null
+++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts
@@ -0,0 +1,175 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as nls from 'vs/nls';
+import { Event, Emitter } from 'vs/base/common/event';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
+import { Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
+import { onUnexpectedError } from 'vs/base/common/errors';
+import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar';
+import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { randomPort } from 'vs/base/common/ports';
+import { IProductService } from 'vs/platform/product/common/productService';
+import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
+import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
+import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler';
+import { CommandsRegistry } from 'vs/platform/commands/common/commands';
+
+export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService {
+
+ declare readonly _serviceBrand: undefined;
+
+ private readonly _onDidChangeState: Emitter<void> = this._register(new Emitter<void>());
+ public readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
+
+ private readonly _onDidChangeLastProfile: Emitter<void> = this._register(new Emitter<void>());
+ public readonly onDidChangeLastProfile: Event<void> = this._onDidChangeLastProfile.event;
+
+ private readonly _unresponsiveProfiles = new Map<string, IExtensionHostProfile>();
+ private _profile: IExtensionHostProfile | null;
+ private _profileSession: ProfileSession | null;
+ private _state: ProfileSessionState = ProfileSessionState.None;
+
+ private profilingStatusBarIndicator: IStatusbarEntryAccessor | undefined;
+ private readonly profilingStatusBarIndicatorLabelUpdater = this._register(new MutableDisposable());
+
+ public get state() { return this._state; }
+ public get lastProfile() { return this._profile; }
+
+ constructor(
+ @IExtensionService private readonly _extensionService: IExtensionService,
+ @IEditorService private readonly _editorService: IEditorService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService,
+ @INativeHostService private readonly _nativeHostService: INativeHostService,
+ @IDialogService private readonly _dialogService: IDialogService,
+ @IStatusbarService private readonly _statusbarService: IStatusbarService,
+ @IProductService private readonly _productService: IProductService
+ ) {
+ super();
+ this._profile = null;
+ this._profileSession = null;
+ this._setState(ProfileSessionState.None);
+
+ CommandsRegistry.registerCommand('workbench.action.extensionHostProfiler.stop', () => {
+ this.stopProfiling();
+ this._editorService.openEditor(RuntimeExtensionsInput.instance, { pinned: true });
+ });
+ }
+
+ private _setState(state: ProfileSessionState): void {
+ if (this._state === state) {
+ return;
+ }
+ this._state = state;
+
+ if (this._state === ProfileSessionState.Running) {
+ this.updateProfilingStatusBarIndicator(true);
+ } else if (this._state === ProfileSessionState.Stopping) {
+ this.updateProfilingStatusBarIndicator(false);
+ }
+
+ this._onDidChangeState.fire(undefined);
+ }
+
+ private updateProfilingStatusBarIndicator(visible: boolean): void {
+ this.profilingStatusBarIndicatorLabelUpdater.clear();
+
+ if (visible) {
+ const indicator: IStatusbarEntry = {
+ name: nls.localize('status.profiler', "Extension Profiler"),
+ text: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
+ showProgress: true,
+ ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
+ tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),
+ command: 'workbench.action.extensionHostProfiler.stop'
+ };
+
+ const timeStarted = Date.now();
+ const handle = setInterval(() => {
+ if (this.profilingStatusBarIndicator) {
+ this.profilingStatusBarIndicator.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });
+ }
+ }, 1000);
+ this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle));
+
+ if (!this.profilingStatusBarIndicator) {
+ this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', StatusbarAlignment.RIGHT);
+ } else {
+ this.profilingStatusBarIndicator.update(indicator);
+ }
+ } else {
+ if (this.profilingStatusBarIndicator) {
+ this.profilingStatusBarIndicator.dispose();
+ this.profilingStatusBarIndicator = undefined;
+ }
+ }
+ }
+
+ public async startProfiling(): Promise<any> {
+ if (this._state !== ProfileSessionState.None) {
+ return null;
+ }
+
+ const inspectPort = await this._extensionService.getInspectPort(true);
+ if (!inspectPort) {
+ return this._dialogService.confirm({
+ type: 'info',
+ message: nls.localize('restart1', "Profile Extensions"),
+ detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this._productService.nameLong),
+ primaryButton: nls.localize('restart3', "&&Restart"),
+ secondaryButton: nls.localize('cancel', "&&Cancel")
+ }).then(res => {
+ if (res.confirmed) {
+ this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
+ }
+ });
+ }
+
+ this._setState(ProfileSessionState.Starting);
+
+ return this._instantiationService.createInstance(ExtensionHostProfiler, inspectPort).start().then((value) => {
+ this._profileSession = value;
+ this._setState(ProfileSessionState.Running);
+ }, (err) => {
+ onUnexpectedError(err);
+ this._setState(ProfileSessionState.None);
+ });
+ }
+
+ public stopProfiling(): void {
+ if (this._state !== ProfileSessionState.Running || !this._profileSession) {
+ return;
+ }
+
+ this._setState(ProfileSessionState.Stopping);
+ this._profileSession.stop().then((result) => {
+ this._setLastProfile(result);
+ this._setState(ProfileSessionState.None);
+ }, (err) => {
+ onUnexpectedError(err);
+ this._setState(ProfileSessionState.None);
+ });
+ this._profileSession = null;
+ }
+
+ private _setLastProfile(profile: IExtensionHostProfile) {
+ this._profile = profile;
+ this._onDidChangeLastProfile.fire(undefined);
+ }
+
+ getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {
+ return this._unresponsiveProfiles.get(ExtensionIdentifier.toKey(extensionId));
+ }
+
+ setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void {
+ this._unresponsiveProfiles.set(ExtensionIdentifier.toKey(extensionId), profile);
+ this._setLastProfile(profile);
+ }
+
+}
diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts
index 55cb5746c3c..19a6f4de8e8 100644
--- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts
@@ -12,7 +12,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
-import { RuntimeExtensionsEditor, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor';
+import { RuntimeExtensionsEditor, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction, IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor';
import { DebugExtensionHostAction } from 'vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction';
import { IEditorSerializer, IEditorFactoryRegistry, ActiveEditorContext, EditorExtensions } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -24,6 +24,12 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services
import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc';
import { Codicon } from 'vs/base/common/codicons';
import { RemoteExtensionsInitializerContribution } from 'vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit';
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService';
+import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler';
+
+// Singletons
+registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true);
// Running Extensions Editor
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
@@ -61,8 +67,8 @@ class ExtensionsContributions implements IWorkbenchContribution {
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting);
+workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInitializerContribution, LifecyclePhase.Restored);
-
// Register Commands
CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => {
diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts
new file mode 100644
index 00000000000..ec49247f46d
--- /dev/null
+++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts
@@ -0,0 +1,199 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
+import { IExtensionService, IResponsiveStateChangeEvent, IExtensionHostProfile, ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { ILogService } from 'vs/platform/log/common/log';
+import { CancellationTokenSource } from 'vs/base/common/cancellation';
+import { onUnexpectedError } from 'vs/base/common/errors';
+import { joinPath } from 'vs/base/common/resources';
+import { IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor';
+import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
+import { localize } from 'vs/nls';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
+import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { createSlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions';
+import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler';
+import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
+import { IFileService } from 'vs/platform/files/common/files';
+import { VSBuffer } from 'vs/base/common/buffer';
+
+export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchContribution {
+
+ private readonly _blame = new Set<string>();
+ private _session: CancellationTokenSource | undefined;
+
+ constructor(
+ @IExtensionService private readonly _extensionService: IExtensionService,
+ @IExtensionHostProfileService private readonly _extensionProfileService: IExtensionHostProfileService,
+ @ITelemetryService private readonly _telemetryService: ITelemetryService,
+ @ILogService private readonly _logService: ILogService,
+ @INotificationService private readonly _notificationService: INotificationService,
+ @IEditorService private readonly _editorService: IEditorService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService,
+ @INativeWorkbenchEnvironmentService private readonly _environmentServie: INativeWorkbenchEnvironmentService,
+ @IFileService private readonly _fileService: IFileService
+ ) {
+ super();
+ this._register(_extensionService.onDidChangeResponsiveChange(this._onDidChangeResponsiveChange, this));
+ }
+
+ private async _onDidChangeResponsiveChange(event: IResponsiveStateChangeEvent): Promise<void> {
+
+ const port = await this._extensionService.getInspectPort(true);
+
+ if (!port) {
+ return;
+ }
+
+ if (event.isResponsive && this._session) {
+ // stop profiling when responsive again
+ this._session.cancel();
+
+ } else if (!event.isResponsive && !this._session) {
+ // start profiling if not yet profiling
+ const cts = new CancellationTokenSource();
+ this._session = cts;
+
+
+ let session: ProfileSession;
+ try {
+ session = await this._instantiationService.createInstance(ExtensionHostProfiler, port).start();
+
+ } catch (err) {
+ this._session = undefined;
+ // fail silent as this is often
+ // caused by another party being
+ // connected already
+ return;
+ }
+
+ // wait 5 seconds or until responsive again
+ await new Promise(resolve => {
+ cts.token.onCancellationRequested(resolve);
+ setTimeout(resolve, 5e3);
+ });
+
+ try {
+ // stop profiling and analyse results
+ this._processCpuProfile(await session.stop());
+ } catch (err) {
+ onUnexpectedError(err);
+ } finally {
+ this._session = undefined;
+ }
+ }
+ }
+
+ private async _processCpuProfile(profile: IExtensionHostProfile) {
+
+ interface NamedSlice {
+ id: string;
+ total: number;
+ percentage: number;
+ }
+
+ let data: NamedSlice[] = [];
+ for (let i = 0; i < profile.ids.length; i++) {
+ let id = profile.ids[i];
+ let total = profile.deltas[i];
+ data.push({ id, total, percentage: 0 });
+ }
+
+ // merge data by identifier
+ let anchor = 0;
+ data.sort((a, b) => a.id.localeCompare(b.id));
+ for (let i = 1; i < data.length; i++) {
+ if (data[anchor].id === data[i].id) {
+ data[anchor].total += data[i].total;
+ } else {
+ anchor += 1;
+ data[anchor] = data[i];
+ }
+ }
+ data = data.slice(0, anchor + 1);
+
+ const duration = profile.endTime - profile.startTime;
+ const percentage = duration / 100;
+ let top: NamedSlice | undefined;
+ for (const slice of data) {
+ slice.percentage = Math.round(slice.total / percentage);
+ if (!top || top.percentage < slice.percentage) {
+ top = slice;
+ }
+ }
+
+ if (!top) {
+ return;
+ }
+
+ const extension = await this._extensionService.getExtension(top.id);
+ if (!extension) {
+ // not an extension => idle, gc, self?
+ return;
+ }
+
+
+ // print message to log
+ const path = joinPath(this._environmentServie.tmpDir, `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`);
+ await this._fileService.writeFile(path, VSBuffer.fromString(JSON.stringify(profile.data)));
+ this._logService.warn(`UNRESPONSIVE extension host, '${top.id}' took ${top.percentage}% of ${duration / 1e3}ms, saved PROFILE here: '${path}'`, data);
+
+
+ /* __GDPR__
+ "exthostunresponsive" : {
+ "id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
+ "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "data": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
+ }
+ */
+ this._telemetryService.publicLog('exthostunresponsive', {
+ duration,
+ data,
+ });
+
+ // add to running extensions view
+ this._extensionProfileService.setUnresponsiveProfile(extension.identifier, profile);
+
+ // prompt: when really slow/greedy
+ if (!(top.percentage >= 99 && top.total >= 5e6)) {
+ return;
+ }
+
+ const action = await this._instantiationService.invokeFunction(createSlowExtensionAction, extension, profile);
+
+ if (!action) {
+ // cannot report issues against this extension...
+ return;
+ }
+
+ // only blame once per extension, don't blame too often
+ if (this._blame.has(ExtensionIdentifier.toKey(extension.identifier)) || this._blame.size >= 3) {
+ return;
+ }
+ this._blame.add(ExtensionIdentifier.toKey(extension.identifier));
+
+ // user-facing message when very bad...
+ this._notificationService.prompt(
+ Severity.Warning,
+ localize(
+ 'unresponsive-exthost',
+ "The extension '{0}' took a very long time to complete its last operation and it has prevented other extensions from running.",
+ extension.displayName || extension.name
+ ),
+ [{
+ label: localize('show', 'Show Extensions'),
+ run: () => this._editorService.openEditor(RuntimeExtensionsInput.instance, { pinned: true })
+ },
+ action
+ ],
+ { silent: true }
+ );
+ }
+}