performance.contribution.ts 10.0 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8
import product from 'vs/platform/node/product';
9
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
10 11
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
12
import { IMessageService } from 'vs/platform/message/common/message';
13
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
14
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
15
import { ITimerService } from 'vs/workbench/services/timer/common/timerService';
16
import { IWindowsService } from 'vs/platform/windows/common/windows';
17
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
18
import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions } from 'vs/workbench/common/contributions';
19
import { Registry } from 'vs/platform/registry/common/platform';
20 21 22 23
import { ReportPerformanceIssueAction } from 'vs/workbench/electron-browser/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { join } from 'path';
import { localize } from 'vs/nls';
24
import { toPromise, filterEvent } from 'vs/base/common/event';
25
import { platform, Platform } from 'vs/base/common/platform';
J
Johannes Rieken 已提交
26
import { readdir, stat } from 'vs/base/node/pfs';
27
import { release } from 'os';
28 29
import { stopProfiling } from 'vs/base/node/profiler';
import { virtualMachineHint } from 'vs/base/node/id';
J
Johannes Rieken 已提交
30 31
import { forEach } from 'vs/base/common/collections';
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
32 33 34

class ProfilingHint implements IWorkbenchContribution {

35
	// p95 to p95 by os&release
J
Johannes Rieken 已提交
36
	static readonly _percentiles: { [key: string]: [number, number] } = {
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
		['Windows_6.3.9600']: [35782, 35782],
		['Windows_6.1.7601']: [11160, 18366],
		['Windows_10.0.16199']: [10423, 17222],
		['Windows_10.0.16193']: [7503, 11033],
		['Windows_10.0.16188']: [8544, 8807],
		['Windows_10.0.15063']: [11085, 16837],
		['Windows_10.0.14393']: [12585, 32662],
		['Windows_10.0.10586']: [7047, 10944],
		['Windows_10.0.10240']: [16176, 16176],
		['Mac_16.7.0']: [2192, 4050],
		['Mac_16.6.0']: [8043, 10608],
		['Mac_16.5.0']: [4912, 11348],
		['Mac_16.4.0']: [3900, 4200],
		['Mac_16.3.0']: [7327, 7327],
		['Mac_16.1.0']: [6090, 6555],
		['Mac_16.0.0']: [32574, 32574],
		['Mac_15.6.0']: [16082, 17469],
		['Linux_4.9.0-3-amd64']: [2092, 2197],
		['Linux_4.9.0-2-amd64']: [9779, 9779],
		['Linux_4.8.0-52-generic']: [12803, 13257],
		['Linux_4.8.0-51-generic']: [2670, 2797],
		['Linux_4.8.0-040800-generic']: [3954, 3954],
		['Linux_4.4.0-78-generic']: [4218, 5891],
		['Linux_4.4.0-77-generic']: [6166, 6166],
		['Linux_4.11.2']: [1323, 1323],
		['Linux_4.10.15-200.fc25.x86_64']: [9270, 9480],
		['Linux_4.10.13-1-ARCH']: [7116, 8511],
		['Linux_4.10.11-100.fc24.x86_64']: [1845, 1845],
		['Linux_4.10.0-21-generic']: [14805, 16050],
		['Linux_3.19.0-84-generic']: [4840, 4840],
		['Linux_3.11.10-29-desktop']: [1637, 2891],
J
Johannes Rieken 已提交
68 69 70
	};

	private static readonly _myPercentiles = ProfilingHint._percentiles[`${Platform[platform]}_${release()}`];
71 72

	constructor(
73 74 75 76 77 78
		@IWindowsService private readonly _windowsService: IWindowsService,
		@ITimerService private readonly _timerService: ITimerService,
		@IMessageService private readonly _messageService: IMessageService,
		@IEnvironmentService private readonly _envService: IEnvironmentService,
		@IStorageService private readonly _storageService: IStorageService,
		@ITelemetryService private readonly _telemetryService: ITelemetryService,
79
	) {
80

J
Johannes Rieken 已提交
81
		setTimeout(() => this._checkTimersAndSuggestToProfile(), 5000);
82 83 84
	}

	getId(): string {
85
		return 'performance.ProfilingHint';
86 87
	}

88 89
	private _checkTimersAndSuggestToProfile() {

J
Johannes Rieken 已提交
90 91
		// Only initial startups, not when already profiling
		if (!this._timerService.isInitialStartup || this._envService.args['prof-startup']) {
92 93
			return;
		}
94

J
Johannes Rieken 已提交
95
		// Check that we have some data about this
96
		// OS version to which we can compare this startup.
97 98
		// Then only go for startups between the 90 and
		// 95th percentile.
J
Johannes Rieken 已提交
99
		if (!Array.isArray(ProfilingHint._myPercentiles)) {
100 101
			return;
		}
J
Johannes Rieken 已提交
102
		const [p80, p90] = ProfilingHint._myPercentiles;
103
		const { ellapsed } = this._timerService.startupMetrics;
J
Johannes Rieken 已提交
104
		if (ellapsed < p80 || ellapsed > p90) {
105 106 107
			return;
		}

J
Johannes Rieken 已提交
108
		// Ignore virtual machines and only ask users
109
		// to profile with a certain propability
110
		if (virtualMachineHint.value() >= .5 || Math.ceil(Math.random() * 1000) !== 1) {
111 112 113
			return;
		}

J
Johannes Rieken 已提交
114
		// Don't ask for the stable version, only
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
		// ask once per version/build
		if (this._envService.appQuality === 'stable') {
			// don't ask in stable
			return;
		}
		const mementoKey = `performance.didPromptToProfile.${product.commit}`;
		const value = this._storageService.get(mementoKey, StorageScope.GLOBAL, undefined);
		if (value !== undefined) {
			// only ask once per version
			return;
		}

		const profile = this._messageService.confirm({
			type: 'info',
			message: localize('slow', "Slow startup detected"),
			detail: localize('slow.detail', "Sorry that you just had a slow startup. Please restart '{0}' with profiling enabled, share the profiles with us, and we will work hard to make startup great again.", this._envService.appNameLong),
			primaryButton: 'Restart and profile'
		});

134 135 136 137
		this._telemetryService.publicLog('profileStartupInvite', {
			acceptedInvite: profile
		});

138 139 140 141 142
		if (profile) {
			this._storageService.store(mementoKey, 'didProfile', StorageScope.GLOBAL);
			this._windowsService.relaunch({ addArgs: ['--prof-startup'] });
		} else {
			this._storageService.store(mementoKey, 'didReject', StorageScope.GLOBAL);
143 144
		}
	}
145 146
}

147 148 149 150 151 152 153
class StartupProfiler implements IWorkbenchContribution {

	constructor(
		@IWindowsService private readonly _windowsService: IWindowsService,
		@IMessageService private readonly _messageService: IMessageService,
		@IEnvironmentService private readonly _environmentService: IEnvironmentService,
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
154
		@ILifecycleService lifecycleService: ILifecycleService,
155 156
		@IExtensionService extensionService: IExtensionService,
	) {
157 158 159 160 161 162 163
		// wait for everything to be ready
		TPromise.join<any>([
			extensionService.onReady(),
			toPromise(filterEvent(lifecycleService.onDidChangePhase, phase => phase === LifecyclePhase.Running)),
		]).then(() => {
			this._stopProfiling();
		});
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
	}

	getId(): string {
		return 'performance.StartupProfiler';
	}

	private _stopProfiling(): void {

		const { profileStartup } = this._environmentService;
		if (!profileStartup) {
			return;
		}

		stopProfiling(profileStartup.dir, profileStartup.prefix).then(() => {
			readdir(profileStartup.dir).then(files => {
				return files.filter(value => value.indexOf(profileStartup.prefix) === 0);
			}).then(files => {
				const profileFiles = files.reduce((prev, cur) => `${prev}${join(profileStartup.dir, cur)}\n`, '\n');

				const primaryButton = this._messageService.confirm({
					type: 'info',
					message: localize('prof.message', "Successfully created profiles."),
					detail: localize('prof.detail', "Please create an issue and manually attach the following files:\n{0}", profileFiles),
					primaryButton: localize('prof.restartAndFileIssue', "Create Issue and Restart"),
					secondaryButton: localize('prof.restart', "Restart")
				});

				if (primaryButton) {
					const action = this._instantiationService.createInstance(ReportPerformanceIssueAction, ReportPerformanceIssueAction.ID, ReportPerformanceIssueAction.LABEL);
193 194
					TPromise.join<any>([
						this._windowsService.showItemInFolder(join(profileStartup.dir, files[0])),
J
Johannes Rieken 已提交
195
						action.run(`:warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')}`)
196 197 198 199 200 201 202 203 204 205 206
					]).then(() => {
						// keep window stable until restart is selected
						this._messageService.confirm({
							type: 'info',
							message: localize('prof.thanks', "Thanks for helping us."),
							detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._environmentService.appNameLong),
							primaryButton: localize('prof.restart', "Restart"),
							secondaryButton: null
						});
						// now we are ready to restart
						this._windowsService.relaunch({ removeArgs: ['--prof-startup'] });
207
					});
208 209 210 211

				} else {
					// simply restart
					this._windowsService.relaunch({ removeArgs: ['--prof-startup'] });
212 213 214 215 216 217
				}
			});
		});
	}
}

218 219 220 221 222 223 224 225 226 227
class PerformanceTelemetry implements IWorkbenchContribution {

	constructor(
		@ITimerService private readonly _timerService: ITimerService,
		@ITelemetryService private readonly _telemetryService: ITelemetryService,
		@IExtensionService extensionService: IExtensionService,
	) {
		TPromise.join<any>([
			TPromise.timeout(7 * 1000),
			extensionService.onReady()
J
Johannes Rieken 已提交
228 229 230 231
		]).then(() => {
			this._sendWorkbenchMainSizeTelemetry();
			this._validateTimers();
		});
232 233 234 235 236 237 238 239 240 241 242 243 244 245
	}

	getId(): string {
		return 'performance.PerformanceTelemetry';
	}

	private _validateTimers(): void {
		const { startupMetrics } = this._timerService;
		const invalidTimers: string[] = [];
		forEach(startupMetrics.timers, (entry) => {
			if (entry.value < 0) {
				invalidTimers.push(entry.key);
			}
		});
J
Johannes Rieken 已提交
246 247 248 249
		this._telemetryService.publicLog('perf:invalidTimers', { invalidTimers });
	}

	private _sendWorkbenchMainSizeTelemetry(): void {
250
		const { fsPath } = URI.parse(require.toUrl('vs/workbench/workbench.main.js'));
J
Johannes Rieken 已提交
251 252 253
		stat(fsPath).then(stats => {
			this._telemetryService.publicLog('perf:jsFileSize', { workbenchMain: stats.size });
		});
254 255 256
	}
}

257
const registry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
J
Johannes Rieken 已提交
258
registry.registerWorkbenchContribution(ProfilingHint);
259
registry.registerWorkbenchContribution(StartupProfiler);
260
registry.registerWorkbenchContribution(PerformanceTelemetry);