Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code --ps #39302

Merged
merged 15 commits into from
Nov 29, 2017
167 changes: 167 additions & 0 deletions src/vs/base/node/ps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* 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 { spawn, exec } from 'child_process';

export interface ProcessItem {
name: string;
cmd: string;
pid: number;
ppid: number;
load: number;
mem: number;

children?: ProcessItem[];
}

export function listProcesses(rootPid: number): Promise<ProcessItem> {

return new Promise((resolve, reject) => {

let rootItem: ProcessItem;
const map = new Map<number, ProcessItem>();

function addToTree(pid: number, ppid: number, cmd: string, load: number, mem: number) {

const parent = map.get(ppid);
if (pid === rootPid || parent) {

const item: ProcessItem = {
name: findName(cmd),
cmd,
pid,
ppid,
load,
mem
};
map.set(pid, item);

if (pid === rootPid) {
rootItem = item;
}

if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(item);
if (parent.children.length > 1) {
parent.children = parent.children.sort((a, b) => a.pid - b.pid);
}
}
}
}

function findName(cmd: string): string {

const RENDERER_PROCESS_HINT = /--disable-blink-features=Auxclick/;
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper.exe/;
const TYPE = /--type=([a-zA-Z-]+)/;

// find windows file watcher
if (WINDOWS_WATCHER_HINT.exec(cmd)) {
return 'watcherService';
}

// find "--type=xxxx"
let matches = TYPE.exec(cmd);
if (matches && matches.length === 2) {
if (matches[1] === 'renderer') {
if (!RENDERER_PROCESS_HINT.exec(cmd)) {
return 'shared-process';
} else {
const RID = /--renderer-client-id=([0-9]+)/;
matches = RID.exec(cmd);
if (matches && matches.length === 2) {
return `renderer-${matches[1]}`;
}
}
}
return matches[1];
}

// find all xxxx.js
const JS = /[a-zA-Z-]+\.js/g;
let result = '';
do {
matches = JS.exec(cmd);
if (matches) {
result += matches + ' ';
}
} while (matches);

if (result) {
if (cmd.indexOf('node ') !== 0) {
return `electron_node ${result}`;
}
}
return cmd;
}

if (process.platform === 'win32') {

const CMD = 'wmic process get ProcessId,ParentProcessId,CommandLine \n';
const CMD_PID = /^(.+)\s+([0-9]+)\s+([0-9]+)$/;

let stdout = '';
let stderr = '';

const cmd = spawn('cmd');

cmd.stdout.on('data', data => {
stdout += data.toString();
});
cmd.stderr.on('data', data => {
stderr += data.toString();
});

cmd.on('exit', () => {

if (stderr.length > 0) {
reject(stderr);
} else {

const lines = stdout.split('\r\n');
for (const line of lines) {
let matches = CMD_PID.exec(line.trim());
if (matches && matches.length === 4) {
addToTree(parseInt(matches[3]), parseInt(matches[2]), matches[1].trim(), 0.0, 0.0);
}
}

resolve(rootItem);
}
});

cmd.stdin.write(CMD);
cmd.stdin.end();

} else { // OS X & Linux

const CMD = 'ps -ax -o pid=,ppid=,pcpu=,pmem=,command=';
const PID_CMD = /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)\s+(.+)$/;

exec(CMD, { maxBuffer: 1000 * 1024 }, (err, stdout, stderr) => {

if (err || stderr) {
reject(err || stderr.toString());
} else {

const lines = stdout.toString().split('\n');
for (const line of lines) {
let matches = PID_CMD.exec(line.trim());
if (matches && matches.length === 6) {
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
}
}

resolve(rootItem);
}
});
}
});
}
41 changes: 39 additions & 2 deletions src/vs/code/electron-main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/work
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { localize } from 'vs/nls';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { listProcesses, ProcessItem } from 'vs/base/node/ps';
import { repeat } from 'vs/base/common/strings';

function createServices(args: ParsedArgs): IInstantiationService {
const services = new ServiceCollection();
Expand Down Expand Up @@ -101,6 +103,11 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
app.dock.show(); // dock might be hidden at this case due to a retry
}

// Print --ps usage info
if (environmentService.args.ps) {
console.log('Warning: The --ps argument can only be used if Code is already running. Please run it again after Code has started.');
}

return server;
}, err => {
if (err.code !== 'EADDRINUSE') {
Expand All @@ -125,8 +132,6 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
return TPromise.wrapError<Server>(new Error(msg));
}

logService.info('Sending env to running instance...');

// Show a warning dialog after some timeout if it takes long to talk to the other instance
// Skip this if we are running with --wait where it is expected that we wait for a while
let startupWarningDialogHandle: number;
Expand All @@ -142,6 +147,21 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
const channel = client.getChannel<ILaunchChannel>('launch');
const service = new LaunchChannelClient(channel);

// Process Info
if (environmentService.args.ps) {
return service.getMainProcessId().then(mainProcessPid => {
return listProcesses(mainProcessPid).then(rootProcess => {
const output: string[] = [];
formatProcess(output, rootProcess, 0);
console.log(output.join('\n'));

return TPromise.wrapError(new ExpectedError());
});
});
}

logService.info('Sending env to running instance...');

return allowSetForegroundWindow(service)
.then(() => service.start(environmentService.args, process.env))
.then(() => client.dispose())
Expand Down Expand Up @@ -186,6 +206,23 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
return setup(true);
}

function formatProcess(output: string[], item: ProcessItem, indent: number): void {

// Format name with indent
let name: string;
if (indent === 0) {
name = `${product.applicationName} main`;
} else {
name = `${repeat(' ', indent)} ${item.name}`;
}
output.push(name);

// Recurse into children if any
if (Array.isArray(item.children)) {
item.children.forEach(child => formatProcess(output, child, indent + 1));
}
}

function showStartupWarningDialog(message: string, detail: string): void {
dialog.showMessageBox(null, {
title: product.nameLong,
Expand Down
28 changes: 20 additions & 8 deletions src/vs/code/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,24 @@ export async function main(argv: string[]): TPromise<any> {
return TPromise.as(null);
}

// Help
if (args.help) {
console.log(buildHelpMessage(product.nameLong, product.applicationName, pkg.version));
} else if (args.version) {
}

// Version Info
else if (args.version) {
console.log(`${pkg.version}\n${product.commit}\n${process.arch}`);
} else if (shouldSpawnCliProcess(args)) {
}

// Extensions Management
else if (shouldSpawnCliProcess(args)) {
const mainCli = new TPromise<IMainCli>(c => require(['vs/code/node/cliProcessMain'], c));
return mainCli.then(cli => cli.main(args));
} else {
}

// Just Code
else {
const env = assign({}, process.env, {
// this will signal Code that it was spawned from this module
'VSCODE_CLI': '1',
Expand All @@ -55,7 +65,9 @@ export async function main(argv: string[]): TPromise<any> {

let processCallbacks: ((child: ChildProcess) => Thenable<any>)[] = [];

if (args.verbose) {
const verbose = args.verbose || args.ps;

if (verbose) {
env['ELECTRON_ENABLE_LOGGING'] = '1';

processCallbacks.push(child => {
Expand All @@ -68,7 +80,7 @@ export async function main(argv: string[]): TPromise<any> {

// If we are running with input from stdin, pipe that into a file and
// open this file via arguments. Ignore this when we are passed with
// paths to open.
// paths to open.
let isReadingFromStdin: boolean;
try {
isReadingFromStdin = args._.length === 0 && !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304
Expand Down Expand Up @@ -97,7 +109,7 @@ export async function main(argv: string[]): TPromise<any> {
stdinFileError = error;
}

if (args.verbose) {
if (verbose) {
if (stdinFileError) {
console.error(`Failed to create file to read via stdin: ${stdinFileError.toString()}`);
} else {
Expand All @@ -122,7 +134,7 @@ export async function main(argv: string[]): TPromise<any> {
waitMarkerError = error;
}

if (args.verbose) {
if (verbose) {
if (waitMarkerError) {
console.error(`Failed to create marker file for --wait: ${waitMarkerError.toString()}`);
} else {
Expand Down Expand Up @@ -195,7 +207,7 @@ export async function main(argv: string[]): TPromise<any> {
env
};

if (!args.verbose) {
if (!verbose) {
options['stdio'] = 'ignore';
}

Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/environment/common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface ParsedArgs {
'disable-updates'?: string;
'disable-crash-reporter'?: string;
'skip-add-to-recently-opened'?: boolean;
'ps'?: boolean;
}

export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/environment/node/argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const options: minimist.Opts = {
'disable-telemetry',
'disable-updates',
'disable-crash-reporter',
'skip-add-to-recently-opened'
'skip-add-to-recently-opened',
'ps'
],
alias: {
add: 'a',
Expand Down Expand Up @@ -146,6 +147,7 @@ export const optionsHelp: { [name: string]: string; } = {
'--enable-proposed-api <extension-id>': localize('experimentalApis', "Enables proposed api features for an extension."),
'--disable-extensions': localize('disableExtensions', "Disable all installed extensions."),
'--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration."),
'--ps': localize('ps', "Print process usage and diagnostics information."),
'-v, --version': localize('version', "Print version."),
'-h, --help': localize('help', "Print usage.")
};
Expand Down