Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/performance/electron-browser/startupTimings.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 { IWorkbenchContribution } from '../../../common/contributions.js';
7
import { timeout } from '../../../../base/common/async.js';
8
import { onUnexpectedError } from '../../../../base/common/errors.js';
9
import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';
10
import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';
11
import { IProductService } from '../../../../platform/product/common/productService.js';
12
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
13
import { IUpdateService } from '../../../../platform/update/common/update.js';
14
import { INativeHostService } from '../../../../platform/native/common/native.js';
15
import { IEditorService } from '../../../services/editor/common/editorService.js';
16
import { ITimerService } from '../../../services/timer/browser/timerService.js';
17
import { IFileService } from '../../../../platform/files/common/files.js';
18
import { URI } from '../../../../base/common/uri.js';
19
import { VSBuffer } from '../../../../base/common/buffer.js';
20
import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';
21
import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';
22
import { StartupTimings } from '../browser/startupTimings.js';
23
import { coalesce } from '../../../../base/common/arrays.js';
24
25
interface ITracingData {
26
readonly args?: {
27
readonly usedHeapSizeAfter?: number;
28
readonly usedHeapSizeBefore?: number;
29
};
30
readonly dur: number; // in microseconds
31
readonly name: string; // e.g. MinorGC or MajorGC
32
readonly pid: number;
33
}
34
35
interface IHeapStatistics {
36
readonly used: number;
37
readonly garbage: number;
38
readonly majorGCs: number;
39
readonly minorGCs: number;
40
readonly duration: number;
41
}
42
43
export class NativeStartupTimings extends StartupTimings implements IWorkbenchContribution {
44
45
constructor(
46
@IFileService private readonly _fileService: IFileService,
47
@ITimerService private readonly _timerService: ITimerService,
48
@INativeHostService private readonly _nativeHostService: INativeHostService,
49
@IEditorService editorService: IEditorService,
50
@IPaneCompositePartService paneCompositeService: IPaneCompositePartService,
51
@ITelemetryService private readonly _telemetryService: ITelemetryService,
52
@ILifecycleService lifecycleService: ILifecycleService,
53
@IUpdateService updateService: IUpdateService,
54
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
55
@IProductService private readonly _productService: IProductService,
56
@IWorkspaceTrustManagementService workspaceTrustService: IWorkspaceTrustManagementService
57
) {
58
super(editorService, paneCompositeService, lifecycleService, updateService, workspaceTrustService);
59
60
this._report().catch(onUnexpectedError);
61
}
62
63
private async _report() {
64
const standardStartupError = await this._isStandardStartup();
65
this._appendStartupTimes(standardStartupError).catch(onUnexpectedError);
66
}
67
68
private async _appendStartupTimes(standardStartupError: string | undefined) {
69
const appendTo = this._environmentService.args['prof-append-timers'];
70
const durationMarkers = this._environmentService.args['prof-duration-markers'];
71
const durationMarkersFile = this._environmentService.args['prof-duration-markers-file'];
72
if (!appendTo && !durationMarkers) {
73
// nothing to do
74
return;
75
}
76
77
try {
78
await Promise.all([
79
this._timerService.whenReady(),
80
timeout(15000), // wait: cached data creation, telemetry sending
81
]);
82
83
const perfBaseline = await this._timerService.perfBaseline;
84
const heapStatistics = await this._resolveStartupHeapStatistics();
85
if (heapStatistics) {
86
this._telemetryLogHeapStatistics(heapStatistics);
87
}
88
89
if (appendTo) {
90
const content = coalesce([
91
this._timerService.startupMetrics.ellapsed,
92
this._productService.nameShort,
93
(this._productService.commit || '').slice(0, 10) || '0000000000',
94
this._telemetryService.sessionId,
95
standardStartupError === undefined ? 'standard_start' : `NO_standard_start : ${standardStartupError}`,
96
`${String(perfBaseline).padStart(4, '0')}ms`,
97
heapStatistics ? this._printStartupHeapStatistics(heapStatistics) : undefined
98
]).join('\t') + '\n';
99
await this._appendContent(URI.file(appendTo), content);
100
}
101
102
if (durationMarkers?.length) {
103
const durations: string[] = [];
104
for (const durationMarker of durationMarkers) {
105
let duration: number = 0;
106
if (durationMarker === 'ellapsed') {
107
duration = this._timerService.startupMetrics.ellapsed;
108
} else if (durationMarker.indexOf('-') !== -1) {
109
const markers = durationMarker.split('-');
110
if (markers.length === 2) {
111
duration = this._timerService.getDuration(markers[0], markers[1]);
112
}
113
}
114
if (duration) {
115
durations.push(durationMarker);
116
durations.push(`${duration}`);
117
}
118
}
119
120
const durationsContent = `${durations.join('\t')}\n`;
121
if (durationMarkersFile) {
122
await this._appendContent(URI.file(durationMarkersFile), durationsContent);
123
} else {
124
console.log(durationsContent);
125
}
126
}
127
128
} catch (err) {
129
console.error(err);
130
} finally {
131
this._nativeHostService.exit(0);
132
}
133
}
134
135
protected override async _isStandardStartup(): Promise<string | undefined> {
136
const windowCount = await this._nativeHostService.getWindowCount();
137
if (windowCount !== 1) {
138
return `Expected window count : 1, Actual : ${windowCount}`;
139
}
140
return super._isStandardStartup();
141
}
142
143
private async _appendContent(file: URI, content: string): Promise<void> {
144
const chunks: VSBuffer[] = [];
145
if (await this._fileService.exists(file)) {
146
chunks.push((await this._fileService.readFile(file)).value);
147
}
148
chunks.push(VSBuffer.fromString(content));
149
await this._fileService.writeFile(file, VSBuffer.concat(chunks));
150
}
151
152
private async _resolveStartupHeapStatistics(): Promise<IHeapStatistics | undefined> {
153
if (
154
!this._environmentService.args['enable-tracing'] ||
155
!this._environmentService.args['trace-startup-file'] ||
156
this._environmentService.args['trace-startup-format'] !== 'json' ||
157
!this._environmentService.args['trace-startup-duration']
158
) {
159
return undefined; // unexpected arguments for startup heap statistics
160
}
161
162
const windowProcessId = await this._nativeHostService.getProcessId();
163
const used = (performance as unknown as { memory?: { usedJSHeapSize?: number } }).memory?.usedJSHeapSize ?? 0; // https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory
164
165
let minorGCs = 0;
166
let majorGCs = 0;
167
let garbage = 0;
168
let duration = 0;
169
170
try {
171
const traceContents: { traceEvents: ITracingData[] } = JSON.parse((await this._fileService.readFile(URI.file(this._environmentService.args['trace-startup-file']))).value.toString());
172
for (const event of traceContents.traceEvents) {
173
if (event.pid !== windowProcessId) {
174
continue;
175
}
176
177
switch (event.name) {
178
179
// Major/Minor GC Events
180
case 'MinorGC':
181
minorGCs++;
182
break;
183
case 'MajorGC':
184
majorGCs++;
185
break;
186
187
// GC Events that block the main thread
188
// Refs: https://v8.dev/blog/trash-talk
189
case 'V8.GCFinalizeMC':
190
case 'V8.GCScavenger':
191
duration += event.dur;
192
break;
193
}
194
195
if (event.name === 'MajorGC' || event.name === 'MinorGC') {
196
if (typeof event.args?.usedHeapSizeAfter === 'number' && typeof event.args.usedHeapSizeBefore === 'number') {
197
garbage += (event.args.usedHeapSizeBefore - event.args.usedHeapSizeAfter);
198
}
199
}
200
}
201
202
return { minorGCs, majorGCs, used, garbage, duration: Math.round(duration / 1000) };
203
} catch (error) {
204
console.error(error);
205
}
206
207
return undefined;
208
}
209
210
private _telemetryLogHeapStatistics({ used, garbage, majorGCs, minorGCs, duration }: IHeapStatistics): void {
211
type StartupHeapStatisticsClassification = {
212
owner: 'bpasero';
213
comment: 'An event that reports startup heap statistics for performance analysis.';
214
heapUsed: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Used heap' };
215
heapGarbage: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Garbage heap' };
216
majorGCs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Major GCs count' };
217
minorGCs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Minor GCs count' };
218
gcsDuration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'GCs duration' };
219
};
220
type StartupHeapStatisticsEvent = {
221
heapUsed: number;
222
heapGarbage: number;
223
majorGCs: number;
224
minorGCs: number;
225
gcsDuration: number;
226
};
227
this._telemetryService.publicLog2<StartupHeapStatisticsEvent, StartupHeapStatisticsClassification>('startupHeapStatistics', {
228
heapUsed: used,
229
heapGarbage: garbage,
230
majorGCs,
231
minorGCs,
232
gcsDuration: duration
233
});
234
}
235
236
private _printStartupHeapStatistics({ used, garbage, majorGCs, minorGCs, duration }: IHeapStatistics) {
237
const MB = 1024 * 1024;
238
return `Heap: ${Math.round(used / MB)}MB (used) ${Math.round(garbage / MB)}MB (garbage) ${majorGCs} (MajorGC) ${minorGCs} (MinorGC) ${duration}ms (GC duration)`;
239
}
240
}
241
242