-
-
Notifications
You must be signed in to change notification settings - Fork 28
/
index.js
138 lines (110 loc) · 3.91 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import process from 'node:process';
import {promisify} from 'node:util';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import childProcess from 'node:child_process';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const TEN_MEGABYTES = 1000 * 1000 * 10;
const execFile = promisify(childProcess.execFile);
const windows = async () => {
// Source: https://github.com/MarkTiedemann/fastlist
let binary;
switch (process.arch) {
case 'x64':
binary = 'fastlist-0.3.0-x64.exe';
break;
case 'ia32':
binary = 'fastlist-0.3.0-x86.exe';
break;
default:
throw new Error(`Unsupported architecture: ${process.arch}`);
}
const binaryPath = path.join(__dirname, 'vendor', binary);
const {stdout} = await execFile(binaryPath, {
maxBuffer: TEN_MEGABYTES,
windowsHide: true,
});
return stdout
.trim()
.split('\r\n')
.map(line => line.split('\t'))
.map(([pid, ppid, name]) => ({
pid: Number.parseInt(pid, 10),
ppid: Number.parseInt(ppid, 10),
name,
}));
};
const nonWindowsMultipleCalls = async (options = {}) => {
const flags = (options.all === false ? '' : 'a') + 'wwxo';
const returnValue = {};
await Promise.all(['comm', 'args', 'ppid', 'uid', '%cpu', '%mem'].map(async cmd => {
const {stdout} = await execFile('ps', [flags, `pid,${cmd}`], {maxBuffer: TEN_MEGABYTES});
for (let line of stdout.trim().split('\n').slice(1)) {
line = line.trim();
const [pid] = line.split(' ', 1);
const value = line.slice(pid.length + 1).trim();
if (returnValue[pid] === undefined) {
returnValue[pid] = {};
}
returnValue[pid][cmd] = value;
}
}));
// Filter out inconsistencies as there might be race
// issues due to differences in `ps` between the spawns
return Object.entries(returnValue)
.filter(([, value]) => value.comm && value.args && value.ppid && value.uid && value['%cpu'] && value['%mem'])
.map(([key, value]) => ({
pid: Number.parseInt(key, 10),
name: path.basename(value.comm),
cmd: value.args,
ppid: Number.parseInt(value.ppid, 10),
uid: Number.parseInt(value.uid, 10),
cpu: Number.parseFloat(value['%cpu']),
memory: Number.parseFloat(value['%mem']),
}));
};
const ERROR_MESSAGE_PARSING_FAILED = 'ps output parsing failed';
const psOutputRegex = /^[ \t]*(?<pid>\d+)[ \t]+(?<ppid>\d+)[ \t]+(?<uid>[-\d]+)[ \t]+(?<cpu>\d+\.\d+)[ \t]+(?<memory>\d+\.\d+)[ \t]+(?<comm>.*)?/;
const nonWindowsCall = async (options = {}) => {
const flags = options.all === false ? 'wwxo' : 'awwxo';
const psPromises = [
execFile('ps', [flags, 'pid,ppid,uid,%cpu,%mem,comm'], {maxBuffer: TEN_MEGABYTES}),
execFile('ps', [flags, 'pid,args'], {maxBuffer: TEN_MEGABYTES}),
];
const [psLines, psArgsLines] = (await Promise.all(psPromises)).map(({stdout}) => stdout.trim().split('\n'));
const psPids = new Set(psPromises.map(promise => promise.child.pid));
psLines.shift();
psArgsLines.shift();
const processCmds = {};
for (const line of psArgsLines) {
const [pid, cmds] = line.trim().split(' ');
processCmds[pid] = cmds.join(' ');
}
const processes = psLines.map(line => {
const match = psOutputRegex.exec(line);
if (match === null) {
throw new Error(ERROR_MESSAGE_PARSING_FAILED);
}
const {pid, ppid, uid, cpu, memory, comm} = match.groups;
const processInfo = {
pid: Number.parseInt(pid, 10),
ppid: Number.parseInt(ppid, 10),
uid: Number.parseInt(uid, 10),
cpu: Number.parseFloat(cpu),
memory: Number.parseFloat(memory),
name: path.basename(comm),
cmd: processCmds[pid],
};
return processInfo;
}).filter(processInfo => !psPids.has(processInfo.pid));
return processes;
};
const nonWindows = async (options = {}) => {
try {
return await nonWindowsCall(options);
} catch { // If the error is not a parsing error, it should manifest itself in multicall version too.
return nonWindowsMultipleCalls(options);
}
};
const psList = process.platform === 'win32' ? windows : nonWindows;
export default psList;