diff --git a/src/vs/code/electron-browser/processExplorer/media/collapsed.svg b/src/vs/code/electron-browser/processExplorer/media/collapsed.svg
new file mode 100755
index 00000000000..3a63808c358
--- /dev/null
+++ b/src/vs/code/electron-browser/processExplorer/media/collapsed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/vs/code/electron-browser/processExplorer/media/expanded.svg b/src/vs/code/electron-browser/processExplorer/media/expanded.svg
new file mode 100755
index 00000000000..75f73adbb02
--- /dev/null
+++ b/src/vs/code/electron-browser/processExplorer/media/expanded.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
index 2691208f2a2..b8c736e817c 100644
--- a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
+++ b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
@@ -88,3 +88,12 @@ td {
tbody > tr:hover {
background-color: #2A2D2E;
}
+
+.hidden {
+ display: none;
+}
+
+img {
+ width: 16px;
+ margin-right: 4px;
+}
\ No newline at end of file
diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts
index f2601c11312..ed19f54b2cd 100644
--- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts
+++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/processExplorer';
-import { listProcesses } from 'vs/base/node/ps';
import { webFrame, ipcRenderer, clipboard } from 'electron';
import { repeat } from 'vs/base/common/strings';
import { totalmem } from 'os';
@@ -16,31 +15,45 @@ import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
+import { addDisposableListener } from 'vs/base/browser/dom';
+import { IDisposable } from 'vs/base/common/lifecycle';
+
-let processList: any[];
let mapPidToWindowTitle = new Map();
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
+const listeners: IDisposable[] = [];
+const collapsedStateCache: Map = new Map();
+let lastRequestTime: number;
-function getProcessList(rootProcess: ProcessItem) {
- const processes: any[] = [];
+interface FormattedProcessItem {
+ cpu: string;
+ memory: string;
+ pid: string;
+ name: string;
+ formattedName: string;
+ cmd: string;
+}
+
+function getProcessList(rootProcess: ProcessItem, isLocal: boolean): FormattedProcessItem[] {
+ const processes: FormattedProcessItem[] = [];
if (rootProcess) {
- getProcessItem(processes, rootProcess, 0);
+ getProcessItem(processes, rootProcess, 0, isLocal);
}
return processes;
}
-function getProcessItem(processes: any[], item: ProcessItem, indent: number): void {
+function getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean): void {
const isRoot = (indent === 0);
const MB = 1024 * 1024;
let name = item.name;
if (isRoot) {
- name = `${product.applicationName} main`;
+ name = isLocal ? `${product.applicationName} main` : 'remote agent';
}
if (name === 'window') {
@@ -52,9 +65,9 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
const formattedName = isRoot ? name : `${repeat(' ', indent)} ${name}`;
const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100));
processes.push({
- cpu: Number(item.load.toFixed(0)),
- memory: Number((memory / MB).toFixed(0)),
- pid: Number((item.pid).toFixed(0)),
+ cpu: item.load.toFixed(0),
+ memory: (memory / MB).toFixed(0),
+ pid: item.pid.toFixed(0),
name,
formattedName,
cmd: item.cmd
@@ -62,7 +75,7 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
// Recurse into children if any
if (Array.isArray(item.children)) {
- item.children.forEach(child => getProcessItem(processes, child, indent + 1));
+ item.children.forEach(child => getProcessItem(processes, child, indent + 1, isLocal));
}
}
@@ -71,7 +84,7 @@ function isDebuggable(cmd: string): boolean {
return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0;
}
-function attachTo(item: ProcessItem) {
+function attachTo(item: FormattedProcessItem) {
const config: any = {
type: 'node',
request: 'attach',
@@ -113,35 +126,57 @@ function getProcessIdWithHighestProperty(processList: any[], propertyName: strin
return maxProcessId;
}
-function updateProcessInfo(processList: any[]): void {
+function updateSectionCollapsedState(shouldExpand: boolean, body: HTMLElement, twistie: HTMLImageElement, sectionName: string) {
+ if (shouldExpand) {
+ body.classList.remove('hidden');
+ collapsedStateCache.set(sectionName, false);
+ twistie.src = './media/expanded.svg';
+ } else {
+ body.classList.add('hidden');
+ collapsedStateCache.set(sectionName, true);
+ twistie.src = './media/collapsed.svg';
+ }
+}
+
+function renderTableSection(sectionName: string, processList: FormattedProcessItem[], renderManySections: boolean, sectionIsLocal: boolean): void {
const container = document.getElementById('process-list');
if (!container) {
return;
}
- container.innerHTML = '';
const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu');
const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory');
- const tableHead = document.createElement('thead');
- tableHead.innerHTML = `
- | ${localize('cpu', "CPU %")} |
- ${localize('memory', "Memory (MB)")} |
- ${localize('pid', "pid")} |
- ${localize('name', "Name")} |
-
`;
+ const body = document.createElement('tbody');
- const tableBody = document.createElement('tbody');
+ if (renderManySections) {
+ const headerRow = document.createElement('tr');
+ const data = document.createElement('td');
+ data.textContent = sectionName;
+ data.colSpan = 4;
+ headerRow.appendChild(data);
+
+ const twistie = document.createElement('img');
+ updateSectionCollapsedState(!collapsedStateCache.get(sectionName), body, twistie, sectionName);
+ data.prepend(twistie);
+
+ listeners.push(addDisposableListener(data, 'click', (e) => {
+ const isHidden = body.classList.contains('hidden');
+ updateSectionCollapsedState(isHidden, body, twistie, sectionName);
+ }));
+
+ container.appendChild(headerRow);
+ }
processList.forEach(p => {
const row = document.createElement('tr');
- row.id = p.pid;
+ row.id = p.pid.toString();
const cpu = document.createElement('td');
p.pid === highestCPUProcess
? cpu.classList.add('centered', 'highest')
: cpu.classList.add('centered');
- cpu.textContent = p.cpu;
+ cpu.textContent = p.cpu.toString();
const memory = document.createElement('td');
p.pid === highestMemoryProcess
@@ -160,10 +195,41 @@ function updateProcessInfo(processList: any[]): void {
name.textContent = p.formattedName;
row.append(cpu, memory, pid, name);
- tableBody.appendChild(row);
+
+ listeners.push(addDisposableListener(row, 'contextmenu', (e) => {
+ showContextMenu(e, p, sectionIsLocal);
+ }));
+
+ body.appendChild(row);
});
- container.append(tableHead, tableBody);
+ container.appendChild(body);
+}
+
+function updateProcessInfo(processLists: { [key: string]: ProcessItem }): void {
+ const container = document.getElementById('process-list');
+ if (!container) {
+ return;
+ }
+
+ container.innerHTML = '';
+ listeners.forEach(l => l.dispose());
+
+ const tableHead = document.createElement('thead');
+ tableHead.innerHTML = `
+ | ${localize('cpu', "CPU %")} |
+ ${localize('memory', "Memory (MB)")} |
+ ${localize('pid', "pid")} |
+ ${localize('name', "Name")} |
+
`;
+
+ container.append(tableHead);
+
+ const hasMultipleMachines = Object.keys(processLists).length > 1;
+ Object.keys(processLists).forEach((key, i) => {
+ const isLocal = i === 0;
+ renderTableSection(key, getProcessList(processLists[key], isLocal), hasMultipleMachines, isLocal);
+ });
}
function applyStyles(styles: ProcessExplorerStyles): void {
@@ -171,11 +237,11 @@ function applyStyles(styles: ProcessExplorerStyles): void {
const content: string[] = [];
if (styles.hoverBackground) {
- content.push(`tbody > tr:hover { background-color: ${styles.hoverBackground}; }`);
+ content.push(`tbody > tr:hover, table > tr:hover { background-color: ${styles.hoverBackground}; }`);
}
if (styles.hoverForeground) {
- content.push(`tbody > tr:hover{ color: ${styles.hoverForeground}; }`);
+ content.push(`tbody > tr:hover, table > tr:hover { color: ${styles.hoverForeground}; }`);
}
if (styles.highlightForeground) {
@@ -200,13 +266,13 @@ function applyZoom(zoomLevel: number): void {
browser.setZoomLevel(webFrame.getZoomLevel(), /*isTrusted*/false);
}
-function showContextMenu(e: MouseEvent) {
+function showContextMenu(e: MouseEvent, item: FormattedProcessItem, isLocal: boolean) {
e.preventDefault();
const items: IContextMenuItem[] = [];
+ const pid = Number(item.pid);
- const pid = parseInt((e.currentTarget as HTMLElement).id);
- if (pid && typeof pid === 'number') {
+ if (isLocal) {
items.push({
label: localize('killProcess', "Kill Process"),
click() {
@@ -224,48 +290,37 @@ function showContextMenu(e: MouseEvent) {
items.push({
type: 'separator'
});
+ }
- items.push({
- label: localize('copy', "Copy"),
- click() {
- const row = document.getElementById(pid.toString());
- if (row) {
- clipboard.writeText(row.innerText);
- }
+ items.push({
+ label: localize('copy', "Copy"),
+ click() {
+ const row = document.getElementById(pid.toString());
+ if (row) {
+ clipboard.writeText(row.innerText);
}
- });
-
- items.push({
- label: localize('copyAll', "Copy All"),
- click() {
- const processList = document.getElementById('process-list');
- if (processList) {
- clipboard.writeText(processList.innerText);
- }
- }
- });
-
- const item = processList.filter(process => process.pid === pid)[0];
- if (item && isDebuggable(item.cmd)) {
- items.push({
- type: 'separator'
- });
-
- items.push({
- label: localize('debug', "Debug"),
- click() {
- attachTo(item);
- }
- });
}
- } else {
+ });
+
+ items.push({
+ label: localize('copyAll', "Copy All"),
+ click() {
+ const processList = document.getElementById('process-list');
+ if (processList) {
+ clipboard.writeText(processList.innerText);
+ }
+ }
+ });
+
+ if (item && isLocal && isDebuggable(item.cmd)) {
items.push({
- label: localize('copyAll', "Copy All"),
+ type: 'separator'
+ });
+
+ items.push({
+ label: localize('debug', "Debug"),
click() {
- const processList = document.getElementById('process-list');
- if (processList) {
- clipboard.writeText(processList.innerText);
- }
+ attachTo(item);
}
});
}
@@ -273,6 +328,22 @@ function showContextMenu(e: MouseEvent) {
popup(items);
}
+function requestProcessList(totalWaitTime: number): void {
+ setTimeout(() => {
+ const nextRequestTime = Date.now();
+ const waited = totalWaitTime + nextRequestTime - lastRequestTime;
+ lastRequestTime = nextRequestTime;
+
+ // Wait at least a second between requests.
+ if (waited > 1000) {
+ ipcRenderer.send('windowsInfoRequest');
+ ipcRenderer.send('vscode:listProcesses');
+ } else {
+ requestProcessList(waited);
+ }
+ }, 200);
+}
+
export function startup(data: ProcessExplorerData): void {
applyStyles(data.styles);
applyZoom(data.zoomLevel);
@@ -283,23 +354,14 @@ export function startup(data: ProcessExplorerData): void {
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
});
- setInterval(() => {
- ipcRenderer.send('windowsInfoRequest');
-
- listProcesses(data.pid).then(processes => {
- processList = getProcessList(processes);
- updateProcessInfo(processList);
-
- const tableRows = document.getElementsByTagName('tr');
- for (let i = 0; i < tableRows.length; i++) {
- const tableRow = tableRows[i];
- tableRow.addEventListener('contextmenu', (e) => {
- showContextMenu(e);
- });
- }
- });
- }, 1200);
+ ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: { [key: string]: ProcessItem }) => {
+ updateProcessInfo(processRoots);
+ requestProcessList(0);
+ });
+ lastRequestTime = Date.now();
+ ipcRenderer.send('windowsInfoRequest');
+ ipcRenderer.send('vscode:listProcesses');
document.onkeydown = (e: KeyboardEvent) => {
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts
index 08eb0472e0b..e5bc282fb55 100644
--- a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts
+++ b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts
@@ -306,13 +306,13 @@ export class DiagnosticsService implements IDiagnosticsService {
output.push('CPU %\tMem MB\t PID\tProcess');
if (rootProcess) {
- this.formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0);
+ this.formatProcessItem(info.mainPID, mapPidToWindowTitle, output, rootProcess, 0);
}
return output.join('\n');
}
- private formatProcessItem(mapPidToWindowTitle: Map, output: string[], item: ProcessItem, indent: number): void {
+ private formatProcessItem(mainPid: number, mapPidToWindowTitle: Map, output: string[], item: ProcessItem, indent: number): void {
const isRoot = (indent === 0);
const MB = 1024 * 1024;
@@ -320,7 +320,7 @@ export class DiagnosticsService implements IDiagnosticsService {
// Format name with indent
let name: string;
if (isRoot) {
- name = `${product.applicationName} main`;
+ name = item.pid === mainPid ? `${product.applicationName} main` : 'remote agent';
} else {
name = `${repeat(' ', indent)} ${item.name}`;
@@ -333,7 +333,7 @@ export class DiagnosticsService implements IDiagnosticsService {
// Recurse into children if any
if (Array.isArray(item.children)) {
- item.children.forEach(child => this.formatProcessItem(mapPidToWindowTitle, output, child, indent + 1));
+ item.children.forEach(child => this.formatProcessItem(mainPid, mapPidToWindowTitle, output, child, indent + 1));
}
}
}
diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts
index 5e9b24afe81..63ef9c2edff 100644
--- a/src/vs/platform/issue/electron-main/issueService.ts
+++ b/src/vs/platform/issue/electron-main/issueService.ts
@@ -15,6 +15,8 @@ import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowState } from 'vs/platform/windows/electron-main/windows';
+import { listProcesses } from 'vs/base/node/ps';
+import { ProcessItem } from 'vs/base/common/processes';
const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
@@ -44,6 +46,16 @@ export class IssueService implements IIssueService {
});
});
+ ipcMain.on('vscode:listProcesses', async (event: Event) => {
+ const mainPid = await this.launchService.getMainProcessId();
+ const rootProcess = await listProcesses(mainPid);
+ const remoteProcesses = (await this.launchService.getRemoteDiagnostics({ includeProcesses: true }))
+ .map(data => data.processes)
+ .filter((x): x is ProcessItem => !!x);
+
+ event.sender.send('vscode:listProcessesResponse', [rootProcess, ...remoteProcesses]);
+ });
+
ipcMain.on('vscode:issuePerformanceInfoRequest', (event: Event) => {
this.getPerformanceInfo().then(msg => {
event.sender.send('vscode:issuePerformanceInfoResponse', msg);