runtimeExtensionsEditor.ts 26.3 KB
Newer Older
A
Alex Dima 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/runtimeExtensionsEditor';
import * as nls from 'vs/nls';
I
isidor 已提交
8
import * as os from 'os';
9
import product from 'vs/platform/product/common/product';
10
import { Action, IAction } from 'vs/base/common/actions';
A
Alex Dima 已提交
11 12
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
13
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
14
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
A
Alex Dima 已提交
15
import { IThemeService } from 'vs/platform/theme/common/themeService';
16
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
17
import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
J
Joao Moreno 已提交
18
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
19
import { WorkbenchList } from 'vs/platform/list/browser/listService';
20
import { append, $, addClass, toggleClass, Dimension, clearNode } from 'vs/base/browser/dom';
21
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
A
Alex Dima 已提交
22
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
23
import { RunOnceScheduler } from 'vs/base/common/async';
I
isidor 已提交
24
import { clipboard } from 'electron';
25
import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
A
Alex Dima 已提交
26
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
27
import { IElectronService } from 'vs/platform/electron/node/electron';
A
Alex Dima 已提交
28
import { writeFile } from 'vs/base/node/pfs';
29
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
I
isidor 已提交
30
import { memoize } from 'vs/base/common/decorators';
31
import { isNonEmptyArray } from 'vs/base/common/arrays';
M
Matt Bierner 已提交
32
import { Event } from 'vs/base/common/event';
33
import { INotificationService } from 'vs/platform/notification/common/notification';
34
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
35
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
36 37
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { randomPort } from 'vs/base/node/ports';
38
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
B
Benjamin Pasero 已提交
39
import { IStorageService } from 'vs/platform/storage/common/storage';
A
Alex Dima 已提交
40
import { ILabelService } from 'vs/platform/label/common/label';
M
Miguel Solorio 已提交
41
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
42
import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
43
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
44
import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions';
45
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
46 47
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
48

49
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
50 51
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
export const CONTEXT_EXTENSION_HOST_PROFILE_RECORDED = new RawContextKey<boolean>('extensionHostProfileRecorded', false);
52

53
export enum ProfileSessionState {
54 55 56 57 58 59 60
	None = 0,
	Starting = 1,
	Running = 2,
	Stopping = 3
}

export interface IExtensionHostProfileService {
61
	_serviceBrand: undefined;
62 63 64 65 66

	readonly onDidChangeState: Event<void>;
	readonly onDidChangeLastProfile: Event<void>;

	readonly state: ProfileSessionState;
67
	readonly lastProfile: IExtensionHostProfile | null;
68 69 70 71

	startProfiling(): void;
	stopProfiling(): void;

72
	getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined;
73
	setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void;
74
}
A
Alex Dima 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88

interface IExtensionProfileInformation {
	/**
	 * segment when the extension was running.
	 * 2*i = segment start time
	 * 2*i+1 = segment end time
	 */
	segments: number[];
	/**
	 * total time when the extension was running.
	 * (sum of all segment lengths).
	 */
	totalTime: number;
}
A
Alex Dima 已提交
89 90

interface IRuntimeExtension {
A
Alex Dima 已提交
91
	originalIndex: number;
A
Alex Dima 已提交
92 93 94
	description: IExtensionDescription;
	marketplaceInfo: IExtension;
	status: IExtensionsStatus;
M
Matt Bierner 已提交
95
	profileInfo?: IExtensionProfileInformation;
96
	unresponsiveProfile?: IExtensionHostProfile;
A
Alex Dima 已提交
97 98 99 100
}

export class RuntimeExtensionsEditor extends BaseEditor {

101
	public static readonly ID: string = 'workbench.editor.runtimeExtensions';
A
Alex Dima 已提交
102

103
	private _list: WorkbenchList<IRuntimeExtension> | null;
104
	private _profileInfo: IExtensionHostProfile | null;
A
Alex Dima 已提交
105

106
	private _elements: IRuntimeExtension[] | null;
A
Alex Dima 已提交
107
	private _extensionsDescriptions: IExtensionDescription[];
A
Alex Dima 已提交
108
	private _updateSoon: RunOnceScheduler;
109
	private _profileSessionState: IContextKey<string>;
110
	private _extensionsHostRecorded: IContextKey<boolean>;
A
Alex Dima 已提交
111 112 113 114

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IThemeService themeService: IThemeService,
115
		@IContextKeyService contextKeyService: IContextKeyService,
A
Alex Dima 已提交
116 117
		@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
		@IExtensionService private readonly _extensionService: IExtensionService,
118
		@INotificationService private readonly _notificationService: INotificationService,
A
Alex Dima 已提交
119
		@IContextMenuService private readonly _contextMenuService: IContextMenuService,
120
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
121
		@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
A
Alex Dima 已提交
122
		@IStorageService storageService: IStorageService,
123
		@ILabelService private readonly _labelService: ILabelService,
124 125
		@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
		@IOpenerService private readonly _openerService: IOpenerService
A
Alex Dima 已提交
126
	) {
127
		super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService);
A
Alex Dima 已提交
128 129

		this._list = null;
130 131 132
		this._profileInfo = this._extensionHostProfileService.lastProfile;
		this._register(this._extensionHostProfileService.onDidChangeLastProfile(() => {
			this._profileInfo = this._extensionHostProfileService.lastProfile;
133
			this._extensionsHostRecorded.set(!!this._profileInfo);
134 135
			this._updateExtensions();
		}));
136
		this._register(this._extensionHostProfileService.onDidChangeState(() => {
137 138
			const state = this._extensionHostProfileService.state;
			this._profileSessionState.set(ProfileSessionState[state].toLowerCase());
139
		}));
140

A
Alex Dima 已提交
141 142 143 144 145
		this._elements = null;

		this._extensionsDescriptions = [];
		this._updateExtensions();

146
		this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService);
147
		this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService);
148

A
Alex Dima 已提交
149 150
		this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200));

A
Alex Dima 已提交
151
		this._extensionService.getExtensions().then((extensions) => {
A
Alex Dima 已提交
152 153 154 155
			// We only deal with extensions with source code!
			this._extensionsDescriptions = extensions.filter((extension) => {
				return !!extension.main;
			});
A
Alex Dima 已提交
156 157
			this._updateExtensions();
		});
A
Alex Dima 已提交
158
		this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule()));
159 160
	}

A
Alex Dima 已提交
161 162 163 164 165 166 167 168 169 170
	private _updateExtensions(): void {
		this._elements = this._resolveExtensions();
		if (this._list) {
			this._list.splice(0, this._list.length, this._elements);
		}
	}

	private _resolveExtensions(): IRuntimeExtension[] {
		let marketplaceMap: { [id: string]: IExtension; } = Object.create(null);
		for (let extension of this._extensionsWorkbenchService.local) {
171
			marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension;
A
Alex Dima 已提交
172 173 174 175
		}

		let statusMap = this._extensionService.getExtensionsStatus();

A
Alex Dima 已提交
176 177 178 179 180 181 182 183 184
		// group profile segments by extension
		let segments: { [id: string]: number[]; } = Object.create(null);

		if (this._profileInfo) {
			let currentStartTime = this._profileInfo.startTime;
			for (let i = 0, len = this._profileInfo.deltas.length; i < len; i++) {
				const id = this._profileInfo.ids[i];
				const delta = this._profileInfo.deltas[i];

185
				let extensionSegments = segments[ExtensionIdentifier.toKey(id)];
A
Alex Dima 已提交
186 187
				if (!extensionSegments) {
					extensionSegments = [];
188
					segments[ExtensionIdentifier.toKey(id)] = extensionSegments;
A
Alex Dima 已提交
189 190 191 192 193 194 195 196
				}

				extensionSegments.push(currentStartTime);
				currentStartTime = currentStartTime + delta;
				extensionSegments.push(currentStartTime);
			}
		}

A
Alex Dima 已提交
197 198 199 200
		let result: IRuntimeExtension[] = [];
		for (let i = 0, len = this._extensionsDescriptions.length; i < len; i++) {
			const extensionDescription = this._extensionsDescriptions[i];

201
			let profileInfo: IExtensionProfileInformation | null = null;
A
Alex Dima 已提交
202
			if (this._profileInfo) {
203
				let extensionSegments = segments[ExtensionIdentifier.toKey(extensionDescription.identifier)] || [];
A
Alex Dima 已提交
204 205 206 207 208 209 210 211 212 213 214 215
				let extensionTotalTime = 0;
				for (let j = 0, lenJ = extensionSegments.length / 2; j < lenJ; j++) {
					const startTime = extensionSegments[2 * j];
					const endTime = extensionSegments[2 * j + 1];
					extensionTotalTime += (endTime - startTime);
				}
				profileInfo = {
					segments: extensionSegments,
					totalTime: extensionTotalTime
				};
			}

A
Alex Dima 已提交
216
			result[i] = {
A
Alex Dima 已提交
217
				originalIndex: i,
A
Alex Dima 已提交
218
				description: extensionDescription,
219
				marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)],
220
				status: statusMap[extensionDescription.identifier.value],
M
Matt Bierner 已提交
221
				profileInfo: profileInfo || undefined,
222
				unresponsiveProfile: this._extensionHostProfileService.getUnresponsiveProfile(extensionDescription.identifier)
A
Alex Dima 已提交
223 224 225
			};
		}

226 227 228 229 230 231 232 233 234 235 236
		result = result.filter(element => element.status.activationTimes);

		// bubble up extensions that have caused slowness
		result = result.sort((a, b) => {
			if (a.unresponsiveProfile === this._profileInfo && !b.unresponsiveProfile) {
				return -1;
			} else if (!a.unresponsiveProfile && b.unresponsiveProfile === this._profileInfo) {
				return 1;
			}
			return a.originalIndex - b.originalIndex;
		});
A
Alex Dima 已提交
237 238

		return result;
A
Alex Dima 已提交
239 240
	}

241 242
	protected createEditor(parent: HTMLElement): void {
		addClass(parent, 'runtime-extensions-editor');
A
Alex Dima 已提交
243 244 245

		const TEMPLATE_ID = 'runtimeExtensionElementTemplate';

J
Joao Moreno 已提交
246
		const delegate = new class implements IListVirtualDelegate<IRuntimeExtension>{
A
Alex Dima 已提交
247 248 249 250 251 252 253 254 255 256 257 258
			getHeight(element: IRuntimeExtension): number {
				return 62;
			}
			getTemplateId(element: IRuntimeExtension): string {
				return TEMPLATE_ID;
			}
		};

		interface IRuntimeExtensionTemplateData {
			root: HTMLElement;
			element: HTMLElement;
			name: HTMLElement;
259 260
			msgContainer: HTMLElement;
			actionbar: ActionBar;
I
isidor 已提交
261
			activationTime: HTMLElement;
A
Alex Dima 已提交
262
			profileTime: HTMLElement;
A
Alex Dima 已提交
263 264 265 266
			disposables: IDisposable[];
			elementDisposables: IDisposable[];
		}

J
Joao Moreno 已提交
267
		const renderer: IListRenderer<IRuntimeExtension, IRuntimeExtensionTemplateData> = {
A
Alex Dima 已提交
268 269 270
			templateId: TEMPLATE_ID,
			renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => {
				const element = append(root, $('.extension'));
A
Alex Dima 已提交
271 272 273

				const desc = append(element, $('div.desc'));
				const name = append(desc, $('div.name'));
A
Alex Dima 已提交
274 275 276

				const msgContainer = append(desc, $('div.msg'));

277 278 279
				const actionbar = new ActionBar(desc, { animated: false });
				actionbar.onDidRun(({ error }) => error && this._notificationService.error(error));

280

I
isidor 已提交
281 282 283
				const timeContainer = append(element, $('.time'));
				const activationTime = append(timeContainer, $('div.activation-time'));
				const profileTime = append(timeContainer, $('div.profile-time'));
A
Alex Dima 已提交
284 285 286 287 288 289 290

				const disposables = [actionbar];

				return {
					root,
					element,
					name,
I
isidor 已提交
291
					actionbar,
I
isidor 已提交
292
					activationTime,
A
Alex Dima 已提交
293
					profileTime,
294
					msgContainer,
A
Alex Dima 已提交
295 296 297 298 299 300 301 302 303
					disposables,
					elementDisposables: []
				};
			},

			renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => {

				data.elementDisposables = dispose(data.elementDisposables);

304 305
				toggleClass(data.root, 'odd', index % 2 === 1);

306
				data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || '';
A
Alex Dima 已提交
307

A
Alex Dima 已提交
308
				const activationTimes = element.status.activationTimes!;
A
Alex Dima 已提交
309
				let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime;
310
				data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`;
311 312

				data.actionbar.clear();
313 314 315 316
				if (element.unresponsiveProfile) {
					data.actionbar.push(this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile), { icon: true, label: true });
				}
				if (isNonEmptyArray(element.status.runtimeErrors)) {
317
					data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService), { icon: true, label: true });
318
				}
A
Alex Dima 已提交
319 320

				let title: string;
321
				const activationId = activationTimes.activationReason.extensionId.value;
C
Christof Marti 已提交
322
				const activationEvent = activationTimes.activationReason.activationEvent;
323 324 325 326
				if (activationEvent === '*') {
					title = nls.localize('starActivation', "Activated by {0} on start-up", activationId);
				} else if (/^workspaceContains:/.test(activationEvent)) {
					let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
A
Alex Dima 已提交
327
					if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
A
Alex Dima 已提交
328 329 330 331 332
						title = nls.localize({
							key: 'workspaceContainsGlobActivation',
							comment: [
								'{0} will be a glob pattern'
							]
333
						}, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId);
A
Alex Dima 已提交
334
					} else {
A
Alex Dima 已提交
335 336 337 338 339
						title = nls.localize({
							key: 'workspaceContainsFileActivation',
							comment: [
								'{0} will be a file name'
							]
340
						}, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId);
A
Alex Dima 已提交
341
					}
342 343
				} else if (/^workspaceContainsTimeout:/.test(activationEvent)) {
					const glob = activationEvent.substr('workspaceContainsTimeout:'.length);
344 345 346 347 348
					title = nls.localize({
						key: 'workspaceContainsTimeout',
						comment: [
							'{0} will be a glob pattern'
						]
349 350 351 352
					}, "Activated by {1} because searching for {0} took too long", glob, activationId);
				} else if (/^onLanguage:/.test(activationEvent)) {
					let language = activationEvent.substr('onLanguage:'.length);
					title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId);
A
Alex Dima 已提交
353
				} else {
A
Alex Dima 已提交
354 355 356 357 358
					title = nls.localize({
						key: 'workspaceGenericActivation',
						comment: [
							'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.'
						]
359
					}, "Activated by {1} on {0}", activationEvent, activationId);
A
Alex Dima 已提交
360
				}
I
isidor 已提交
361
				data.activationTime.title = title;
362 363 364

				clearNode(data.msgContainer);

365
				if (this._extensionHostProfileService.getUnresponsiveProfile(element.description.identifier)) {
366
					const el = $('span');
M
Miguel Solorio 已提交
367
					el.innerHTML = renderCodicons(` $(alert) Unresponsive`);
368 369 370 371
					el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze.");
					data.msgContainer.appendChild(el);
				}

372
				if (isNonEmptyArray(element.status.runtimeErrors)) {
373
					const el = $('span');
M
Miguel Solorio 已提交
374
					el.innerHTML = renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`);
375 376 377 378 379
					data.msgContainer.appendChild(el);
				}

				if (element.status.messages && element.status.messages.length > 0) {
					const el = $('span');
M
Miguel Solorio 已提交
380
					el.innerHTML = renderCodicons(`$(alert) ${element.status.messages[0].message}`);
381
					data.msgContainer.appendChild(el);
A
Alex Dima 已提交
382
				}
A
Alex Dima 已提交
383

384
				if (element.description.extensionLocation.scheme !== 'file') {
385
					const el = $('span');
M
Miguel Solorio 已提交
386
					el.innerHTML = renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`);
387
					data.msgContainer.appendChild(el);
A
Alex Dima 已提交
388

389
					const hostLabel = this._labelService.getHostLabel(REMOTE_HOST_SCHEME, this._environmentService.configuration.remoteAuthority);
A
Alex Dima 已提交
390
					if (hostLabel) {
M
Miguel Solorio 已提交
391
						el.innerHTML = renderCodicons(`$(remote) ${hostLabel}`);
A
Alex Dima 已提交
392
					}
393 394
				}

395
				if (this._profileInfo && element.profileInfo) {
I
isidor 已提交
396
					data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`;
A
Alex Dima 已提交
397 398 399
				} else {
					data.profileTime.textContent = '';
				}
400

A
Alex Dima 已提交
401 402 403 404 405 406 407
			},

			disposeTemplate: (data: IRuntimeExtensionTemplateData): void => {
				data.disposables = dispose(data.disposables);
			}
		};

J
Joao Moreno 已提交
408 409 410
		this._list = this._instantiationService.createInstance(WorkbenchList,
			'RuntimeExtensions',
			parent, delegate, [renderer], {
M
Matt Bierner 已提交
411 412 413 414
			multipleSelectionSupport: false,
			setRowLineHeight: false,
			horizontalScrolling: false
		});
A
Alex Dima 已提交
415

M
Matt Bierner 已提交
416
		this._list.splice(0, this._list.length, this._elements || undefined);
A
Alex Dima 已提交
417 418

		this._list.onContextMenu((e) => {
J
Joao Moreno 已提交
419 420 421 422
			if (!e.element) {
				return;
			}

A
Alex Dima 已提交
423 424
			const actions: IAction[] = [];

425
			actions.push(new ReportExtensionIssueAction(e.element, this._openerService));
426 427
			actions.push(new Separator());

S
Sandeep Somavarapu 已提交
428
			if (e.element.marketplaceInfo) {
429 430
				actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace)));
				actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally)));
431 432
				actions.push(new Separator());
			}
433 434 435 436 437 438 439
			const state = this._extensionHostProfileService.state;
			if (state === ProfileSessionState.Running) {
				actions.push(this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL));
			} else {
				actions.push(this._instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL));
			}
			actions.push(this.saveExtensionHostProfileAction);
A
Alex Dima 已提交
440 441 442

			this._contextMenuService.showContextMenu({
				getAnchor: () => e.anchor,
443
				getActions: () => actions
A
Alex Dima 已提交
444 445
			});
		});
A
Alex Dima 已提交
446 447
	}

448 449
	@memoize
	private get saveExtensionHostProfileAction(): IAction {
450
		return this._instantiationService.createInstance(SaveExtensionHostProfileAction, SaveExtensionHostProfileAction.ID, SaveExtensionHostProfileAction.LABEL);
I
isidor 已提交
451 452
	}

A
Alex Dima 已提交
453
	public layout(dimension: Dimension): void {
454 455 456
		if (this._list) {
			this._list.layout(dimension.height);
		}
A
Alex Dima 已提交
457 458 459 460
	}
}

export class ShowRuntimeExtensionsAction extends Action {
461
	static readonly ID = 'workbench.action.showRuntimeExtensions';
462
	static readonly LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions");
A
Alex Dima 已提交
463 464 465

	constructor(
		id: string, label: string,
466
		@IEditorService private readonly _editorService: IEditorService,
A
Alex Dima 已提交
467 468 469 470 471
		@IInstantiationService private readonly _instantiationService: IInstantiationService
	) {
		super(id, label);
	}

472 473
	public async run(e?: any): Promise<any> {
		await this._editorService.openEditor(this._instantiationService.createInstance(RuntimeExtensionsInput), { revealIfOpened: true });
A
Alex Dima 已提交
474 475
	}
}
I
isidor 已提交
476

477
export class ReportExtensionIssueAction extends Action {
I
isidor 已提交
478

479
	private static readonly _id = 'workbench.extensions.action.reportExtensionIssue';
480
	private static readonly _label = nls.localize('reportExtensionIssue', "Report Issue");
481 482

	private readonly _url: string;
I
isidor 已提交
483

484 485 486
	constructor(extension: {
		description: IExtensionDescription;
		marketplaceInfo: IExtension;
487
		status?: IExtensionsStatus;
488
		unresponsiveProfile?: IExtensionHostProfile
489
	}, @IOpenerService private readonly openerService: IOpenerService) {
490 491
		super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue');
		this.enabled = extension.marketplaceInfo
492
			&& extension.marketplaceInfo.type === ExtensionType.User
493
			&& !!extension.description.repository && !!extension.description.repository.url;
494

495
		this._url = ReportExtensionIssueAction._generateNewIssueUrl(extension);
496
	}
I
isidor 已提交
497

498
	async run(): Promise<void> {
499
		this.openerService.open(URI.parse(this._url));
I
isidor 已提交
500 501
	}

502 503 504
	private static _generateNewIssueUrl(extension: {
		description: IExtensionDescription;
		marketplaceInfo: IExtension;
505
		status?: IExtensionsStatus;
506
		unresponsiveProfile?: IExtensionHostProfile
507
	}): string {
508
		let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined;
I
isidor 已提交
509 510 511
		if (!!baseUrl) {
			baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`;
		} else {
512
			baseUrl = product.reportIssueUrl!;
I
isidor 已提交
513 514
		}

515 516 517 518
		let reason = 'Bug';
		let title = 'Extension issue';
		let message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:';
		clipboard.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```');
519

I
isidor 已提交
520 521 522
		const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
		const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';
		const body = encodeURIComponent(
523 524 525 526
			`- Issue Type: \`${reason}\`
- Extension Name: \`${extension.description.name}\`
- Extension Version: \`${extension.description.version}\`
- OS Version: \`${osVersion}\`
527
- VSCode version: \`${product.version}\`\n\n${message}`
I
isidor 已提交
528 529
		);

530
		return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`;
I
isidor 已提交
531
	}
A
Alex Dima 已提交
532 533
}

534
export class DebugExtensionHostAction extends Action {
535
	static readonly ID = 'workbench.extensions.action.debugExtensionHost';
536 537
	static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host");
	static readonly CSS_CLASS = 'debug-extension-host';
538 539 540

	constructor(
		@IDebugService private readonly _debugService: IDebugService,
541
		@IElectronService private readonly _electronService: IElectronService,
542 543 544 545 546 547
		@IDialogService private readonly _dialogService: IDialogService,
		@IExtensionService private readonly _extensionService: IExtensionService,
	) {
		super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS);
	}

548
	async run(): Promise<any> {
549

550
		const inspectPort = await this._extensionService.getInspectPort(false);
551
		if (!inspectPort) {
552
			const res = await this._dialogService.confirm({
553 554 555 556 557 558
				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?", product.nameLong),
				primaryButton: nls.localize('restart3', "Restart"),
				secondaryButton: nls.localize('cancel', "Cancel")
			});
559
			if (res.confirmed) {
560
				this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
561
			}
562 563
		}

M
Matt Bierner 已提交
564
		return this._debugService.startDebugging(undefined, {
565
			type: 'node',
A
Andre Weinand 已提交
566
			name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"),
567 568 569 570 571 572
			request: 'attach',
			port: inspectPort
		});
	}
}

573
export class StartExtensionHostProfileAction extends Action {
574
	static readonly ID = 'workbench.extensions.action.extensionHostProfile';
575
	static readonly LABEL = nls.localize('extensionHostProfileStart', "Start Extension Host Profile");
A
Alex Dima 已提交
576 577

	constructor(
578
		id: string = StartExtensionHostProfileAction.ID, label: string = StartExtensionHostProfileAction.LABEL,
579
		@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
A
Alex Dima 已提交
580
	) {
581 582
		super(id, label);
	}
583

584
	run(): Promise<any> {
585
		this._extensionHostProfileService.startProfiling();
586
		return Promise.resolve();
A
Alex Dima 已提交
587
	}
588
}
A
Alex Dima 已提交
589

590 591
export class StopExtensionHostProfileAction extends Action {
	static readonly ID = 'workbench.extensions.action.stopExtensionHostProfile';
592
	static readonly LABEL = nls.localize('stopExtensionHostProfileStart', "Stop Extension Host Profile");
593

594 595 596 597 598
	constructor(
		id: string = StartExtensionHostProfileAction.ID, label: string = StartExtensionHostProfileAction.LABEL,
		@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
	) {
		super(id, label);
A
Alex Dima 已提交
599 600
	}

601
	run(): Promise<any> {
602
		this._extensionHostProfileService.stopProfiling();
603
		return Promise.resolve();
A
Alex Dima 已提交
604 605
	}
}
606

607
export class SaveExtensionHostProfileAction extends Action {
608

609
	static readonly LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile");
610
	static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile';
611 612 613

	constructor(
		id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL,
614
		@IElectronService private readonly _electronService: IElectronService,
615
		@IEnvironmentService private readonly _environmentService: IEnvironmentService,
616
		@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
617
	) {
618
		super(id, label, undefined, false);
619 620 621
		this._extensionHostProfileService.onDidChangeLastProfile(() => {
			this.enabled = (this._extensionHostProfileService.lastProfile !== null);
		});
622 623
	}

624 625
	run(): Promise<any> {
		return Promise.resolve(this._asyncRun());
626 627 628
	}

	private async _asyncRun(): Promise<any> {
629
		let picked = await this._electronService.showSaveDialog({
630 631 632 633 634 635 636 637 638
			title: 'Save Extension Host Profile',
			buttonLabel: 'Save',
			defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`,
			filters: [{
				name: 'CPU Profiles',
				extensions: ['cpuprofile', 'txt']
			}]
		});

639
		if (!picked || !picked.filePath || picked.canceled) {
640 641 642
			return;
		}

643
		const profileInfo = this._extensionHostProfileService.lastProfile;
644
		let dataToWrite: object = profileInfo ? profileInfo.data : {};
645

646 647
		let savePath = picked.filePath;

648 649 650 651 652 653 654
		if (this._environmentService.isBuilt) {
			const profiler = await import('v8-inspect-profiler');
			// when running from a not-development-build we remove
			// absolute filenames because we don't want to reveal anything
			// about users. We also append the `.txt` suffix to make it
			// easier to attach these files to GH issues

J
Johannes Rieken 已提交
655
			let tmp = profiler.rewriteAbsolutePaths({ profile: dataToWrite as any }, 'piiRemoved');
656 657
			dataToWrite = tmp.profile;

658
			savePath = savePath + '.txt';
659 660
		}

661
		return writeFile(savePath, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t'));
662 663
	}
}