Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.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 { disposableWindowInterval } from '../../../../base/browser/dom.js';
7
import { mainWindow } from '../../../../base/browser/window.js';
8
import { onUnexpectedError } from '../../../../base/common/errors.js';
9
import { Emitter, Event } from '../../../../base/common/event.js';
10
import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
11
import { randomPort } from '../../../../base/common/ports.js';
12
import * as nls from '../../../../nls.js';
13
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
14
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
15
import { ExtensionIdentifier, ExtensionIdentifierMap } from '../../../../platform/extensions/common/extensions.js';
16
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
17
import { INativeHostService } from '../../../../platform/native/common/native.js';
18
import { IProductService } from '../../../../platform/product/common/productService.js';
19
import { RuntimeExtensionsInput } from '../common/runtimeExtensionsInput.js';
20
import { IExtensionHostProfileService, ProfileSessionState } from './runtimeExtensionsEditor.js';
21
import { IEditorService } from '../../../services/editor/common/editorService.js';
22
import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js';
23
import { IExtensionHostProfile, IExtensionService, ProfileSession } from '../../../services/extensions/common/extensions.js';
24
import { ExtensionHostProfiler } from '../../../services/extensions/electron-browser/extensionHostProfiler.js';
25
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from '../../../services/statusbar/browser/statusbar.js';
26
import { URI } from '../../../../base/common/uri.js';
27
28
export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService {
29
30
declare readonly _serviceBrand: undefined;
31
32
private readonly _onDidChangeState: Emitter<void> = this._register(new Emitter<void>());
33
public readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
34
35
private readonly _onDidChangeLastProfile: Emitter<void> = this._register(new Emitter<void>());
36
public readonly onDidChangeLastProfile: Event<void> = this._onDidChangeLastProfile.event;
37
38
private readonly _unresponsiveProfiles = new ExtensionIdentifierMap<IExtensionHostProfile>();
39
private _profile: IExtensionHostProfile | null;
40
private _profileSession: ProfileSession | null;
41
private _state: ProfileSessionState = ProfileSessionState.None;
42
43
private profilingStatusBarIndicator: IStatusbarEntryAccessor | undefined;
44
private readonly profilingStatusBarIndicatorLabelUpdater = this._register(new MutableDisposable());
45
46
public lastProfileSavedTo: URI | undefined;
47
public get state() { return this._state; }
48
public get lastProfile() { return this._profile; }
49
50
constructor(
51
@IExtensionService private readonly _extensionService: IExtensionService,
52
@IEditorService private readonly _editorService: IEditorService,
53
@IInstantiationService private readonly _instantiationService: IInstantiationService,
54
@INativeHostService private readonly _nativeHostService: INativeHostService,
55
@IDialogService private readonly _dialogService: IDialogService,
56
@IStatusbarService private readonly _statusbarService: IStatusbarService,
57
@IProductService private readonly _productService: IProductService
58
) {
59
super();
60
this._profile = null;
61
this._profileSession = null;
62
this._setState(ProfileSessionState.None);
63
64
CommandsRegistry.registerCommand('workbench.action.extensionHostProfiler.stop', () => {
65
this.stopProfiling();
66
this._editorService.openEditor(RuntimeExtensionsInput.instance, { pinned: true });
67
});
68
}
69
70
private _setState(state: ProfileSessionState): void {
71
if (this._state === state) {
72
return;
73
}
74
this._state = state;
75
76
if (this._state === ProfileSessionState.Running) {
77
this.updateProfilingStatusBarIndicator(true);
78
} else if (this._state === ProfileSessionState.Stopping) {
79
this.updateProfilingStatusBarIndicator(false);
80
}
81
82
this._onDidChangeState.fire(undefined);
83
}
84
85
private updateProfilingStatusBarIndicator(visible: boolean): void {
86
this.profilingStatusBarIndicatorLabelUpdater.clear();
87
88
if (visible) {
89
const indicator: IStatusbarEntry = {
90
name: nls.localize('status.profiler', "Extension Profiler"),
91
text: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
92
showProgress: true,
93
ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
94
tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),
95
command: 'workbench.action.extensionHostProfiler.stop'
96
};
97
98
const timeStarted = Date.now();
99
const handle = disposableWindowInterval(mainWindow, () => {
100
this.profilingStatusBarIndicator?.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });
101
}, 1000);
102
this.profilingStatusBarIndicatorLabelUpdater.value = handle;
103
104
if (!this.profilingStatusBarIndicator) {
105
this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', StatusbarAlignment.RIGHT);
106
} else {
107
this.profilingStatusBarIndicator.update(indicator);
108
}
109
} else {
110
if (this.profilingStatusBarIndicator) {
111
this.profilingStatusBarIndicator.dispose();
112
this.profilingStatusBarIndicator = undefined;
113
}
114
}
115
}
116
117
public async startProfiling(): Promise<any> {
118
if (this._state !== ProfileSessionState.None) {
119
return null;
120
}
121
122
const inspectPorts = await this._extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, true);
123
124
if (inspectPorts.length === 0) {
125
return this._dialogService.confirm({
126
type: 'info',
127
message: nls.localize('restart1', "Profile Extensions"),
128
detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this._productService.nameLong),
129
primaryButton: nls.localize({ key: 'restart3', comment: ['&& denotes a mnemonic'] }, "&&Restart")
130
}).then(res => {
131
if (res.confirmed) {
132
this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
133
}
134
});
135
}
136
137
if (inspectPorts.length > 1) {
138
// TODO
139
console.warn(`There are multiple extension hosts available for profiling. Picking the first one...`);
140
}
141
142
this._setState(ProfileSessionState.Starting);
143
144
return this._instantiationService.createInstance(ExtensionHostProfiler, inspectPorts[0].host, inspectPorts[0].port).start().then((value) => {
145
this._profileSession = value;
146
this._setState(ProfileSessionState.Running);
147
}, (err) => {
148
onUnexpectedError(err);
149
this._setState(ProfileSessionState.None);
150
});
151
}
152
153
public stopProfiling(): void {
154
if (this._state !== ProfileSessionState.Running || !this._profileSession) {
155
return;
156
}
157
158
this._setState(ProfileSessionState.Stopping);
159
this._profileSession.stop().then((result) => {
160
this._setLastProfile(result);
161
this._setState(ProfileSessionState.None);
162
}, (err) => {
163
onUnexpectedError(err);
164
this._setState(ProfileSessionState.None);
165
});
166
this._profileSession = null;
167
}
168
169
private _setLastProfile(profile: IExtensionHostProfile) {
170
this._profile = profile;
171
this.lastProfileSavedTo = undefined;
172
this._onDidChangeLastProfile.fire(undefined);
173
}
174
175
getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {
176
return this._unresponsiveProfiles.get(extensionId);
177
}
178
179
setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void {
180
this._unresponsiveProfiles.set(extensionId, profile);
181
this._setLastProfile(profile);
182
}
183
184
}
185
186