Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.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 { IProductService } from '../../../../platform/product/common/productService.js';
7
import { Action } from '../../../../base/common/actions.js';
8
import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
9
import { URI } from '../../../../base/common/uri.js';
10
import { IExtensionHostProfile } from '../../../services/extensions/common/extensions.js';
11
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
12
import { localize } from '../../../../nls.js';
13
import { CancellationToken } from '../../../../base/common/cancellation.js';
14
import { IRequestService, asText } from '../../../../platform/request/common/request.js';
15
import { joinPath } from '../../../../base/common/resources.js';
16
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
17
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
18
import { INativeHostService } from '../../../../platform/native/common/native.js';
19
import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';
20
import { Utils } from '../../../../platform/profiling/common/profiling.js';
21
import { IFileService } from '../../../../platform/files/common/files.js';
22
import { VSBuffer } from '../../../../base/common/buffer.js';
23
import { IRequestContext } from '../../../../base/parts/request/common/request.js';
24
25
abstract class RepoInfo {
26
abstract get base(): string;
27
abstract get owner(): string;
28
abstract get repo(): string;
29
30
static fromExtension(desc: IExtensionDescription): RepoInfo | undefined {
31
32
let result: RepoInfo | undefined;
33
34
// scheme:auth/OWNER/REPO/issues/
35
if (desc.bugs && typeof desc.bugs.url === 'string') {
36
const base = URI.parse(desc.bugs.url);
37
const match = /\/([^/]+)\/([^/]+)\/issues\/?$/.exec(desc.bugs.url);
38
if (match) {
39
result = {
40
base: base.with({ path: null, fragment: null, query: null }).toString(true),
41
owner: match[1],
42
repo: match[2]
43
};
44
}
45
}
46
// scheme:auth/OWNER/REPO.git
47
if (!result && desc.repository && typeof desc.repository.url === 'string') {
48
const base = URI.parse(desc.repository.url);
49
const match = /\/([^/]+)\/([^/]+)(\.git)?$/.exec(desc.repository.url);
50
if (match) {
51
result = {
52
base: base.with({ path: null, fragment: null, query: null }).toString(true),
53
owner: match[1],
54
repo: match[2]
55
};
56
}
57
}
58
59
// for now only GH is supported
60
if (result && result.base.indexOf('github') === -1) {
61
result = undefined;
62
}
63
64
return result;
65
}
66
}
67
68
export class SlowExtensionAction extends Action {
69
70
constructor(
71
readonly extension: IExtensionDescription,
72
readonly profile: IExtensionHostProfile,
73
@IInstantiationService private readonly _instantiationService: IInstantiationService,
74
) {
75
super('report.slow', localize('cmd.reportOrShow', "Performance Issue"), 'extension-action report-issue');
76
this.enabled = Boolean(RepoInfo.fromExtension(extension));
77
}
78
79
override async run(): Promise<void> {
80
const action = await this._instantiationService.invokeFunction(createSlowExtensionAction, this.extension, this.profile);
81
if (action) {
82
await action.run();
83
}
84
}
85
}
86
87
export async function createSlowExtensionAction(
88
accessor: ServicesAccessor,
89
extension: IExtensionDescription,
90
profile: IExtensionHostProfile
91
): Promise<Action | undefined> {
92
93
const info = RepoInfo.fromExtension(extension);
94
if (!info) {
95
return undefined;
96
}
97
98
const requestService = accessor.get(IRequestService);
99
const instaService = accessor.get(IInstantiationService);
100
const url = `https://api.github.com/search/issues?q=is:issue+state:open+in:title+repo:${info.owner}/${info.repo}+%22Extension+causes+high+cpu+load%22`;
101
let res: IRequestContext;
102
try {
103
res = await requestService.request({ url }, CancellationToken.None);
104
} catch {
105
return undefined;
106
}
107
const rawText = await asText(res);
108
if (!rawText) {
109
return undefined;
110
}
111
112
const data = <{ total_count: number }>JSON.parse(rawText);
113
if (!data || typeof data.total_count !== 'number') {
114
return undefined;
115
} else if (data.total_count === 0) {
116
return instaService.createInstance(ReportExtensionSlowAction, extension, info, profile);
117
} else {
118
return instaService.createInstance(ShowExtensionSlowAction, extension, info, profile);
119
}
120
}
121
122
class ReportExtensionSlowAction extends Action {
123
124
constructor(
125
readonly extension: IExtensionDescription,
126
readonly repoInfo: RepoInfo,
127
readonly profile: IExtensionHostProfile,
128
@IDialogService private readonly _dialogService: IDialogService,
129
@IOpenerService private readonly _openerService: IOpenerService,
130
@IProductService private readonly _productService: IProductService,
131
@INativeHostService private readonly _nativeHostService: INativeHostService,
132
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
133
@IFileService private readonly _fileService: IFileService,
134
) {
135
super('report.slow', localize('cmd.report', "Report Issue"));
136
}
137
138
override async run(): Promise<void> {
139
140
// rewrite pii (paths) and store on disk
141
const data = Utils.rewriteAbsolutePaths(this.profile.data, 'pii_removed');
142
const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`);
143
await this._fileService.writeFile(path, VSBuffer.fromString(JSON.stringify(data, undefined, 4)));
144
145
// build issue
146
const os = await this._nativeHostService.getOSProperties();
147
const title = encodeURIComponent('Extension causes high cpu load');
148
const osVersion = `${os.type} ${os.arch} ${os.release}`;
149
const message = `:warning: Make sure to **attach** this file from your *home*-directory:\n:warning:\`${path}\`\n\nFind more details here: https://github.com/microsoft/vscode/wiki/Explain-extension-causes-high-cpu-load`;
150
const body = encodeURIComponent(`- Issue Type: \`Performance\`
151
- Extension Name: \`${this.extension.name}\`
152
- Extension Version: \`${this.extension.version}\`
153
- OS Version: \`${osVersion}\`
154
- VS Code version: \`${this._productService.version}\`\n\n${message}`);
155
156
const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues/new/?body=${body}&title=${title}`;
157
this._openerService.open(URI.parse(url));
158
159
this._dialogService.info(
160
localize('attach.title', "Did you attach the CPU-Profile?"),
161
localize('attach.msg', "This is a reminder to make sure that you have not forgotten to attach '{0}' to the issue you have just created.", path.fsPath)
162
);
163
}
164
}
165
166
class ShowExtensionSlowAction extends Action {
167
168
constructor(
169
readonly extension: IExtensionDescription,
170
readonly repoInfo: RepoInfo,
171
readonly profile: IExtensionHostProfile,
172
@IDialogService private readonly _dialogService: IDialogService,
173
@IOpenerService private readonly _openerService: IOpenerService,
174
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
175
@IFileService private readonly _fileService: IFileService,
176
177
) {
178
super('show.slow', localize('cmd.show', "Show Issues"));
179
}
180
181
override async run(): Promise<void> {
182
183
// rewrite pii (paths) and store on disk
184
const data = Utils.rewriteAbsolutePaths(this.profile.data, 'pii_removed');
185
const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`);
186
await this._fileService.writeFile(path, VSBuffer.fromString(JSON.stringify(data, undefined, 4)));
187
188
// show issues
189
const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues?utf8=✓&q=is%3Aissue+state%3Aopen+%22Extension+causes+high+cpu+load%22`;
190
this._openerService.open(URI.parse(url));
191
192
this._dialogService.info(
193
localize('attach.title', "Did you attach the CPU-Profile?"),
194
localize('attach.msg2', "This is a reminder to make sure that you have not forgotten to attach '{0}' to an existing performance issue.", path.fsPath)
195
);
196
}
197
}
198
199