diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8cad5519515..1c5d35f748f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -399,6 +399,11 @@ "from": "util-deprecate@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" }, + "v8-profiler": { + "version": "5.6.5", + "from": "v8-profiler@5.6.5", + "resolved": "git://github.com/jrieken/v8-profiler.git#bc0803a4d4b2150b8a1bbffa80270769007036c2" + }, "vscode-debugprotocol": { "version": "1.17.0", "from": "vscode-debugprotocol@1.17.0", diff --git a/package.json b/package.json index 0d9fa60972f..1379955f74a 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "native-keymap": "0.4.0", "node-pty": "0.6.2", "semver": "4.3.6", + "v8-profiler": "git://github.com/jrieken/v8-profiler.git#bc0803a4d4b2150b8a1bbffa80270769007036c2", "vscode-debugprotocol": "1.17.0", "vscode-textmate": "^3.1.1", "winreg": "1.2.0", diff --git a/src/main.js b/src/main.js index 1ddbaba960f..76ba42c53ce 100644 --- a/src/main.js +++ b/src/main.js @@ -5,6 +5,11 @@ 'use strict'; +if (process.argv.indexOf('--performance-startup-profile') >= 0) { + var profiler = require('v8-profiler'); + profiler.startProfiling('startup-main', true); +} + // Perf measurements global.perfStartTime = Date.now(); diff --git a/src/vs/base/node/profiler.ts b/src/vs/base/node/profiler.ts new file mode 100644 index 00000000000..cbd744bbfe8 --- /dev/null +++ b/src/vs/base/node/profiler.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { homedir } from 'os'; +import { join } from 'path'; +import { writeFile } from 'vs/base/node/pfs'; + +export function startProfiling(name: string): TPromise { + return lazyV8Profiler.value.then(profiler => { + profiler.startProfiling(name); + return true; + }); +} + +export function stopProfiling(name: string): TPromise { + return lazyV8Profiler.value.then(profiler => { + return profiler.stopProfiling(); + }).then(profile => { + return new TPromise((resolve, reject) => { + profile.export(function (error, result) { + profile.delete(); + if (error) { + reject(error); + return; + } + const filepath = join(homedir(), `${name}-${Date.now()}.cpuprofile`); + writeFile(filepath, result).then(() => resolve(filepath), reject); + }); + }); + }); +} + +declare interface Profiler { + startProfiling(name: string); + stopProfiling(): Profile; +} + +declare interface Profile { + export(callback: (err, data) => void); + delete(); +} + +const lazyV8Profiler = new class { + private _value: TPromise; + get value() { + if (!this._value) { + this._value = new TPromise((resolve, reject) => { + require(['v8-profiler'], resolve, reject); + }); + } + return this._value; + } +}; diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 26b26875743..33db968e2f0 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import * as platform from 'vs/base/common/platform'; import * as objects from 'vs/base/common/objects'; +import { stopProfiling } from 'vs/base/node/profiler'; import nls = require('vs/nls'); import { IStorageService } from 'vs/code/electron-main/storage'; import { shell, screen, BrowserWindow, systemPreferences, app } from 'electron'; @@ -466,6 +467,10 @@ export class VSCodeWindow { } }, 10000); } + + if (this.environmentService.args['performance-startup-profile']) { + stopProfiling('startup-main').then(path => console.log(`cpu profile stored in ${path}`), err => console.error(err)); + } } public reload(cli?: ParsedArgs): void { diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 9a3509e0c54..d61fd525bb9 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -22,6 +22,7 @@ export interface ParsedArgs { locale?: string; 'user-data-dir'?: string; performance?: boolean; + ['performance-startup-profile']?: boolean; verbose?: boolean; logExtensionHostCommunication?: boolean; 'disable-extensions'?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 89ccbfc3a53..b175e5b9298 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -34,6 +34,7 @@ const options: minimist.Opts = { 'unity-launch', 'reuse-window', 'performance', + 'performance-startup-profile', 'verbose', 'logExtensionHostCommunication', 'disable-extensions', @@ -112,6 +113,7 @@ export const optionsHelp: { [name: string]: string; } = { '--locale ': localize('locale', "The locale to use (e.g. en-US or zh-TW)."), '-n, --new-window': localize('newWindow', "Force a new instance of Code."), '-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."), + '--performance-startup-profile': localize('performance-startup-profile', "Run CPU profiler during startup"), '-r, --reuse-window': localize('reuseWindow', "Force opening a file or folder in the last active window."), '--user-data-dir ': localize('userDataDir', "Specifies the directory that user data is kept in, useful when running as root."), '--verbose': localize('verbose', "Print verbose output (implies --wait)."), diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 558cc7d6ce7..c7be3db6a2d 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -7,6 +7,11 @@ 'use strict'; +if (window.location.search.indexOf('performance-startup-profile') >= 0) { + var profiler = require('v8-profiler'); + profiler.startProfiling('startup-renderer', true); +} + /*global window,document,define*/ const path = require('path'); diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index d0059f19c63..f79a3265731 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -16,6 +16,7 @@ import aria = require('vs/base/browser/ui/aria/aria'); import { dispose, IDisposable, Disposables } from 'vs/base/common/lifecycle'; import errors = require('vs/base/common/errors'); import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { stopProfiling } from 'vs/base/node/profiler'; import product from 'vs/platform/node/product'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import pkg from 'vs/platform/node/package'; @@ -123,6 +124,7 @@ export class WorkbenchShell { private contextService: IWorkspaceContextService; private telemetryService: ITelemetryService; private extensionService: MainProcessExtensionService; + private windowsService: IWindowsService; private windowIPCService: IWindowIPCService; private timerService: ITimerService; @@ -230,6 +232,25 @@ export class WorkbenchShell { if ((platform.isLinux || platform.isMacintosh) && process.getuid() === 0) { this.messageService.show(Severity.Warning, nls.localize('runningAsRoot', "It is recommended not to run Code as 'root'.")); } + + // Profiler: startup cpu profile + if (this.environmentService.args['performance-startup-profile']) { + stopProfiling('startup-renderer').then(path => { + console.log(`cpu profile stored in ${path}`); + + const restart = this.messageService.confirm({ + type: 'info', + message: nls.localize('prof.message', "Successfully created profiles."), + detail: nls.localize('prof.detail', "To not lose unsaved work, we strongly recommended to restart '{0}' now.", this.environmentService.appNameLong), + primaryButton: nls.localize('prof.restart', "&&Restart") + }); + + if (restart) { + this.windowsService.relaunch({ removeArgs: ['--performance-startup-profile'] }); + } + + }, err => console.error(err)); + } } private initServiceCollection(container: HTMLElement): [IInstantiationService, ServiceCollection] { @@ -251,7 +272,8 @@ export class WorkbenchShell { disposables.add(mainProcessClient); const windowsChannel = mainProcessClient.getChannel('windows'); - serviceCollection.set(IWindowsService, new SyncDescriptor(WindowsChannelClient, windowsChannel)); + this.windowsService = new WindowsChannelClient(windowsChannel); + serviceCollection.set(IWindowsService, this.windowsService); serviceCollection.set(IWindowService, new SyncDescriptor(WindowService, this.windowIPCService.getWindowId()));