diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 3f84050a818..fe06a15dfe7 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -12,7 +12,7 @@ import { ILifecycleService, LifecyclePhase, StartupKindToString } from 'vs/workb import { IModeService } from 'vs/editor/common/services/modeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITimerService, IStartupMetrics } from 'vs/workbench/services/timer/browser/timerService'; +import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -116,17 +116,17 @@ class PerfModelContentProvider implements ITextModelContentProvider { private _updateModel(): void { Promise.all([ - this._timerService.startupMetrics, + this._timerService.whenReady(), this._lifecycleService.when(LifecyclePhase.Eventually), this._extensionService.whenInstalledExtensionsRegistered() - ]).then(([metrics]) => { + ]).then(() => { if (this._model && !this._model.isDisposed()) { let stats = LoaderStats.get(); let md = new MarkdownBuilder(); - this._addSummary(md, metrics); + this._addSummary(md); md.blank(); - this._addSummaryTable(md, metrics, stats); + this._addSummaryTable(md, stats); md.blank(); this._addExtensionsTable(md); md.blank(); @@ -142,7 +142,8 @@ class PerfModelContentProvider implements ITextModelContentProvider { } - private _addSummary(md: MarkdownBuilder, metrics: IStartupMetrics): void { + private _addSummary(md: MarkdownBuilder): void { + const metrics = this._timerService.startupMetrics; md.heading(2, 'System Info'); md.li(`${this._productService.nameShort}: ${this._productService.version} (${this._productService.commit || '0000000'})`); md.li(`OS: ${metrics.platform}(${metrics.release})`); @@ -162,8 +163,9 @@ class PerfModelContentProvider implements ITextModelContentProvider { md.li(`Empty Workspace: ${metrics.emptyWorkbench}`); } - private _addSummaryTable(md: MarkdownBuilder, metrics: IStartupMetrics, stats?: LoaderStats): void { + private _addSummaryTable(md: MarkdownBuilder, stats?: LoaderStats): void { + const metrics = this._timerService.startupMetrics; const table: Array> = []; table.push(['start => app.isReady', metrics.timers.ellapsedAppReady, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index 52f82c64dab..47c85ed5304 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -55,10 +55,10 @@ export class StartupTimings implements IWorkbenchContribution { const { sessionId } = await this._telemetryService.getTelemetryInfo(); Promise.all([ - this._timerService.startupMetrics, + this._timerService.whenReady(), timeout(15000), // wait: cached data creation, telemetry sending - ]).then(([startupMetrics]) => { - return promisify(appendFile)(appendTo, `${startupMetrics.ellapsed}\t${this._productService.nameShort}\t${(this._productService.commit || '').slice(0, 10) || '0000000000'}\t${sessionId}\t${standardStartupError === undefined ? 'standard_start' : 'NO_standard_start : ' + standardStartupError}\n`); + ]).then(() => { + return promisify(appendFile)(appendTo, `${this._timerService.startupMetrics.ellapsed}\t${this._productService.nameShort}\t${(this._productService.commit || '').slice(0, 10) || '0000000000'}\t${sessionId}\t${standardStartupError === undefined ? 'standard_start' : 'NO_standard_start : ' + standardStartupError}\n`); }).then(() => { this._nativeHostService.quit(); }).catch(err => { diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index 93d042637f1..4096f477c71 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -14,6 +14,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Barrier } from 'vs/base/common/async'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -326,10 +327,29 @@ export interface IStartupMetrics { export interface ITimerService { readonly _serviceBrand: undefined; - readonly startupMetrics: Promise; + /** + * A promise that resolved when startup timings and perf marks + * are available. This depends on lifecycle phases and extension + * hosts being started. + */ + whenReady(): Promise; + + /** + * Startup metrics. Can ONLY be accessed after `whenReady` has resolved. + */ + readonly startupMetrics: IStartupMetrics; + + /** + * Deliver performance marks from a source, like the main process or extension hosts. + * The source argument acts as an identifier and therefore it must be unique. + */ setPerformanceMarks(source: string, marks: perf.PerformanceMark[]): void; + /** + * Get all currently known performance marks by source. There is no sorting of the + * returned tuples but the marks of a tuple are guaranteed to be sorted by start times. + */ getPerformanceMarks(): [source: string, marks: readonly perf.PerformanceMark[]][]; } @@ -380,8 +400,9 @@ export abstract class AbstractTimerService implements ITimerService { declare readonly _serviceBrand: undefined; - private readonly _startupMetrics: Promise; + private readonly _barrier = new Barrier(); private readonly _marks = new PerfMarks(); + private _startupMetrics?: IStartupMetrics; constructor( @ILifecycleService private readonly _lifecycleService: ILifecycleService, @@ -394,21 +415,31 @@ export abstract class AbstractTimerService implements ITimerService { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { - this._startupMetrics = Promise.all([ + Promise.all([ this._extensionService.whenInstalledExtensionsRegistered(), _lifecycleService.when(LifecyclePhase.Restored) ]).then(() => { - // set perf mark from renderer this.setPerformanceMarks('renderer', perf.getMarks()); - return this._computeStartupMetrics(); }).then(metrics => { + this._startupMetrics = metrics; this._reportStartupTimes(metrics); - return metrics; + this._barrier.open(); }); } + whenReady(): Promise { + return this._barrier.wait(); + } + + get startupMetrics(): IStartupMetrics { + if (!this._startupMetrics) { + throw new Error('illegal state, MUST NOT access startupMetrics before whenReady has resolved'); + } + return this._startupMetrics; + } + setPerformanceMarks(source: string, marks: perf.PerformanceMark[]): void { this._marks.setMarks(source, marks); } @@ -417,10 +448,6 @@ export abstract class AbstractTimerService implements ITimerService { return this._marks.getEntries(); } - get startupMetrics(): Promise { - return this._startupMetrics; - } - private _reportStartupTimes(metrics: IStartupMetrics): void { // report IStartupMetrics as telemetry /* __GDPR__