Skip to content

Commit

Permalink
cleanup support for sub-process debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
weinand committed Dec 14, 2017
1 parent 713adac commit 895bee4
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 55 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.0.5
* Add support for attaching to all subprocesses of a process (help with 'Cluster').

## 0.0.2
* The Process View no longer shows by default. Use "Show Process View" action to make it visible.
* Detect the `--inspect`, `--inspect-brk`, `--debug`, or `--debug-brk` flags (with or without port number)
Expand Down
18 changes: 13 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,16 @@
"when": "viewItem == 'node'"
},
{
"command": "extension.vscode-processes.startClusterDebug",
"when": "viewItem == 'node'"
"command": "extension.vscode-processes.startDebug",
"when": "viewItem == 'node-subs'"
},
{
"command": "extension.vscode-processes.startDebugAll",
"when": "viewItem == 'subs'"
},
{
"command": "extension.vscode-processes.startDebugAll",
"when": "viewItem == 'node-subs'"
}
]
},
Expand All @@ -88,11 +96,11 @@
},
{
"command": "extension.vscode-processes.startDebug",
"title": "Start Node Debugging"
"title": "Debug Process"
},
{
"command": "extension.vscode-processes.startClusterDebug",
"title": "Debug Cluster"
"command": "extension.vscode-processes.startDebugAll",
"title": "Debug Sub Processes"
}
]
}
Expand Down
179 changes: 130 additions & 49 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;

let processViewer: ProcessProvider;

type ElementId = string;

export function activate(context: vscode.ExtensionContext) {

context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.showProcessView', () => {
Expand All @@ -32,23 +34,26 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.executeCommand('setContext', 'extension.vscode-processes.processViewerContext', true)
}));

context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.startDebug', (item: ProcessTreeItem) => {
attachTo(item);
context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.startDebug', (elementId: ElementId) => {
attachTo(ProcessTreeItem.find(elementId));
}));

context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.startClusterDebug', (item: ProcessTreeItem) => {
context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.startDebugAll', (elementId: ElementId) => {
const item = ProcessTreeItem.find(elementId);
for (let child of item._children) {
attachTo(child);
}
}));

context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.kill', (item: ProcessTreeItem) => {
context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.kill', (elementId: ElementId) => {
const item = ProcessTreeItem.find(elementId);
if (item._pid) {
process.kill(item._pid, 'SIGTERM');
}
}));

context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.forceKill', (item: ProcessTreeItem) => {
context.subscriptions.push(vscode.commands.registerCommand('extension.vscode-processes.forceKill', (elementId: ElementId) => {
const item = ProcessTreeItem.find(elementId);
if (item._pid) {
process.kill(item._pid, 'SIGKILL');
}
Expand Down Expand Up @@ -78,7 +83,7 @@ function attachTo(item: ProcessTreeItem) {
// no port -> try to attach via pid (send SIGUSR1)
config.processId = String(item._pid);
}

// a debug-port=n or inspect-port=n overrides the port
matches = DEBUG_PORT_PATTERN.exec(item._cmd);
if (matches && matches.length === 3) {
Expand All @@ -90,21 +95,36 @@ function attachTo(item: ProcessTreeItem) {
}

class ProcessTreeItem extends TreeItem {

static _map = new Map<string, ProcessTreeItem>();

_pid: number;
_cmd: string;
_children: ProcessTreeItem[];

constructor() {
static find(id: ElementId): ProcessTreeItem {
return ProcessTreeItem._map.get(id);
}

constructor(pid: number) {
super('', vscode.TreeItemCollapsibleState.None);
this._pid = pid;
ProcessTreeItem._map.set(pid.toString(), this);
}

getChildren(): ProcessTreeItem[] {
return this._children || [];
getId(): ElementId {
return this._pid.toString();
}

merge(process: ProcessItem): ProcessTreeItem | undefined {
getChildIds(): ElementId[] {
return (this._children || []).map(x => x.getId());
}

let changed = false;
/*
* Update this item with the information from the given ProcessItem.
* Returns the elementId of the subtree that needs to be refreshed or undefined if nothing has changed.
*/
merge(process: ProcessItem): ElementId | undefined {

// update item's name
const oldLabel = this.label;
Expand All @@ -114,95 +134,156 @@ class ProcessTreeItem extends TreeItem {
this.label = `[[ ${this.label} ]]`;
}
} else {
this._pid = process.pid;
this._cmd = process.cmd;
this.label = process.load && process.mem ? `${process.name} (${process.load}, ${process.mem})` : process.name;
}
changed = this.label !== oldLabel;
let changed = this.label !== oldLabel;

// enable item's context (for node debug action)
// enable item's context (for debug actions)
const oldContextValue = this.contextValue;
this.contextValue = undefined;
if (process) {
const matches = DEBUG_FLAGS_PATTERN.exec(process.cmd);
if ((matches && matches.length >= 2) || process.cmd.indexOf('node ') >= 0 ||process.cmd.indexOf('node.exe') >= 0) {
this.contextValue = 'node';
}
}
this.contextValue = this.getContextValue();
changed = changed || this.contextValue !== oldContextValue;

// update children
const childChanges: ElementId[] = [];
const nextChildren: ProcessTreeItem[] = [];
if (process) {
process.children = process.children || [];
for (const child of process.children) {
let found = this._children ? this._children.find(c => child.pid === c._pid) : undefined;
if (!found) {
found = new ProcessTreeItem();
found = new ProcessTreeItem(child.pid);
changed = true;
}
if (found.merge(child)) {
changed = true;
const changedChild = found.merge(child);
if (changedChild) {
childChanges.push(changedChild);
}
nextChildren.push(found);
}

if (KEEP_TERMINATED && this._children) {
if (this._children) {
for (const child of this._children) {
const found = process.children.find(c => child._pid === c.pid);
if (!found) {
child.merge(null);
nextChildren.push(child);
changed = true;
if (KEEP_TERMINATED) {
child.merge(null);
nextChildren.push(child);
}
}
}
}
}
this._children = nextChildren;

// update collapsible state
const oldCollapsibleState = this.collapsibleState;
// custom explorer bug: https://github.com/Microsoft/vscode/issues/40179
this.collapsibleState = this._children.length > 0 ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None;
if (this.collapsibleState !== oldCollapsibleState) {
changed = true;
}

// attribute changes or changes in more than one child
if (changed || childChanges.length > 1) {
return this.getId();
}

// changes only in one child -> propagate that child for refresh
if (childChanges.length === 1) {
return childChanges[0];
}

// no changes
return undefined;
}

//return changed ? this : undefined;
return this;
getContextValue(): string {

const myselfDebuggable = this.isDebuggable();

let anyChildDebuggable = false;
if (this._children) {
for (let child of this._children) {
if (child.isDebuggable()) {
anyChildDebuggable = true;
break;
}
}
}

if (myselfDebuggable || anyChildDebuggable) {
let contextValue = '';
if (myselfDebuggable) {
contextValue += 'node';
}
if (myselfDebuggable && anyChildDebuggable) {
contextValue += '-';
}
if (anyChildDebuggable) {
contextValue += 'subs';
}
return contextValue;
}

return undefined;
}

isDebuggable(): boolean {
const matches = DEBUG_FLAGS_PATTERN.exec(this._cmd);
if ((matches && matches.length >= 2) || this._cmd.indexOf('node ') >= 0 || this._cmd.indexOf('node.exe') >= 0) {
return true;
}
return false;
}
}

export class ProcessProvider implements TreeDataProvider<ProcessTreeItem> {
export class ProcessProvider implements TreeDataProvider<ElementId> {

private _root: ProcessTreeItem;

private _onDidChangeTreeData: EventEmitter<ProcessTreeItem> = new EventEmitter<ProcessTreeItem>();
readonly onDidChangeTreeData: Event<ProcessTreeItem> = this._onDidChangeTreeData.event;
private _onDidChangeTreeData: EventEmitter<ElementId> = new EventEmitter<ElementId>();
readonly onDidChangeTreeData: Event<ElementId> = this._onDidChangeTreeData.event;

constructor(context: vscode.ExtensionContext) {
// everything is lazy
}

getTreeItem(element: ProcessTreeItem): ProcessTreeItem | Thenable<ProcessTreeItem> {
return element;
getTreeItem(elementId: ElementId): ProcessTreeItem | Thenable<ProcessTreeItem> {
return ProcessTreeItem.find(elementId);
}

getChildren(element?: ProcessTreeItem): vscode.ProviderResult<ProcessTreeItem[]> {
if (!element) {
const pid = parseInt(process.env['VSCODE_PID']);

setTimeout(_ => {
listProcesses(pid).then(process => {
const changed = this._root.merge(process);
if (changed) {
this._onDidChangeTreeData.fire(undefined);
}
});
}, POLL_INTERVAL);
getChildren(elementId?: ElementId): vscode.ProviderResult<ElementId[]> {

let element: ProcessTreeItem;
if (elementId) {
element = ProcessTreeItem.find(elementId);
} else {
if (!this._root) {
this._root = new ProcessTreeItem();
const pid = parseInt(process.env['VSCODE_PID']);

setInterval(_ => {
listProcesses(pid).then(process => {
let changedId = this._root.merge(process);
if (changedId) {
// workaround for https://github.com/Microsoft/vscode/issues/40185
if (changedId === this._root.getId()) {
changedId = undefined;
}
this._onDidChangeTreeData.fire(changedId);
}
});
}, POLL_INTERVAL);

this._root = new ProcessTreeItem(pid);
return listProcesses(pid).then(process => {
this._root.merge(process);
return this._root.getChildren();
return this._root.getChildIds();
});
}
element = this._root;
}
return element.getChildren();
return element.getChildIds();
}
}
5 changes: 4 additions & 1 deletion src/ps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
let matches = CMD_PAT.exec(line.trim());
if (matches && matches.length === 6) {
const mb = TOTAL_MB / 100 * parseFloat(matches[4]);
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], matches[3]+'%', mb.toFixed(2)+'MB');
const pid = parseInt(matches[1]);
//if (pid !== p.pid) {
addToTree(pid, parseInt(matches[2]), matches[5], matches[3]+'%', mb.toFixed(2)+'MB');
//}
}
}

Expand Down

0 comments on commit 895bee4

Please sign in to comment.