提交 52c2843c 编写于 作者: J Johannes Rieken

redo `--prof-startup` with the new protocol based profiler, also profile extension host startup

上级 38311adc
......@@ -302,7 +302,6 @@ function packageTask(platform, arch, opts) {
.pipe(util.cleanNodeModule('windows-foreground-love', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('windows-process-tree', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('gc-signals', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node', 'src/index.js']))
.pipe(util.cleanNodeModule('v8-profiler', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node', 'src/index.js']))
.pipe(util.cleanNodeModule('keytar', ['binding.gyp', 'build/**', 'src/**', 'script/**', 'node_modules/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('node-pty', ['binding.gyp', 'build/**', 'src/**', 'tools/**'], ['build/Release/**']))
.pipe(util.cleanNodeModule('nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['**/*.node', '**/*.a']))
......
......@@ -5,13 +5,6 @@
'use strict';
if (process.argv.indexOf('--prof-startup') >= 0) {
var profiler = require('v8-profiler');
var prefix = require('crypto').randomBytes(2).toString('hex');
process.env.VSCODE_PROFILES_PREFIX = prefix;
profiler.startProfiling('main', true);
}
var perf = require('./vs/base/common/performance');
perf.mark('main:started');
......@@ -122,6 +115,10 @@ function getNLSConfiguration() {
}
function getNodeCachedDataDir() {
// flag to disable cached data support
if (process.argv.indexOf('--no-cached-data') > 0) {
return Promise.resolve(undefined);
}
// IEnvironmentService.isBuilt
if (process.env['VSCODE_DEV']) {
......
declare module 'v8-inspect-profiler' {
export interface Profile { }
export interface ProfilingSession {
stop(afterDelay?: number): PromiseLike<Profile>;
}
export function startProfiling(options: { port: number, tries?: number, retyWait?: number }): PromiseLike<ProfilingSession>;
export function writeProfile(profile: Profile, name?: string): PromiseLike<void>;
export function rewriteAbsolutePaths(profile, replaceWith?);
}
......@@ -11,24 +11,24 @@ import net = require('net');
* Given a start point and a max number of retries, will find a port that
* is openable. Will return 0 in case no free port can be found.
*/
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number, clb: (port: number) => void): void {
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number): Thenable<number> {
let done = false;
const timeoutHandle = setTimeout(() => {
if (!done) {
done = true;
return clb(0);
}
}, timeout);
doFindFreePort(startPort, giveUpAfter, (port) => {
if (!done) {
done = true;
clearTimeout(timeoutHandle);
return clb(port);
}
return new Promise(resolve => {
const timeoutHandle = setTimeout(() => {
if (!done) {
done = true;
return resolve(0);
}
}, timeout);
doFindFreePort(startPort, giveUpAfter, (port) => {
if (!done) {
done = true;
clearTimeout(timeoutHandle);
return resolve(port);
}
});
});
}
......
/*---------------------------------------------------------------------------------------------
* 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 { join, basename } from 'path';
import { writeFile } from 'vs/base/node/pfs';
export function startProfiling(name: string): TPromise<boolean> {
return lazyV8Profiler.value.then(profiler => {
profiler.startProfiling(name);
return true;
});
}
const _isRunningOutOfDev = process.env['VSCODE_DEV'];
export function stopProfiling(dir: string, prefix: string): TPromise<string> {
return lazyV8Profiler.value.then(profiler => {
return profiler.stopProfiling();
}).then(profile => {
return new TPromise<any>((resolve, reject) => {
// remove pii paths
if (!_isRunningOutOfDev) {
removePiiPaths(profile); // remove pii from our users
}
profile.export(function (error, result) {
profile.delete();
if (error) {
reject(error);
return;
}
let filepath = join(dir, `${prefix}_${profile.title}.cpuprofile`);
if (!_isRunningOutOfDev) {
filepath += '.txt'; // github issues must be: txt, zip, png, gif
}
writeFile(filepath, result).then(() => resolve(filepath), reject);
});
});
});
}
export function removePiiPaths(profile: Profile) {
const stack = [profile.head];
while (stack.length > 0) {
const element = stack.pop();
if (element.url) {
const shortUrl = basename(element.url);
if (element.url !== shortUrl) {
element.url = `pii_removed/${shortUrl}`;
}
}
if (element.children) {
stack.push(...element.children);
}
}
}
declare interface Profiler {
startProfiling(name: string): void;
stopProfiling(): Profile;
}
export declare interface Profile {
title: string;
export(callback: (err, data) => void): void;
delete(): void;
head: ProfileSample;
}
export declare interface ProfileSample {
// bailoutReason:""
// callUID:2333
// children:Array[39]
// functionName:"(root)"
// hitCount:0
// id:1
// lineNumber:0
// scriptId:0
// url:""
url: string;
children: ProfileSample[];
}
const lazyV8Profiler = new class {
private _value: TPromise<Profiler>;
get value() {
if (!this._value) {
this._value = new TPromise<Profiler>((resolve, reject) => {
require(['v8-profiler'], resolve, reject);
});
}
return this._value;
}
};
......@@ -18,7 +18,7 @@ suite('Ports', () => {
}
// get an initial freeport >= 7000
ports.findFreePort(7000, 100, 300000, (initialPort) => {
ports.findFreePort(7000, 100, 300000).then(initialPort => {
assert.ok(initialPort >= 7000);
// create a server to block this port
......@@ -26,7 +26,7 @@ suite('Ports', () => {
server.listen(initialPort, null, null, () => {
// once listening, find another free port and assert that the port is different from the opened one
ports.findFreePort(7000, 50, 300000, (freePort) => {
ports.findFreePort(7000, 50, 300000).then(freePort => {
assert.ok(freePort >= 7000 && freePort !== initialPort);
server.close();
......
......@@ -7,7 +7,6 @@
import * as path from 'path';
import * as objects from 'vs/base/common/objects';
import { stopProfiling } from 'vs/base/node/profiler';
import nls = require('vs/nls');
import URI from 'vs/base/common/uri';
import { IStorageService } from 'vs/platform/storage/node/storage';
......@@ -522,12 +521,6 @@ export class CodeWindow implements ICodeWindow {
}
}, 10000);
}
// (--prof-startup) save profile to disk
const { profileStartup } = this.environmentService;
if (profileStartup) {
stopProfiling(profileStartup.dir, profileStartup.prefix).done(undefined, err => this.logService.error(err));
}
}
public reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { spawn } from 'child_process';
import { spawn, ChildProcess } from 'child_process';
import { TPromise } from 'vs/base/common/winjs.base';
import { assign } from 'vs/base/common/objects';
import { parseCLIProcessArgv, buildHelpMessage } from 'vs/platform/environment/node/argv';
......@@ -15,6 +15,7 @@ import * as paths from 'path';
import * as os from 'os';
import { whenDeleted } from 'vs/base/node/pfs';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import { findFreePort } from 'vs/base/node/ports';
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
return !!argv['install-source']
......@@ -27,7 +28,7 @@ interface IMainCli {
main: (argv: ParsedArgs) => TPromise<void>;
}
export function main(argv: string[]): TPromise<void> {
export async function main(argv: string[]): TPromise<any> {
let args: ParsedArgs;
try {
......@@ -53,8 +54,16 @@ export function main(argv: string[]): TPromise<void> {
delete env['ELECTRON_RUN_AS_NODE'];
let processCallbacks: ((child: ChildProcess) => Thenable<any>)[] = [];
if (args.verbose) {
env['ELECTRON_ENABLE_LOGGING'] = '1';
processCallbacks.push(child => {
child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
return new TPromise<void>(c => child.once('exit', () => c(null)));
});
}
// If we are started with --wait create a random temporary file
......@@ -82,6 +91,50 @@ export function main(argv: string[]): TPromise<void> {
}
}
// If we have been started with `--prof-startup` we need to find free ports to profile
// the main process, the renderer, and the extension host. We also disable v8 cached data
// to get better profile traces. Last, we listen on stdout for a signal that tells us to
// stop profiling.
if (args['prof-startup']) {
const portMain = await findFreePort(9222, 10, 6000);
const portRenderer = await findFreePort(portMain + 1, 10, 6000);
const portExthost = await findFreePort(portRenderer + 1, 10, 6000);
if (!portMain || !portRenderer || !portExthost) {
console.error('Failed to find free ports for profiler to connect to do.');
return;
}
const filenamePrefix = paths.join(os.homedir(), Math.random().toString(16).slice(-4));
argv.push(`--inspect-brk=${portMain}`);
argv.push(`--remote-debugging-port=${portRenderer}`);
argv.push(`--inspect-brk-extensions=${portExthost}`);
argv.push(`--prof-startup-prefix`, filenamePrefix);
argv.push(`--no-cached-data`);
writeFileAndFlushSync(filenamePrefix, argv.slice(-6).join('|'));
processCallbacks.push(async child => {
// load and start profiler
const profiler = await import('v8-inspect-profiler');
const main = await profiler.startProfiling({ port: portMain });
const renderer = await profiler.startProfiling({ port: portRenderer, tries: 200 });
const extHost = await profiler.startProfiling({ port: portExthost, tries: 300 });
// wait for the renderer to delete the
// marker file
whenDeleted(filenamePrefix);
// finally stop profiling and save profiles to disk
await profiler.writeProfile(await main.stop(), `${filenamePrefix}-main.cpuprofile`);
await profiler.writeProfile(await renderer.stop(), `${filenamePrefix}-renderer.cpuprofile`);
await profiler.writeProfile(await extHost.stop(), `${filenamePrefix}-exthost.cpuprofile`);
});
}
const options = {
detached: true,
env
......@@ -93,15 +146,6 @@ export function main(argv: string[]): TPromise<void> {
const child = spawn(process.execPath, argv.slice(2), options);
if (args.verbose) {
child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
}
if (args.verbose) {
return new TPromise<void>(c => child.once('exit', () => c(null)));
}
if (args.wait && waitMarkerFilePath) {
return new TPromise<void>(c => {
......@@ -112,6 +156,8 @@ export function main(argv: string[]): TPromise<void> {
whenDeleted(waitMarkerFilePath).done(c, c);
});
}
return TPromise.join(processCallbacks.map(callback => callback(child)));
}
return TPromise.as(null);
......
......@@ -22,6 +22,7 @@ export interface ParsedArgs {
'user-data-dir'?: string;
performance?: boolean;
'prof-startup'?: string;
'prof-startup-prefix'?: string;
verbose?: boolean;
logExtensionHostCommunication?: boolean;
'disable-extensions'?: boolean;
......@@ -100,7 +101,6 @@ export interface IEnvironmentService {
verbose: boolean;
wait: boolean;
performance: boolean;
profileStartup: { prefix: string, dir: string } | undefined;
skipGettingStarted: boolean | undefined;
......
......@@ -118,18 +118,6 @@ export class EnvironmentService implements IEnvironmentService {
get performance(): boolean { return this._args.performance; }
@memoize
get profileStartup(): { prefix: string, dir: string } | undefined {
if (this._args['prof-startup']) {
return {
prefix: process.env.VSCODE_PROFILES_PREFIX,
dir: os.homedir()
};
} else {
return undefined;
}
}
@memoize
get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); }
......
......@@ -7,11 +7,6 @@
'use strict';
if (window.location.search.indexOf('prof-startup') >= 0) {
var profiler = require('v8-profiler');
profiler.startProfiling('renderer', true);
}
/*global window,document,define,Monaco_Loader_Init*/
const perf = require('../../../base/common/performance');
......
......@@ -15,10 +15,10 @@ import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions } f
import { Registry } from 'vs/platform/registry/common/platform';
import { ReportPerformanceIssueAction } from 'vs/workbench/electron-browser/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { join } from 'path';
import { join, dirname } from 'path';
import { localize } from 'vs/nls';
import { readdir } from 'vs/base/node/pfs';
import { stopProfiling } from 'vs/base/node/profiler';
import { readdir, del, readFile } from 'vs/base/node/pfs';
import { basename } from 'vs/base/common/paths';
class StartupProfiler implements IWorkbenchContribution {
......@@ -31,55 +31,64 @@ class StartupProfiler implements IWorkbenchContribution {
@IExtensionService extensionService: IExtensionService,
) {
// wait for everything to be ready
extensionService.whenInstalledExtensionsRegistered().then(() => {
Promise.all([
lifecycleService.when(LifecyclePhase.Eventually),
extensionService.whenInstalledExtensionsRegistered()
]).then(() => {
this._stopProfiling();
});
}
private _stopProfiling(): void {
const { profileStartup } = this._environmentService;
if (!profileStartup) {
const profileFilenamePrefix = this._environmentService.args['prof-startup-prefix'];
if (!profileFilenamePrefix) {
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 dir = dirname(profileFilenamePrefix);
const prefix = basename(profileFilenamePrefix);
const primaryButton = this._messageService.confirmSync({
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")
});
const removeArgs: string[] = ['--prof-startup'];
const markerFile = readFile(profileFilenamePrefix).then(value => removeArgs.push(...value.toString().split('|')))
.then(() => del(profileFilenamePrefix))
.then(() => TPromise.timeout(1000));
if (primaryButton) {
const action = this._instantiationService.createInstance(ReportPerformanceIssueAction, ReportPerformanceIssueAction.ID, ReportPerformanceIssueAction.LABEL);
TPromise.join<any>([
this._windowsService.showItemInFolder(join(profileStartup.dir, files[0])),
action.run(`:warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')}`)
]).then(() => {
// keep window stable until restart is selected
this._messageService.confirmSync({
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'] });
});
markerFile.then(() => {
return readdir(dir).then(files => files.filter(value => value.indexOf(prefix) === 0));
}).then(files => {
const profileFiles = files.reduce((prev, cur) => `${prev}${join(dir, cur)}\n`, '\n');
} else {
// simply restart
this._windowsService.relaunch({ removeArgs: ['--prof-startup'] });
}
const primaryButton = this._messageService.confirmSync({
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);
TPromise.join<any>([
this._windowsService.showItemInFolder(join(dir, files[0])),
action.run(`:warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')}`)
]).then(() => {
// keep window stable until restart is selected
this._messageService.confirmSync({
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 });
});
} else {
// simply restart
this._windowsService.relaunch({ removeArgs });
}
});
}
}
......
......@@ -265,7 +265,7 @@ export class ExtensionHostProcessWorker {
return TPromise.wrap<number>(0);
}
return new TPromise<number>((c, e) => {
findFreePort(extensionHostPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */, (port) => {
return findFreePort(extensionHostPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */).then(port => {
if (!port) {
console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color: black');
return c(void 0);
......
......@@ -252,6 +252,10 @@ async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
async@1.x, async@^1.4.0, async@^1.5.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
......@@ -580,6 +584,13 @@ cheerio@^1.0.0-rc.1:
lodash "^4.15.0"
parse5 "^3.0.1"
chrome-remote-interface@^0.25.3:
version "0.25.3"
resolved "https://registry.yarnpkg.com/chrome-remote-interface/-/chrome-remote-interface-0.25.3.tgz#b692ae538cd5af3a6dd285636bfab3d29a7006c1"
dependencies:
commander "2.11.x"
ws "3.3.x"
ci-info@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.1.tgz#47b44df118c48d2597b56d342e7e25791060171a"
......@@ -741,6 +752,10 @@ commander@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06"
commander@2.11.x, commander@^2.8.1, commander@^2.9.0, commander@~2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
commander@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
......@@ -751,10 +766,6 @@ commander@2.8.x:
dependencies:
graceful-readlink ">= 1.0.0"
commander@^2.8.1, commander@^2.9.0, commander@~2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
commandpost@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.2.1.tgz#2e9c4c7508b9dc704afefaa91cab92ee6054cc68"
......@@ -3719,7 +3730,7 @@ nan@^2.0.0, nan@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
nan@^2.0.9, nan@^2.3.0, nan@^2.3.2:
nan@^2.0.9, nan@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232"
......@@ -5466,6 +5477,10 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
ultron@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
unc-path-regex@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
......@@ -5559,11 +5574,11 @@ uuid@^3.0.0, uuid@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
v8-profiler@jrieken/v8-profiler#vscode:
version "5.6.5"
resolved "https://codeload.github.com/jrieken/v8-profiler/tar.gz/5e4a336693e1d5b079c7aecd286a1abcfbc10421"
v8-inspect-profiler@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/v8-inspect-profiler/-/v8-inspect-profiler-0.0.6.tgz#7885de7e9c3304118bde25d5b3d6ec5116467f2c"
dependencies:
nan "^2.3.2"
chrome-remote-interface "^0.25.3"
v8flags@^2.0.2:
version "2.1.1"
......@@ -5850,6 +5865,14 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@3.3.x:
version "3.3.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.2.tgz#96c1d08b3fefda1d5c1e33700d3bfaa9be2d5608"
dependencies:
async-limiter "~1.0.0"
safe-buffer "~5.1.0"
ultron "~1.1.0"
xml-name-validator@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-1.0.0.tgz#dcf82ee092322951ef8cc1ba596c9cbfd14a83f1"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册