未验证 提交 307987fe 编写于 作者: B Benjamin Pasero 提交者: GitHub

Add code --ps (#39302)

* initial commit

* 💄

* get the machine id from the main process

* first cut formatting

* nicer output

* added support for Windows

* refactor to share common code

* simplify regexps

* always use the 'type' argument

* differentiate between node and electron_node

* some polish

* add render id to renderer

* add memory load (macOS, linux)

* 💄
上级 90b94baa
/*---------------------------------------------------------------------------------------------
* 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,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();
......@@ -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') {
......@@ -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;
......@@ -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())
......@@ -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,
......
......@@ -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',
......@@ -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 => {
......@@ -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
......@@ -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 {
......@@ -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 {
......@@ -195,7 +207,7 @@ export async function main(argv: string[]): TPromise<any> {
env
};
if (!args.verbose) {
if (!verbose) {
options['stdio'] = 'ignore';
}
......
......@@ -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');
......
......@@ -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',
......@@ -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.")
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册