Path: blob/main/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IWorkbenchContribution } from '../../../common/contributions.js';6import { timeout } from '../../../../base/common/async.js';7import { onUnexpectedError } from '../../../../base/common/errors.js';8import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';9import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';10import { IProductService } from '../../../../platform/product/common/productService.js';11import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';12import { IUpdateService } from '../../../../platform/update/common/update.js';13import { INativeHostService } from '../../../../platform/native/common/native.js';14import { IEditorService } from '../../../services/editor/common/editorService.js';15import { ITimerService } from '../../../services/timer/browser/timerService.js';16import { IFileService } from '../../../../platform/files/common/files.js';17import { URI } from '../../../../base/common/uri.js';18import { VSBuffer } from '../../../../base/common/buffer.js';19import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';20import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';21import { StartupTimings } from '../browser/startupTimings.js';22import { coalesce } from '../../../../base/common/arrays.js';2324interface ITracingData {25readonly args?: {26readonly usedHeapSizeAfter?: number;27readonly usedHeapSizeBefore?: number;28};29readonly dur: number; // in microseconds30readonly name: string; // e.g. MinorGC or MajorGC31readonly pid: number;32}3334interface IHeapStatistics {35readonly used: number;36readonly garbage: number;37readonly majorGCs: number;38readonly minorGCs: number;39readonly duration: number;40}4142export class NativeStartupTimings extends StartupTimings implements IWorkbenchContribution {4344constructor(45@IFileService private readonly _fileService: IFileService,46@ITimerService private readonly _timerService: ITimerService,47@INativeHostService private readonly _nativeHostService: INativeHostService,48@IEditorService editorService: IEditorService,49@IPaneCompositePartService paneCompositeService: IPaneCompositePartService,50@ITelemetryService private readonly _telemetryService: ITelemetryService,51@ILifecycleService lifecycleService: ILifecycleService,52@IUpdateService updateService: IUpdateService,53@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,54@IProductService private readonly _productService: IProductService,55@IWorkspaceTrustManagementService workspaceTrustService: IWorkspaceTrustManagementService56) {57super(editorService, paneCompositeService, lifecycleService, updateService, workspaceTrustService);5859this._report().catch(onUnexpectedError);60}6162private async _report() {63const standardStartupError = await this._isStandardStartup();64this._appendStartupTimes(standardStartupError).catch(onUnexpectedError);65}6667private async _appendStartupTimes(standardStartupError: string | undefined) {68const appendTo = this._environmentService.args['prof-append-timers'];69const durationMarkers = this._environmentService.args['prof-duration-markers'];70const durationMarkersFile = this._environmentService.args['prof-duration-markers-file'];71if (!appendTo && !durationMarkers) {72// nothing to do73return;74}7576try {77await Promise.all([78this._timerService.whenReady(),79timeout(15000), // wait: cached data creation, telemetry sending80]);8182const perfBaseline = await this._timerService.perfBaseline;83const heapStatistics = await this._resolveStartupHeapStatistics();84if (heapStatistics) {85this._telemetryLogHeapStatistics(heapStatistics);86}8788if (appendTo) {89const content = coalesce([90this._timerService.startupMetrics.ellapsed,91this._productService.nameShort,92(this._productService.commit || '').slice(0, 10) || '0000000000',93this._telemetryService.sessionId,94standardStartupError === undefined ? 'standard_start' : `NO_standard_start : ${standardStartupError}`,95`${String(perfBaseline).padStart(4, '0')}ms`,96heapStatistics ? this._printStartupHeapStatistics(heapStatistics) : undefined97]).join('\t') + '\n';98await this._appendContent(URI.file(appendTo), content);99}100101if (durationMarkers?.length) {102const durations: string[] = [];103for (const durationMarker of durationMarkers) {104let duration: number = 0;105if (durationMarker === 'ellapsed') {106duration = this._timerService.startupMetrics.ellapsed;107} else if (durationMarker.indexOf('-') !== -1) {108const markers = durationMarker.split('-');109if (markers.length === 2) {110duration = this._timerService.getDuration(markers[0], markers[1]);111}112}113if (duration) {114durations.push(durationMarker);115durations.push(`${duration}`);116}117}118119const durationsContent = `${durations.join('\t')}\n`;120if (durationMarkersFile) {121await this._appendContent(URI.file(durationMarkersFile), durationsContent);122} else {123console.log(durationsContent);124}125}126127} catch (err) {128console.error(err);129} finally {130this._nativeHostService.exit(0);131}132}133134protected override async _isStandardStartup(): Promise<string | undefined> {135const windowCount = await this._nativeHostService.getWindowCount();136if (windowCount !== 1) {137return `Expected window count : 1, Actual : ${windowCount}`;138}139return super._isStandardStartup();140}141142private async _appendContent(file: URI, content: string): Promise<void> {143const chunks: VSBuffer[] = [];144if (await this._fileService.exists(file)) {145chunks.push((await this._fileService.readFile(file)).value);146}147chunks.push(VSBuffer.fromString(content));148await this._fileService.writeFile(file, VSBuffer.concat(chunks));149}150151private async _resolveStartupHeapStatistics(): Promise<IHeapStatistics | undefined> {152if (153!this._environmentService.args['enable-tracing'] ||154!this._environmentService.args['trace-startup-file'] ||155this._environmentService.args['trace-startup-format'] !== 'json' ||156!this._environmentService.args['trace-startup-duration']157) {158return undefined; // unexpected arguments for startup heap statistics159}160161const windowProcessId = await this._nativeHostService.getProcessId();162const used = (performance as unknown as { memory?: { usedJSHeapSize?: number } }).memory?.usedJSHeapSize ?? 0; // https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory163164let minorGCs = 0;165let majorGCs = 0;166let garbage = 0;167let duration = 0;168169try {170const traceContents: { traceEvents: ITracingData[] } = JSON.parse((await this._fileService.readFile(URI.file(this._environmentService.args['trace-startup-file']))).value.toString());171for (const event of traceContents.traceEvents) {172if (event.pid !== windowProcessId) {173continue;174}175176switch (event.name) {177178// Major/Minor GC Events179case 'MinorGC':180minorGCs++;181break;182case 'MajorGC':183majorGCs++;184break;185186// GC Events that block the main thread187// Refs: https://v8.dev/blog/trash-talk188case 'V8.GCFinalizeMC':189case 'V8.GCScavenger':190duration += event.dur;191break;192}193194if (event.name === 'MajorGC' || event.name === 'MinorGC') {195if (typeof event.args?.usedHeapSizeAfter === 'number' && typeof event.args.usedHeapSizeBefore === 'number') {196garbage += (event.args.usedHeapSizeBefore - event.args.usedHeapSizeAfter);197}198}199}200201return { minorGCs, majorGCs, used, garbage, duration: Math.round(duration / 1000) };202} catch (error) {203console.error(error);204}205206return undefined;207}208209private _telemetryLogHeapStatistics({ used, garbage, majorGCs, minorGCs, duration }: IHeapStatistics): void {210type StartupHeapStatisticsClassification = {211owner: 'bpasero';212comment: 'An event that reports startup heap statistics for performance analysis.';213heapUsed: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Used heap' };214heapGarbage: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Garbage heap' };215majorGCs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Major GCs count' };216minorGCs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Minor GCs count' };217gcsDuration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'GCs duration' };218};219type StartupHeapStatisticsEvent = {220heapUsed: number;221heapGarbage: number;222majorGCs: number;223minorGCs: number;224gcsDuration: number;225};226this._telemetryService.publicLog2<StartupHeapStatisticsEvent, StartupHeapStatisticsClassification>('startupHeapStatistics', {227heapUsed: used,228heapGarbage: garbage,229majorGCs,230minorGCs,231gcsDuration: duration232});233}234235private _printStartupHeapStatistics({ used, garbage, majorGCs, minorGCs, duration }: IHeapStatistics) {236const MB = 1024 * 1024;237return `Heap: ${Math.round(used / MB)}MB (used) ${Math.round(garbage / MB)}MB (garbage) ${majorGCs} (MajorGC) ${minorGCs} (MinorGC) ${duration}ms (GC duration)`;238}239}240241242