Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Action } from '../../../../base/common/actions.js';
7
import { VSBuffer } from '../../../../base/common/buffer.js';
8
import { Codicon } from '../../../../base/common/codicons.js';
9
import { Event } from '../../../../base/common/event.js';
10
import { Schemas } from '../../../../base/common/network.js';
11
import { joinPath } from '../../../../base/common/resources.js';
12
import { URI } from '../../../../base/common/uri.js';
13
import * as nls from '../../../../nls.js';
14
import { Action2, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';
15
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
16
import { ICommandService } from '../../../../platform/commands/common/commands.js';
17
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
18
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
19
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
20
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
21
import { IFileService } from '../../../../platform/files/common/files.js';
22
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
23
import { IInstantiationService, ServicesAccessor, createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
24
import { ILabelService } from '../../../../platform/label/common/label.js';
25
import { INotificationService } from '../../../../platform/notification/common/notification.js';
26
import { IV8Profile, Utils } from '../../../../platform/profiling/common/profiling.js';
27
import { IStorageService } from '../../../../platform/storage/common/storage.js';
28
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
29
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
30
import { ActiveEditorContext } from '../../../common/contextkeys.js';
31
import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';
32
import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';
33
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
34
import { IExtensionFeaturesManagementService } from '../../../services/extensionManagement/common/extensionFeatures.js';
35
import { IExtensionHostProfile, IExtensionService } from '../../../services/extensions/common/extensions.js';
36
import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from '../browser/abstractRuntimeExtensionsEditor.js';
37
import { IExtensionsWorkbenchService } from '../common/extensions.js';
38
import { ReportExtensionIssueAction } from '../common/reportExtensionIssueAction.js';
39
import { SlowExtensionAction } from './extensionsSlowActions.js';
40
41
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
42
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
43
export const CONTEXT_EXTENSION_HOST_PROFILE_RECORDED = new RawContextKey<boolean>('extensionHostProfileRecorded', false);
44
45
export enum ProfileSessionState {
46
None = 0,
47
Starting = 1,
48
Running = 2,
49
Stopping = 3
50
}
51
52
export interface IExtensionHostProfileService {
53
readonly _serviceBrand: undefined;
54
55
readonly onDidChangeState: Event<void>;
56
readonly onDidChangeLastProfile: Event<void>;
57
58
readonly state: ProfileSessionState;
59
readonly lastProfile: IExtensionHostProfile | null;
60
lastProfileSavedTo: URI | undefined;
61
62
startProfiling(): void;
63
stopProfiling(): void;
64
65
getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined;
66
setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void;
67
}
68
69
export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor {
70
71
private _profileInfo: IExtensionHostProfile | null;
72
private _extensionsHostRecorded: IContextKey<boolean>;
73
private _profileSessionState: IContextKey<string>;
74
75
constructor(
76
group: IEditorGroup,
77
@ITelemetryService telemetryService: ITelemetryService,
78
@IThemeService themeService: IThemeService,
79
@IContextKeyService contextKeyService: IContextKeyService,
80
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
81
@IExtensionService extensionService: IExtensionService,
82
@INotificationService notificationService: INotificationService,
83
@IContextMenuService contextMenuService: IContextMenuService,
84
@IInstantiationService instantiationService: IInstantiationService,
85
@IStorageService storageService: IStorageService,
86
@ILabelService labelService: ILabelService,
87
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
88
@IClipboardService clipboardService: IClipboardService,
89
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
90
@IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService,
91
@IHoverService hoverService: IHoverService,
92
@IMenuService menuService: IMenuService,
93
) {
94
super(group, telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService, extensionFeaturesManagementService, hoverService, menuService);
95
this._profileInfo = this._extensionHostProfileService.lastProfile;
96
this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService);
97
this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService);
98
99
this._register(this._extensionHostProfileService.onDidChangeLastProfile(() => {
100
this._profileInfo = this._extensionHostProfileService.lastProfile;
101
this._extensionsHostRecorded.set(!!this._profileInfo);
102
this._updateExtensions();
103
}));
104
this._register(this._extensionHostProfileService.onDidChangeState(() => {
105
const state = this._extensionHostProfileService.state;
106
this._profileSessionState.set(ProfileSessionState[state].toLowerCase());
107
}));
108
}
109
110
protected _getProfileInfo(): IExtensionHostProfile | null {
111
return this._profileInfo;
112
}
113
114
protected _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {
115
return this._extensionHostProfileService.getUnresponsiveProfile(extensionId);
116
}
117
118
protected _createSlowExtensionAction(element: IRuntimeExtension): Action | null {
119
if (element.unresponsiveProfile) {
120
return this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile);
121
}
122
return null;
123
}
124
125
protected _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null {
126
if (element.marketplaceInfo) {
127
return this._instantiationService.createInstance(ReportExtensionIssueAction, element.description);
128
}
129
return null;
130
}
131
}
132
133
export class StartExtensionHostProfileAction extends Action2 {
134
static readonly ID = 'workbench.extensions.action.extensionHostProfile';
135
static readonly LABEL = nls.localize('extensionHostProfileStart', "Start Extension Host Profile");
136
137
constructor() {
138
super({
139
id: StartExtensionHostProfileAction.ID,
140
title: { value: StartExtensionHostProfileAction.LABEL, original: 'Start Extension Host Profile' },
141
precondition: CONTEXT_PROFILE_SESSION_STATE.isEqualTo('none'),
142
icon: Codicon.circleFilled,
143
menu: [{
144
id: MenuId.EditorTitle,
145
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running')),
146
group: 'navigation',
147
}, {
148
id: MenuId.ExtensionEditorContextMenu,
149
when: CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running'),
150
group: 'profiling',
151
}]
152
});
153
}
154
155
run(accessor: ServicesAccessor): Promise<any> {
156
const extensionHostProfileService = accessor.get(IExtensionHostProfileService);
157
extensionHostProfileService.startProfiling();
158
return Promise.resolve();
159
}
160
}
161
162
export class StopExtensionHostProfileAction extends Action2 {
163
static readonly ID = 'workbench.extensions.action.stopExtensionHostProfile';
164
static readonly LABEL = nls.localize('stopExtensionHostProfileStart', "Stop Extension Host Profile");
165
166
constructor() {
167
super({
168
id: StopExtensionHostProfileAction.ID,
169
title: { value: StopExtensionHostProfileAction.LABEL, original: 'Stop Extension Host Profile' },
170
icon: Codicon.debugStop,
171
menu: [{
172
id: MenuId.EditorTitle,
173
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running')),
174
group: 'navigation',
175
}, {
176
id: MenuId.ExtensionEditorContextMenu,
177
when: CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running'),
178
group: 'profiling',
179
}]
180
});
181
}
182
183
run(accessor: ServicesAccessor): Promise<any> {
184
const extensionHostProfileService = accessor.get(IExtensionHostProfileService);
185
extensionHostProfileService.stopProfiling();
186
return Promise.resolve();
187
}
188
}
189
190
export class OpenExtensionHostProfileACtion extends Action2 {
191
static readonly LABEL = nls.localize('openExtensionHostProfile', "Open Extension Host Profile");
192
static readonly ID = 'workbench.extensions.action.openExtensionHostProfile';
193
194
constructor() {
195
super({
196
id: OpenExtensionHostProfileACtion.ID,
197
title: { value: OpenExtensionHostProfileACtion.LABEL, original: 'Open Extension Host Profile' },
198
precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,
199
icon: Codicon.graph,
200
menu: [{
201
id: MenuId.EditorTitle,
202
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)),
203
group: 'navigation',
204
}, {
205
id: MenuId.ExtensionEditorContextMenu,
206
when: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,
207
group: 'profiling',
208
}]
209
});
210
}
211
212
async run(accessor: ServicesAccessor): Promise<void> {
213
const extensionHostProfileService = accessor.get(IExtensionHostProfileService);
214
const commandService = accessor.get(ICommandService);
215
const editorService = accessor.get(IEditorService);
216
if (!extensionHostProfileService.lastProfileSavedTo) {
217
await commandService.executeCommand(SaveExtensionHostProfileAction.ID);
218
}
219
if (!extensionHostProfileService.lastProfileSavedTo) {
220
return;
221
}
222
223
await editorService.openEditor({
224
resource: extensionHostProfileService.lastProfileSavedTo,
225
options: {
226
revealIfOpened: true,
227
override: 'jsProfileVisualizer.cpuprofile.table',
228
},
229
}, SIDE_GROUP);
230
}
231
232
}
233
234
export class SaveExtensionHostProfileAction extends Action2 {
235
236
static readonly LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile");
237
static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile';
238
239
constructor() {
240
super({
241
id: SaveExtensionHostProfileAction.ID,
242
title: { value: SaveExtensionHostProfileAction.LABEL, original: 'Save Extension Host Profile' },
243
precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,
244
icon: Codicon.saveAll,
245
menu: [{
246
id: MenuId.EditorTitle,
247
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)),
248
group: 'navigation',
249
}, {
250
id: MenuId.ExtensionEditorContextMenu,
251
when: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED,
252
group: 'profiling',
253
}]
254
});
255
}
256
257
run(accessor: ServicesAccessor): Promise<any> {
258
const environmentService = accessor.get(IWorkbenchEnvironmentService);
259
const extensionHostProfileService = accessor.get(IExtensionHostProfileService);
260
const fileService = accessor.get(IFileService);
261
const fileDialogService = accessor.get(IFileDialogService);
262
return this._asyncRun(environmentService, extensionHostProfileService, fileService, fileDialogService);
263
}
264
265
private async _asyncRun(
266
environmentService: IWorkbenchEnvironmentService,
267
extensionHostProfileService: IExtensionHostProfileService,
268
fileService: IFileService,
269
fileDialogService: IFileDialogService
270
): Promise<any> {
271
const picked = await fileDialogService.showSaveDialog({
272
title: nls.localize('saveprofile.dialogTitle', "Save Extension Host Profile"),
273
availableFileSystems: [Schemas.file],
274
defaultUri: joinPath(await fileDialogService.defaultFilePath(), `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`),
275
filters: [{
276
name: 'CPU Profiles',
277
extensions: ['cpuprofile', 'txt']
278
}]
279
});
280
281
if (!picked) {
282
return;
283
}
284
285
const profileInfo = extensionHostProfileService.lastProfile;
286
let dataToWrite: object = profileInfo ? profileInfo.data : {};
287
288
let savePath = picked.fsPath;
289
290
if (environmentService.isBuilt) {
291
// when running from a not-development-build we remove
292
// absolute filenames because we don't want to reveal anything
293
// about users. We also append the `.txt` suffix to make it
294
// easier to attach these files to GH issues
295
dataToWrite = Utils.rewriteAbsolutePaths(dataToWrite as IV8Profile, 'piiRemoved');
296
297
savePath = savePath + '.txt';
298
}
299
300
const saveURI = URI.file(savePath);
301
extensionHostProfileService.lastProfileSavedTo = saveURI;
302
return fileService.writeFile(saveURI, VSBuffer.fromString(JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')));
303
}
304
}
305
306
307