Skip to content

Commit

Permalink
feat: provide tree API
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed Mar 26, 2024
1 parent b267d07 commit 611065d
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 14 deletions.
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* [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)
* [x] Provides promisified responses
* [ ] Brings sync API
* [ ] Builds a process tree
* [x] Builds a process tree

## Install
```bash
Expand Down Expand Up @@ -79,6 +79,38 @@ lookup({
})
```

### tree()
Returns a child processes list by the specified parent `pid`. Some kind of shortcut for `lookup({ppid: pid})`.
```ts
import { tree } from '@webpod/ps'

const children = await tree(123)
/**
[
{pid: 124, ppid: 123},
{pid: 125, ppid: 123}
]
*/
```

To obtain all nested children, set `recursive` option to `true`:
```ts
const children = await tree({pid: 123, recursive: true})
/**
[
{pid: 124, ppid: 123},
{pid: 125, ppid: 123},
{pid: 126, ppid: 124},
{pid: 127, ppid: 124},
{pid: 128, ppid: 124},
{pid: 129, ppid: 125},
{pid: 130, ppid: 125},
]
*/
```

### kill()
Eliminates the process by its `pid`.

Expand Down
6 changes: 3 additions & 3 deletions src/main/ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { kill, lookup } from './ps.ts'
import { kill, lookup, tree } from './ps.ts'

export type * from './ps.ts'
export { kill, lookup } from './ps.ts'
export default { lookup, kill }
export { kill, lookup, tree } from './ps.ts'
export default { lookup, kill, tree }
36 changes: 33 additions & 3 deletions src/main/ts/ps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type TPsLookupQuery = {
pid?: number | string | (string | number)[]
command?: string
arguments?: string
ppid?: string
ppid?: number | string
psargs?: string | string[]
}

Expand All @@ -52,7 +52,7 @@ export type TPsNext = (err?: any, data?: any) => void
* @return {Object}
*/
export const lookup = (query: TPsLookupQuery = {}, cb: TPsLookupCallback = noop) => {
const { promise, resolve, reject } = makeDeferred()
const { promise, resolve, reject } = makeDeferred<TPsLookupEntry[]>()
const { psargs = ['-lx'] } = query // add 'lx' as default ps arguments, since the default ps output in linux like "ubuntu", wont include command arguments
const args = typeof psargs === 'string' ? psargs.split(/\s+/) : psargs
const extract = IS_WIN ? extractWmic : identity
Expand Down Expand Up @@ -94,7 +94,7 @@ export const parseProcessList = (output: string, query: TPsLookupQuery = {}) =>
const filter = (['command', 'arguments', 'ppid'] as Array<TFilterKeys>)
.reduce((m, k) => {
const param = query[k]
if (param) m[k] = new RegExp(param, 'i')
if (param) m[k] = new RegExp(param + '', 'i')
return m
}, {} as Record<TFilterKeys, RegExp>)

Expand All @@ -116,6 +116,36 @@ export const extractWmic = (stdout: string): string => {
return _stdout.join(SystemEOL)
}

export type TPsTreeOpts = {
pid: string | number
recursive?: boolean
}

export const pickTree = (list: TPsLookupEntry[], pid: string | number, recursive = false): TPsLookupEntry[] => {
const children = list.filter(p => p.ppid === pid + '')
return [
...children,
...children.flatMap(p => recursive ? pickTree(list, p.pid, true) : [])
]
}

export const tree = async (opts: string | number | TPsTreeOpts, cb: TPsLookupCallback = noop): Promise<TPsLookupEntry[]> => {
if (typeof opts === 'string' || typeof opts === 'number') {
return tree({ pid: opts }, cb)
}

try {
const {pid, recursive = false} = opts
const list = pickTree(await lookup(), pid, recursive)

cb(null, list)
return list
} catch (err) {
cb(err)
throw err
}
}

/**
* Kill process
* @param pid
Expand Down
22 changes: 19 additions & 3 deletions src/test/legacy/node_process_for_test.cjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
var cp = require('node:child_process')
var process = require('node:process')
var now = Date.now();
console.log('[child] child process start!');
var argv = process.argv.slice(2);
var marker = argv[0]
var fork = +argv.find(v => v.startsWith('--fork='))?.slice(7) || 0;
var depth = +argv.find(v => v.startsWith('--depth='))?.slice(8) || 0;

function doSomething() {
return null;
while(depth) {
depth--
const _fork = fork
while (fork) {
fork--
cp.fork(__filename, [marker, `--depth=${depth}`, `--fork=${_fork}`])
}
}

console.log('[child] child process start!', 'argv=', argv);

setInterval(function () {
doSomething();
}, 50);

function doSomething() {
return null;
}
4 changes: 3 additions & 1 deletion src/test/ts/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as assert from 'node:assert'
import { describe, it } from 'node:test'
import ps, { kill, lookup } from '../../main/ts/index.ts'
import ps, { kill, lookup, tree } from '../../main/ts/index.ts'

describe('index', () => {
it('has proper exports', () => {
assert.equal(ps.lookup, lookup)
assert.equal(ps.kill, kill)
assert.equal(ps.tree, tree)
assert.equal(typeof lookup, 'function')
assert.equal(typeof kill, 'function')
assert.equal(typeof tree, 'function')
})
})
27 changes: 24 additions & 3 deletions src/test/ts/ps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { describe, it, before, after } from 'node:test'
import process from 'node:process'
import * as cp from 'node:child_process'
import * as path from 'node:path'
import { kill, lookup } from '../../main/ts/ps.ts'
import { kill, lookup, tree } from '../../main/ts/ps.ts'

const __dirname = new URL('.', import.meta.url).pathname
const marker = Math.random().toString(16).slice(2)
const testScript = path.resolve(__dirname, '../legacy/node_process_for_test.cjs')
const testScriptArgs = ['--foo', '--bar', Math.random().toString(16).slice(2)]
const testScriptArgs = [marker, '--foo', '--bar']

describe('lookup()', () => {
let pid: number
Expand Down Expand Up @@ -35,7 +36,7 @@ describe('lookup()', () => {
})

it('filters by args', async () => {
const list = await lookup({ arguments: testScriptArgs[2] })
const list = await lookup({ arguments: marker })

assert.equal(list.length, 1)
assert.equal(list[0].pid, pid)
Expand Down Expand Up @@ -63,3 +64,23 @@ describe('kill()', () => {
assert.equal(cheked, true)
})
})

describe('tree()', () => {
it('returns 1st level child', async () => {
const pid = cp.fork(testScript, [...testScriptArgs, '--fork=1', '--depth=2']).pid as number
await new Promise(resolve => setTimeout(resolve, 2000)) // wait for child process to spawn

const list = await lookup({ arguments: marker })
const children = await tree(pid)
const childrenAll = await tree({pid, recursive: true})

await Promise.all(list.map(p => kill(p.pid)))
await kill(pid)

assert.equal(children.length, 1)
assert.equal(childrenAll.length, 2)
assert.equal(list.length, 3)

assert.equal((await lookup({ arguments: marker })).length, 0)
})
})

0 comments on commit 611065d

Please sign in to comment.