Skip to content

Commit 97f93d2

Browse files
author
Rachel Macfarlane
committed
Process explorer refactoring
1 parent 591842c commit 97f93d2

6 files changed

Lines changed: 170 additions & 85 deletions

File tree

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

src/vs/code/electron-browser/processExplorer/media/processExplorer.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,12 @@ td {
8888
tbody > tr:hover {
8989
background-color: #2A2D2E;
9090
}
91+
92+
.hidden {
93+
display: none;
94+
}
95+
96+
img {
97+
width: 16px;
98+
margin-right: 4px;
99+
}

src/vs/code/electron-browser/processExplorer/processExplorerMain.ts

Lines changed: 143 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import 'vs/css!./media/processExplorer';
7-
import { listProcesses } from 'vs/base/node/ps';
87
import { webFrame, ipcRenderer, clipboard } from 'electron';
98
import { repeat } from 'vs/base/common/strings';
109
import { totalmem } from 'os';
@@ -16,31 +15,45 @@ import * as platform from 'vs/base/common/platform';
1615
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
1716
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
1817
import { ProcessItem } from 'vs/base/common/processes';
18+
import { addDisposableListener } from 'vs/base/browser/dom';
19+
import { IDisposable } from 'vs/base/common/lifecycle';
20+
1921

20-
let processList: any[];
2122
let mapPidToWindowTitle = new Map<number, string>();
2223

2324
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
2425
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
26+
const listeners: IDisposable[] = [];
27+
const collapsedStateCache: Map<string, boolean> = new Map<string, boolean>();
28+
let lastRequestTime: number;
29+
30+
interface FormattedProcessItem {
31+
cpu: string;
32+
memory: string;
33+
pid: string;
34+
name: string;
35+
formattedName: string;
36+
cmd: string;
37+
}
2538

26-
function getProcessList(rootProcess: ProcessItem) {
27-
const processes: any[] = [];
39+
function getProcessList(rootProcess: ProcessItem, isLocal: boolean): FormattedProcessItem[] {
40+
const processes: FormattedProcessItem[] = [];
2841

2942
if (rootProcess) {
30-
getProcessItem(processes, rootProcess, 0);
43+
getProcessItem(processes, rootProcess, 0, isLocal);
3144
}
3245

3346
return processes;
3447
}
3548

36-
function getProcessItem(processes: any[], item: ProcessItem, indent: number): void {
49+
function getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean): void {
3750
const isRoot = (indent === 0);
3851

3952
const MB = 1024 * 1024;
4053

4154
let name = item.name;
4255
if (isRoot) {
43-
name = `${product.applicationName} main`;
56+
name = isLocal ? `${product.applicationName} main` : 'remote agent';
4457
}
4558

4659
if (name === 'window') {
@@ -52,17 +65,17 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
5265
const formattedName = isRoot ? name : `${repeat(' ', indent)} ${name}`;
5366
const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100));
5467
processes.push({
55-
cpu: Number(item.load.toFixed(0)),
56-
memory: Number((memory / MB).toFixed(0)),
57-
pid: Number((item.pid).toFixed(0)),
68+
cpu: item.load.toFixed(0),
69+
memory: (memory / MB).toFixed(0),
70+
pid: item.pid.toFixed(0),
5871
name,
5972
formattedName,
6073
cmd: item.cmd
6174
});
6275

6376
// Recurse into children if any
6477
if (Array.isArray(item.children)) {
65-
item.children.forEach(child => getProcessItem(processes, child, indent + 1));
78+
item.children.forEach(child => getProcessItem(processes, child, indent + 1, isLocal));
6679
}
6780
}
6881

@@ -71,7 +84,7 @@ function isDebuggable(cmd: string): boolean {
7184
return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0;
7285
}
7386

74-
function attachTo(item: ProcessItem) {
87+
function attachTo(item: FormattedProcessItem) {
7588
const config: any = {
7689
type: 'node',
7790
request: 'attach',
@@ -113,35 +126,57 @@ function getProcessIdWithHighestProperty(processList: any[], propertyName: strin
113126
return maxProcessId;
114127
}
115128

116-
function updateProcessInfo(processList: any[]): void {
129+
function updateSectionCollapsedState(shouldExpand: boolean, body: HTMLElement, twistie: HTMLImageElement, sectionName: string) {
130+
if (shouldExpand) {
131+
body.classList.remove('hidden');
132+
collapsedStateCache.set(sectionName, false);
133+
twistie.src = './media/expanded.svg';
134+
} else {
135+
body.classList.add('hidden');
136+
collapsedStateCache.set(sectionName, true);
137+
twistie.src = './media/collapsed.svg';
138+
}
139+
}
140+
141+
function renderTableSection(sectionName: string, processList: FormattedProcessItem[], renderManySections: boolean, sectionIsLocal: boolean): void {
117142
const container = document.getElementById('process-list');
118143
if (!container) {
119144
return;
120145
}
121146

122-
container.innerHTML = '';
123147
const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu');
124148
const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory');
125149

126-
const tableHead = document.createElement('thead');
127-
tableHead.innerHTML = `<tr>
128-
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
129-
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
130-
<th scope="col" class="pid">${localize('pid', "pid")}</th>
131-
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
132-
</tr>`;
150+
const body = document.createElement('tbody');
151+
152+
if (renderManySections) {
153+
const headerRow = document.createElement('tr');
154+
const data = document.createElement('td');
155+
data.textContent = sectionName;
156+
data.colSpan = 4;
157+
headerRow.appendChild(data);
158+
159+
const twistie = document.createElement('img');
160+
updateSectionCollapsedState(!collapsedStateCache.get(sectionName), body, twistie, sectionName);
161+
data.prepend(twistie);
133162

134-
const tableBody = document.createElement('tbody');
163+
listeners.push(addDisposableListener(data, 'click', (e) => {
164+
const isHidden = body.classList.contains('hidden');
165+
updateSectionCollapsedState(isHidden, body, twistie, sectionName);
166+
}));
167+
168+
container.appendChild(headerRow);
169+
}
135170

136171
processList.forEach(p => {
137172
const row = document.createElement('tr');
138-
row.id = p.pid;
173+
row.id = p.pid.toString();
139174

140175
const cpu = document.createElement('td');
141176
p.pid === highestCPUProcess
142177
? cpu.classList.add('centered', 'highest')
143178
: cpu.classList.add('centered');
144-
cpu.textContent = p.cpu;
179+
cpu.textContent = p.cpu.toString();
145180

146181
const memory = document.createElement('td');
147182
p.pid === highestMemoryProcess
@@ -160,22 +195,53 @@ function updateProcessInfo(processList: any[]): void {
160195
name.textContent = p.formattedName;
161196

162197
row.append(cpu, memory, pid, name);
163-
tableBody.appendChild(row);
198+
199+
listeners.push(addDisposableListener(row, 'contextmenu', (e) => {
200+
showContextMenu(e, p, sectionIsLocal);
201+
}));
202+
203+
body.appendChild(row);
164204
});
165205

166-
container.append(tableHead, tableBody);
206+
container.appendChild(body);
207+
}
208+
209+
function updateProcessInfo(processLists: { [key: string]: ProcessItem }): void {
210+
const container = document.getElementById('process-list');
211+
if (!container) {
212+
return;
213+
}
214+
215+
container.innerHTML = '';
216+
listeners.forEach(l => l.dispose());
217+
218+
const tableHead = document.createElement('thead');
219+
tableHead.innerHTML = `<tr>
220+
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
221+
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
222+
<th scope="col" class="pid">${localize('pid', "pid")}</th>
223+
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
224+
</tr>`;
225+
226+
container.append(tableHead);
227+
228+
const hasMultipleMachines = Object.keys(processLists).length > 1;
229+
Object.keys(processLists).forEach((key, i) => {
230+
const isLocal = i === 0;
231+
renderTableSection(key, getProcessList(processLists[key], isLocal), hasMultipleMachines, isLocal);
232+
});
167233
}
168234

169235
function applyStyles(styles: ProcessExplorerStyles): void {
170236
const styleTag = document.createElement('style');
171237
const content: string[] = [];
172238

173239
if (styles.hoverBackground) {
174-
content.push(`tbody > tr:hover { background-color: ${styles.hoverBackground}; }`);
240+
content.push(`tbody > tr:hover, table > tr:hover { background-color: ${styles.hoverBackground}; }`);
175241
}
176242

177243
if (styles.hoverForeground) {
178-
content.push(`tbody > tr:hover{ color: ${styles.hoverForeground}; }`);
244+
content.push(`tbody > tr:hover, table > tr:hover { color: ${styles.hoverForeground}; }`);
179245
}
180246

181247
if (styles.highlightForeground) {
@@ -200,13 +266,13 @@ function applyZoom(zoomLevel: number): void {
200266
browser.setZoomLevel(webFrame.getZoomLevel(), /*isTrusted*/false);
201267
}
202268

203-
function showContextMenu(e: MouseEvent) {
269+
function showContextMenu(e: MouseEvent, item: FormattedProcessItem, isLocal: boolean) {
204270
e.preventDefault();
205271

206272
const items: IContextMenuItem[] = [];
273+
const pid = Number(item.pid);
207274

208-
const pid = parseInt((e.currentTarget as HTMLElement).id);
209-
if (pid && typeof pid === 'number') {
275+
if (isLocal) {
210276
items.push({
211277
label: localize('killProcess', "Kill Process"),
212278
click() {
@@ -224,55 +290,60 @@ function showContextMenu(e: MouseEvent) {
224290
items.push({
225291
type: 'separator'
226292
});
293+
}
227294

228-
items.push({
229-
label: localize('copy', "Copy"),
230-
click() {
231-
const row = document.getElementById(pid.toString());
232-
if (row) {
233-
clipboard.writeText(row.innerText);
234-
}
295+
items.push({
296+
label: localize('copy', "Copy"),
297+
click() {
298+
const row = document.getElementById(pid.toString());
299+
if (row) {
300+
clipboard.writeText(row.innerText);
235301
}
236-
});
302+
}
303+
});
237304

238-
items.push({
239-
label: localize('copyAll', "Copy All"),
240-
click() {
241-
const processList = document.getElementById('process-list');
242-
if (processList) {
243-
clipboard.writeText(processList.innerText);
244-
}
305+
items.push({
306+
label: localize('copyAll', "Copy All"),
307+
click() {
308+
const processList = document.getElementById('process-list');
309+
if (processList) {
310+
clipboard.writeText(processList.innerText);
245311
}
312+
}
313+
});
314+
315+
if (item && isLocal && isDebuggable(item.cmd)) {
316+
items.push({
317+
type: 'separator'
246318
});
247319

248-
const item = processList.filter(process => process.pid === pid)[0];
249-
if (item && isDebuggable(item.cmd)) {
250-
items.push({
251-
type: 'separator'
252-
});
253-
254-
items.push({
255-
label: localize('debug', "Debug"),
256-
click() {
257-
attachTo(item);
258-
}
259-
});
260-
}
261-
} else {
262320
items.push({
263-
label: localize('copyAll', "Copy All"),
321+
label: localize('debug', "Debug"),
264322
click() {
265-
const processList = document.getElementById('process-list');
266-
if (processList) {
267-
clipboard.writeText(processList.innerText);
268-
}
323+
attachTo(item);
269324
}
270325
});
271326
}
272327

273328
popup(items);
274329
}
275330

331+
function requestProcessList(totalWaitTime: number): void {
332+
setTimeout(() => {
333+
const nextRequestTime = Date.now();
334+
const waited = totalWaitTime + nextRequestTime - lastRequestTime;
335+
lastRequestTime = nextRequestTime;
336+
337+
// Wait at least a second between requests.
338+
if (waited > 1000) {
339+
ipcRenderer.send('windowsInfoRequest');
340+
ipcRenderer.send('vscode:listProcesses');
341+
} else {
342+
requestProcessList(waited);
343+
}
344+
}, 200);
345+
}
346+
276347
export function startup(data: ProcessExplorerData): void {
277348
applyStyles(data.styles);
278349
applyZoom(data.zoomLevel);
@@ -283,23 +354,14 @@ export function startup(data: ProcessExplorerData): void {
283354
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
284355
});
285356

286-
setInterval(() => {
287-
ipcRenderer.send('windowsInfoRequest');
288-
289-
listProcesses(data.pid).then(processes => {
290-
processList = getProcessList(processes);
291-
updateProcessInfo(processList);
292-
293-
const tableRows = document.getElementsByTagName('tr');
294-
for (let i = 0; i < tableRows.length; i++) {
295-
const tableRow = tableRows[i];
296-
tableRow.addEventListener('contextmenu', (e) => {
297-
showContextMenu(e);
298-
});
299-
}
300-
});
301-
}, 1200);
357+
ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: { [key: string]: ProcessItem }) => {
358+
updateProcessInfo(processRoots);
359+
requestProcessList(0);
360+
});
302361

362+
lastRequestTime = Date.now();
363+
ipcRenderer.send('windowsInfoRequest');
364+
ipcRenderer.send('vscode:listProcesses');
303365

304366
document.onkeydown = (e: KeyboardEvent) => {
305367
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;

0 commit comments

Comments
 (0)