|
2 | 2 |
|
3 | 3 | > A Node.js module for looking up running processes. Originated from [neekey/ps](https://github.com/neekey/ps), [UmbraEngineering/ps](https://github.com/UmbraEngineering/ps) and completely reforged. |
4 | 4 |
|
5 | | -## Differences |
6 | | -* [x] Rewritten in TypeScript |
7 | | -* [x] CJS and ESM package entry points |
8 | | -* [x] `table-parser` replaced with `@webpod/ingrid` to handle some issues: [neekey/ps#76](https://github.com/neekey/ps/issues/76), [neekey/ps#62](https://github.com/neekey/ps/issues/62), [neekey/table-parser#11](https://github.com/neekey/table-parser/issues/11), [neekey/table-parser#18](https://github.com/neekey/table-parser/issues/18) |
9 | | -* [x] Provides promisified responses |
10 | | -* [x] Brings sync API |
11 | | -* [x] Builds a process subtree by parent |
| 5 | +## Features |
| 6 | +- Written in TypeScript, ships with types |
| 7 | +- CJS and ESM entry points |
| 8 | +- Promise and callback API, sync variants |
| 9 | +- Process tree traversal by parent pid |
| 10 | +- Uses `@webpod/ingrid` instead of `table-parser` ([neekey/ps#76](https://github.com/neekey/ps/issues/76), [neekey/ps#62](https://github.com/neekey/ps/issues/62)) |
12 | 11 |
|
13 | 12 | ## Install |
14 | 13 | ```bash |
15 | | -$ npm install @webpod/ps |
| 14 | +npm install @webpod/ps |
16 | 15 | ``` |
17 | 16 |
|
18 | 17 | ## Internals |
19 | | -This module uses different approaches for getting process list: |
20 | 18 |
|
21 | | -| Platform | Method | |
22 | | -|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| |
23 | | -| Unix/Mac | `ps -eo pid,ppid,args` | |
24 | | -| Windows (kernel >= 26000)| `pwsh -NoProfile -Command "Get-CimInstance Win32_Process \| Select-Object ProcessId,ParentProcessId,CommandLine \| ConvertTo-Json -Compress"` | |
25 | | -| Windows (kernel < 26000) | [`wmic`](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmic) `process get ProcessId,CommandLine` | |
| 19 | +| Platform | Command | |
| 20 | +|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| |
| 21 | +| Unix / macOS | `ps -eo pid,ppid,args` | |
| 22 | +| Windows (kernel >= 26000) | `pwsh -NoProfile -Command "Get-CimInstance Win32_Process \| Select-Object ProcessId,ParentProcessId,CommandLine \| ConvertTo-Json -Compress"` | |
| 23 | +| Windows (kernel < 26000) | [`wmic`](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmic) `process get ProcessId,CommandLine` | |
26 | 24 |
|
27 | | -## Usage |
| 25 | +## API |
| 26 | + |
| 27 | +### lookup(query?, callback?) |
| 28 | +Returns a list of processes matching the query. |
28 | 29 |
|
29 | | -### lookup() |
30 | | -Searches for the process by the specified `pid`. |
31 | 30 | ```ts |
32 | | -import {lookup} from '@webpod/ps' |
33 | | - |
34 | | -// Both callback and promise styles are supported |
35 | | -const list = await lookup({pid: 12345}) |
36 | | - |
37 | | -// or |
38 | | -lookup({pid: 12345}, (err, list) => { |
39 | | - if (err) { |
40 | | - throw new Error(err) |
41 | | - } |
42 | | - |
43 | | - const [found] = list |
44 | | - if (found) { |
45 | | - console.log('PID: %s, COMMAND: %s, ARGUMENTS: %s', found.pid, found.command, found.arguments) |
46 | | - } else { |
47 | | - console.log('No such process found!') |
48 | | - } |
49 | | -}) |
| 31 | +import { lookup } from '@webpod/ps' |
50 | 32 |
|
51 | | -// or syncronously |
52 | | -const _list = lookup.sync({pid: 12345}) |
53 | | -``` |
| 33 | +// Find by pid |
| 34 | +const list = await lookup({ pid: 12345 }) |
| 35 | +// [{ pid: '12345', ppid: '123', command: '/usr/bin/node', arguments: ['server.js', '--port=3000'] }] |
54 | 36 |
|
55 | | -Define a query opts to filter the results by `command` and/or `arguments` predicates: |
56 | | -```ts |
57 | | -const list = await lookup({ |
58 | | - command: 'node', // it will be used to build a regex |
59 | | - arguments: '--debug', |
60 | | -}) |
| 37 | +// Filter by command and/or arguments (treated as RegExp) |
| 38 | +const nodes = await lookup({ command: 'node', arguments: '--debug' }) |
61 | 39 |
|
62 | | -list.forEach(entry => { |
63 | | - console.log('PID: %s, COMMAND: %s, ARGUMENTS: %s', entry.pid, entry.command, entry.arguments); |
64 | | -}) |
| 40 | +// Filter by parent pid |
| 41 | +const children = await lookup({ ppid: 82292 }) |
| 42 | + |
| 43 | +// Synchronous |
| 44 | +const all = lookup.sync() |
65 | 45 | ``` |
66 | 46 |
|
67 | | -Unix users can override the default `ps` arguments: |
| 47 | +On Unix, you can override the default `ps` arguments via `psargs`: |
68 | 48 | ```ts |
69 | | -lookup({ |
70 | | - command: 'node', |
71 | | - psargs: '-eo pid,ppid,comm' |
72 | | -}, (err, resultList) => { |
73 | | -// ... |
74 | | -}) |
| 49 | +const list = await lookup({ command: 'node', psargs: '-eo pid,ppid,comm' }) |
75 | 50 | ``` |
76 | 51 |
|
77 | | -Specify the `ppid` option to filter the results by the parent process id (make sure that your custom `psargs` provides this output: `-l` or `-j` for instance): |
| 52 | +Callback style is also supported: |
78 | 53 | ```ts |
79 | | -lookup({ |
80 | | - command: 'mongod', |
81 | | - psargs: '-l', |
82 | | - ppid: 82292 |
83 | | -}, (err, resultList) => { |
84 | | - // ... |
85 | | -}) |
| 54 | +lookup({ pid: 12345 }, (err, list) => { /* ... */ }) |
86 | 55 | ``` |
87 | 56 |
|
88 | | -### tree() |
89 | | -Returns a child processes list by the specified parent `pid`. Some kind of shortcut for `lookup({ppid: pid})`. |
| 57 | +### tree(opts?, callback?) |
| 58 | +Returns child processes of a given parent pid. |
| 59 | + |
90 | 60 | ```ts |
91 | 61 | import { tree } from '@webpod/ps' |
92 | 62 |
|
93 | | -const children = await tree(123) |
94 | | -/** |
95 | | -[ |
96 | | - {pid: 124, ppid: 123}, |
97 | | - {pid: 125, ppid: 123} |
98 | | -] |
99 | | -*/ |
| 63 | +// Direct children |
| 64 | +const children = await tree(123) |
| 65 | +// [ |
| 66 | +// { pid: '124', ppid: '123', command: 'node', arguments: ['worker.js'] }, |
| 67 | +// { pid: '125', ppid: '123', command: 'node', arguments: ['worker.js'] } |
| 68 | +// ] |
| 69 | + |
| 70 | +// All descendants |
| 71 | +const all = await tree({ pid: 123, recursive: true }) |
| 72 | +// [ |
| 73 | +// { pid: '124', ppid: '123', ... }, |
| 74 | +// { pid: '125', ppid: '123', ... }, |
| 75 | +// { pid: '126', ppid: '124', ... }, |
| 76 | +// { pid: '127', ppid: '125', ... } |
| 77 | +// ] |
| 78 | + |
| 79 | +// Synchronous |
| 80 | +const list = tree.sync({ pid: 123, recursive: true }) |
100 | 81 | ``` |
101 | 82 |
|
102 | | -To obtain all nested children, set `recursive` option to `true`: |
103 | | -```ts |
104 | | -const children = await tree({pid: 123, recursive: true}) |
105 | | -/** |
106 | | -[ |
107 | | - {pid: 124, ppid: 123}, |
108 | | - {pid: 125, ppid: 123}, |
109 | | -
|
110 | | - {pid: 126, ppid: 124}, |
111 | | - {pid: 127, ppid: 124}, |
112 | | - {pid: 128, ppid: 124}, |
113 | | - |
114 | | - {pid: 129, ppid: 125}, |
115 | | - {pid: 130, ppid: 125}, |
116 | | -] |
117 | | -*/ |
118 | | - |
119 | | -// or syncronously |
120 | | -const list = tree.sync({pid: 123, recursive: true}) |
121 | | -``` |
122 | | - |
123 | | -### kill() |
124 | | -Eliminates the process by its `pid`. |
| 83 | +### kill(pid, opts?, callback?) |
| 84 | +Kills a process and optionally verifies it has exited. |
125 | 85 |
|
126 | 86 | ```ts |
127 | 87 | import { kill } from '@webpod/ps' |
128 | 88 |
|
129 | | -kill('12345', (err, pid) => { |
130 | | - if (err) { |
131 | | - throw new Error(err) |
132 | | - } else { |
133 | | - console.log('Process %s has been killed!', pid) |
134 | | - } |
135 | | -}) |
136 | | -``` |
137 | | - |
138 | | -Method `kill` also supports a `signal` option to be passed. It's only a wrapper of `process.kill()` with checking of that killing is finished after the method is called. |
| 89 | +await kill(12345) |
139 | 90 |
|
140 | | -```ts |
141 | | -import { kill } from '@webpod/ps' |
| 91 | +// With signal |
| 92 | +await kill(12345, 'SIGKILL') |
142 | 93 |
|
143 | | -// Pass signal SIGKILL for killing the process without allowing it to clean up |
144 | | -kill('12345', 'SIGKILL', (err, pid) => { |
145 | | - if (err) { |
146 | | - throw new Error(err) |
147 | | - } else { |
148 | | - console.log('Process %s has been killed without a clean-up!', pid) |
149 | | - } |
| 94 | +// With options and verification callback |
| 95 | +await kill(12345, { signal: 'SIGKILL', timeout: 10 }, (err, pid) => { |
| 96 | + // called when the process is confirmed dead or timeout is reached |
150 | 97 | }) |
151 | 98 | ``` |
152 | 99 |
|
153 | | -You can also use object notation to specify more opts: |
154 | | -```ts |
155 | | -kill( '12345', { |
156 | | - signal: 'SIGKILL', |
157 | | - timeout: 10, // will set up a ten seconds timeout if the killing is not successful |
158 | | -}, () => {}) |
159 | | -``` |
160 | | - |
161 | | -Notice that the nodejs build-in `process.kill()` does not accept number as a signal, you will have to use string format. |
| 100 | +When a `callback` is provided, `kill` polls the process list until the process disappears or the `timeout` (default 30s) is reached. |
162 | 101 |
|
163 | 102 | ## License |
164 | 103 | [MIT](./LICENSE) |
0 commit comments