提交 97f93d29 编写于 作者: R Rachel Macfarlane

Process explorer refactoring

上级 591842cc
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M11 10H5.344L11 4.414V10z"/></svg>
\ No newline at end of file
...@@ -88,3 +88,12 @@ td { ...@@ -88,3 +88,12 @@ td {
tbody > tr:hover { tbody > tr:hover {
background-color: #2A2D2E; background-color: #2A2D2E;
} }
.hidden {
display: none;
}
img {
width: 16px;
margin-right: 4px;
}
\ No newline at end of file
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/processExplorer'; import 'vs/css!./media/processExplorer';
import { listProcesses } from 'vs/base/node/ps';
import { webFrame, ipcRenderer, clipboard } from 'electron'; import { webFrame, ipcRenderer, clipboard } from 'electron';
import { repeat } from 'vs/base/common/strings'; import { repeat } from 'vs/base/common/strings';
import { totalmem } from 'os'; import { totalmem } from 'os';
...@@ -16,31 +15,45 @@ import * as platform from 'vs/base/common/platform'; ...@@ -16,31 +15,45 @@ import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
import { ProcessItem } from 'vs/base/common/processes'; 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<number, string>(); let mapPidToWindowTitle = new Map<number, string>();
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/; const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
const listeners: IDisposable[] = [];
const collapsedStateCache: Map<string, boolean> = new Map<string, boolean>();
let lastRequestTime: number;
interface FormattedProcessItem {
cpu: string;
memory: string;
pid: string;
name: string;
formattedName: string;
cmd: string;
}
function getProcessList(rootProcess: ProcessItem) { function getProcessList(rootProcess: ProcessItem, isLocal: boolean): FormattedProcessItem[] {
const processes: any[] = []; const processes: FormattedProcessItem[] = [];
if (rootProcess) { if (rootProcess) {
getProcessItem(processes, rootProcess, 0); getProcessItem(processes, rootProcess, 0, isLocal);
} }
return processes; 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 isRoot = (indent === 0);
const MB = 1024 * 1024; const MB = 1024 * 1024;
let name = item.name; let name = item.name;
if (isRoot) { if (isRoot) {
name = `${product.applicationName} main`; name = isLocal ? `${product.applicationName} main` : 'remote agent';
} }
if (name === 'window') { if (name === 'window') {
...@@ -52,9 +65,9 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo ...@@ -52,9 +65,9 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
const formattedName = isRoot ? name : `${repeat(' ', indent)} ${name}`; const formattedName = isRoot ? name : `${repeat(' ', indent)} ${name}`;
const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100)); const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100));
processes.push({ processes.push({
cpu: Number(item.load.toFixed(0)), cpu: item.load.toFixed(0),
memory: Number((memory / MB).toFixed(0)), memory: (memory / MB).toFixed(0),
pid: Number((item.pid).toFixed(0)), pid: item.pid.toFixed(0),
name, name,
formattedName, formattedName,
cmd: item.cmd cmd: item.cmd
...@@ -62,7 +75,7 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo ...@@ -62,7 +75,7 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
// Recurse into children if any // Recurse into children if any
if (Array.isArray(item.children)) { 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 { ...@@ -71,7 +84,7 @@ function isDebuggable(cmd: string): boolean {
return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0; 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 = { const config: any = {
type: 'node', type: 'node',
request: 'attach', request: 'attach',
...@@ -113,35 +126,57 @@ function getProcessIdWithHighestProperty(processList: any[], propertyName: strin ...@@ -113,35 +126,57 @@ function getProcessIdWithHighestProperty(processList: any[], propertyName: strin
return maxProcessId; 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'); const container = document.getElementById('process-list');
if (!container) { if (!container) {
return; return;
} }
container.innerHTML = '';
const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu'); const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu');
const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory'); const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory');
const tableHead = document.createElement('thead'); const body = document.createElement('tbody');
tableHead.innerHTML = `<tr>
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th> if (renderManySections) {
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th> const headerRow = document.createElement('tr');
<th scope="col" class="pid">${localize('pid', "pid")}</th> const data = document.createElement('td');
<th scope="col" class="nameLabel">${localize('name', "Name")}</th> data.textContent = sectionName;
</tr>`; data.colSpan = 4;
headerRow.appendChild(data);
const twistie = document.createElement('img');
updateSectionCollapsedState(!collapsedStateCache.get(sectionName), body, twistie, sectionName);
data.prepend(twistie);
const tableBody = document.createElement('tbody'); listeners.push(addDisposableListener(data, 'click', (e) => {
const isHidden = body.classList.contains('hidden');
updateSectionCollapsedState(isHidden, body, twistie, sectionName);
}));
container.appendChild(headerRow);
}
processList.forEach(p => { processList.forEach(p => {
const row = document.createElement('tr'); const row = document.createElement('tr');
row.id = p.pid; row.id = p.pid.toString();
const cpu = document.createElement('td'); const cpu = document.createElement('td');
p.pid === highestCPUProcess p.pid === highestCPUProcess
? cpu.classList.add('centered', 'highest') ? cpu.classList.add('centered', 'highest')
: cpu.classList.add('centered'); : cpu.classList.add('centered');
cpu.textContent = p.cpu; cpu.textContent = p.cpu.toString();
const memory = document.createElement('td'); const memory = document.createElement('td');
p.pid === highestMemoryProcess p.pid === highestMemoryProcess
...@@ -160,10 +195,41 @@ function updateProcessInfo(processList: any[]): void { ...@@ -160,10 +195,41 @@ function updateProcessInfo(processList: any[]): void {
name.textContent = p.formattedName; name.textContent = p.formattedName;
row.append(cpu, memory, pid, name); 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 = `<tr>
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
<th scope="col" class="pid">${localize('pid', "pid")}</th>
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
</tr>`;
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 { function applyStyles(styles: ProcessExplorerStyles): void {
...@@ -171,11 +237,11 @@ function applyStyles(styles: ProcessExplorerStyles): void { ...@@ -171,11 +237,11 @@ function applyStyles(styles: ProcessExplorerStyles): void {
const content: string[] = []; const content: string[] = [];
if (styles.hoverBackground) { 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) { 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) { if (styles.highlightForeground) {
...@@ -200,13 +266,13 @@ function applyZoom(zoomLevel: number): void { ...@@ -200,13 +266,13 @@ function applyZoom(zoomLevel: number): void {
browser.setZoomLevel(webFrame.getZoomLevel(), /*isTrusted*/false); browser.setZoomLevel(webFrame.getZoomLevel(), /*isTrusted*/false);
} }
function showContextMenu(e: MouseEvent) { function showContextMenu(e: MouseEvent, item: FormattedProcessItem, isLocal: boolean) {
e.preventDefault(); e.preventDefault();
const items: IContextMenuItem[] = []; const items: IContextMenuItem[] = [];
const pid = Number(item.pid);
const pid = parseInt((e.currentTarget as HTMLElement).id); if (isLocal) {
if (pid && typeof pid === 'number') {
items.push({ items.push({
label: localize('killProcess', "Kill Process"), label: localize('killProcess', "Kill Process"),
click() { click() {
...@@ -224,48 +290,37 @@ function showContextMenu(e: MouseEvent) { ...@@ -224,48 +290,37 @@ function showContextMenu(e: MouseEvent) {
items.push({ items.push({
type: 'separator' type: 'separator'
}); });
}
items.push({ items.push({
label: localize('copy', "Copy"), label: localize('copy', "Copy"),
click() { click() {
const row = document.getElementById(pid.toString()); const row = document.getElementById(pid.toString());
if (row) { if (row) {
clipboard.writeText(row.innerText); clipboard.writeText(row.innerText);
}
} }
}); }
});
items.push({ items.push({
label: localize('copyAll', "Copy All"), label: localize('copyAll', "Copy All"),
click() { click() {
const processList = document.getElementById('process-list'); const processList = document.getElementById('process-list');
if (processList) { if (processList) {
clipboard.writeText(processList.innerText); clipboard.writeText(processList.innerText);
}
} }
}
});
if (item && isLocal && isDebuggable(item.cmd)) {
items.push({
type: 'separator'
}); });
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({ items.push({
label: localize('copyAll', "Copy All"), label: localize('debug', "Debug"),
click() { click() {
const processList = document.getElementById('process-list'); attachTo(item);
if (processList) {
clipboard.writeText(processList.innerText);
}
} }
}); });
} }
...@@ -273,6 +328,22 @@ function showContextMenu(e: MouseEvent) { ...@@ -273,6 +328,22 @@ function showContextMenu(e: MouseEvent) {
popup(items); 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 { export function startup(data: ProcessExplorerData): void {
applyStyles(data.styles); applyStyles(data.styles);
applyZoom(data.zoomLevel); applyZoom(data.zoomLevel);
...@@ -283,23 +354,14 @@ export function startup(data: ProcessExplorerData): void { ...@@ -283,23 +354,14 @@ export function startup(data: ProcessExplorerData): void {
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title)); windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
}); });
setInterval(() => { ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: { [key: string]: ProcessItem }) => {
ipcRenderer.send('windowsInfoRequest'); updateProcessInfo(processRoots);
requestProcessList(0);
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);
lastRequestTime = Date.now();
ipcRenderer.send('windowsInfoRequest');
ipcRenderer.send('vscode:listProcesses');
document.onkeydown = (e: KeyboardEvent) => { document.onkeydown = (e: KeyboardEvent) => {
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
......
...@@ -306,13 +306,13 @@ export class DiagnosticsService implements IDiagnosticsService { ...@@ -306,13 +306,13 @@ export class DiagnosticsService implements IDiagnosticsService {
output.push('CPU %\tMem MB\t PID\tProcess'); output.push('CPU %\tMem MB\t PID\tProcess');
if (rootProcess) { if (rootProcess) {
this.formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0); this.formatProcessItem(info.mainPID, mapPidToWindowTitle, output, rootProcess, 0);
} }
return output.join('\n'); return output.join('\n');
} }
private formatProcessItem(mapPidToWindowTitle: Map<number, string>, output: string[], item: ProcessItem, indent: number): void { private formatProcessItem(mainPid: number, mapPidToWindowTitle: Map<number, string>, output: string[], item: ProcessItem, indent: number): void {
const isRoot = (indent === 0); const isRoot = (indent === 0);
const MB = 1024 * 1024; const MB = 1024 * 1024;
...@@ -320,7 +320,7 @@ export class DiagnosticsService implements IDiagnosticsService { ...@@ -320,7 +320,7 @@ export class DiagnosticsService implements IDiagnosticsService {
// Format name with indent // Format name with indent
let name: string; let name: string;
if (isRoot) { if (isRoot) {
name = `${product.applicationName} main`; name = item.pid === mainPid ? `${product.applicationName} main` : 'remote agent';
} else { } else {
name = `${repeat(' ', indent)} ${item.name}`; name = `${repeat(' ', indent)} ${item.name}`;
...@@ -333,7 +333,7 @@ export class DiagnosticsService implements IDiagnosticsService { ...@@ -333,7 +333,7 @@ export class DiagnosticsService implements IDiagnosticsService {
// Recurse into children if any // Recurse into children if any
if (Array.isArray(item.children)) { 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));
} }
} }
} }
......
...@@ -15,6 +15,8 @@ import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; ...@@ -15,6 +15,8 @@ import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowState } from 'vs/platform/windows/electron-main/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'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
...@@ -44,6 +46,16 @@ export class IssueService implements IIssueService { ...@@ -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) => { ipcMain.on('vscode:issuePerformanceInfoRequest', (event: Event) => {
this.getPerformanceInfo().then(msg => { this.getPerformanceInfo().then(msg => {
event.sender.send('vscode:issuePerformanceInfoResponse', msg); event.sender.send('vscode:issuePerformanceInfoResponse', msg);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册