Path: blob/main/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.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 { localize } from '../../../../nls.js';7import { dirname, basename } from '../../../../base/common/resources.js';8import { ITextModelService } from '../../../../editor/common/services/resolverService.js';9import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';10import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';11import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';12import { PerfviewContrib } from '../browser/perfviewEditor.js';13import { IExtensionService } from '../../../services/extensions/common/extensions.js';14import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';15import { URI } from '../../../../base/common/uri.js';16import { IOpenerService } from '../../../../platform/opener/common/opener.js';17import { INativeHostService } from '../../../../platform/native/common/native.js';18import { IProductService } from '../../../../platform/product/common/productService.js';19import { IFileService } from '../../../../platform/files/common/files.js';20import { ILabelService } from '../../../../platform/label/common/label.js';2122export class StartupProfiler implements IWorkbenchContribution {2324constructor(25@IDialogService private readonly _dialogService: IDialogService,26@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,27@ITextModelService private readonly _textModelResolverService: ITextModelService,28@IClipboardService private readonly _clipboardService: IClipboardService,29@ILifecycleService lifecycleService: ILifecycleService,30@IExtensionService extensionService: IExtensionService,31@IOpenerService private readonly _openerService: IOpenerService,32@INativeHostService private readonly _nativeHostService: INativeHostService,33@IProductService private readonly _productService: IProductService,34@IFileService private readonly _fileService: IFileService,35@ILabelService private readonly _labelService: ILabelService,36) {37// wait for everything to be ready38Promise.all([39lifecycleService.when(LifecyclePhase.Eventually),40extensionService.whenInstalledExtensionsRegistered()41]).then(() => {42this._stopProfiling();43});44}4546private _stopProfiling(): void {4748if (!this._environmentService.args['prof-startup-prefix']) {49return;50}51const profileFilenamePrefix = URI.file(this._environmentService.args['prof-startup-prefix']);5253const dir = dirname(profileFilenamePrefix);54const prefix = basename(profileFilenamePrefix);5556const removeArgs: string[] = ['--prof-startup'];57const markerFile = this._fileService.readFile(profileFilenamePrefix).then(value => removeArgs.push(...value.toString().split('|')))58.then(() => this._fileService.del(profileFilenamePrefix, { recursive: true })) // (1) delete the file to tell the main process to stop profiling59.then(() => new Promise<void>(resolve => { // (2) wait for main that recreates the fail to signal profiling has stopped60const check = () => {61this._fileService.exists(profileFilenamePrefix).then(exists => {62if (exists) {63resolve();64} else {65setTimeout(check, 500);66}67});68};69check();70}))71.then(() => this._fileService.del(profileFilenamePrefix, { recursive: true })); // (3) finally delete the file again7273markerFile.then(() => {74return this._fileService.resolve(dir).then(stat => {75return (stat.children ? stat.children.filter(value => value.resource.path.includes(prefix)) : []).map(stat => stat.resource);76});77}).then(files => {78const profileFiles = files.reduce((prev, cur) => `${prev}${this._labelService.getUriLabel(cur)}\n`, '\n');7980return this._dialogService.confirm({81type: 'info',82message: localize('prof.message', "Successfully created profiles."),83detail: localize('prof.detail', "Please create an issue and manually attach the following files:\n{0}", profileFiles),84primaryButton: localize({ key: 'prof.restartAndFileIssue', comment: ['&& denotes a mnemonic'] }, "&&Create Issue and Restart"),85cancelButton: localize('prof.restart', "Restart")86}).then(res => {87if (res.confirmed) {88Promise.all<any>([89this._nativeHostService.showItemInFolder(files[0].fsPath),90this._createPerfIssue(files.map(file => basename(file)))91]).then(() => {92// keep window stable until restart is selected93return this._dialogService.confirm({94type: 'info',95message: localize('prof.thanks', "Thanks for helping us."),96detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._productService.nameLong),97primaryButton: localize({ key: 'prof.restart.button', comment: ['&& denotes a mnemonic'] }, "&&Restart")98}).then(res => {99// now we are ready to restart100if (res.confirmed) {101this._nativeHostService.relaunch({ removeArgs });102}103});104});105106} else {107// simply restart108this._nativeHostService.relaunch({ removeArgs });109}110});111});112}113114private async _createPerfIssue(files: string[]): Promise<void> {115const reportIssueUrl = this._productService.reportIssueUrl;116if (!reportIssueUrl) {117return;118}119120const contrib = PerfviewContrib.get();121const ref = await this._textModelResolverService.createModelReference(contrib.getInputUri());122try {123await this._clipboardService.writeText(ref.object.textEditorModel.getValue());124} finally {125ref.dispose();126}127128const body = `1291. :warning: We have copied additional data to your clipboard. Make sure to **paste** here. :warning:1301. :warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')}131`;132133const baseUrl = reportIssueUrl;134const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';135136this._openerService.open(URI.parse(`${baseUrl}${queryStringPrefix}body=${encodeURIComponent(body)}`));137}138}139140141