UNPKG

cdpc

Version:

child process management

395 lines (315 loc) 8.26 kB
'use strict' const fs = require('node:fs') let fsp = fs.promises let name_preg = /^[0-9]+$/ function getProcList() { let flist = fs.readdirSync('/proc', {withFileTypes: true}) let rlist = [] flist.filter(f => { if (f.isDirectory() && name_preg.test(f.name)) { if (f.name !== '1' && f.name !== '0') rlist.push(f.name) } }) return rlist } let _stat_index = { pid : 0, comm : 1, state : 2, ppid : 3, pgrp : 4, session : 5 } /** * 进程目录中存在comm,保存命令的名称,cmdline保存完整命令格式 * man 5 proc 参看stat部分 * pid * comm 此部分表示命令名称,命令有可能会有空格,会使用()包含 * 解析过程不能简单的使用空格分割 */ function parse_linux_stat(pid) { try { let comm = fs.readFileSync(`/proc/${pid}/comm`, {encoding: 'utf8'}) let stat = fs.readFileSync(`/proc/${pid}/stat`, {encoding: 'utf8'}) let stlines = stat.replace(comm, '$').split(' ').filter(p => p.length > 0) let p = {} let val for (let k in _stat_index) { val = stlines[_stat_index[k]] if (name_preg.test(val)) { p[k] = parseInt(val) } else { p[k] = val } } p.comm = p.name = comm p.cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, {encoding: 'utf8'}) p.cmdsplit = p.cmdline.split('\x00').map(p => p.trim()).filter(p => p.length > 0) return p } catch (err) { return false } return false } function parse_ids(idtext, type = 'uid') { let ids = idtext.split(/\s+/).filter(p => p.length > 0) return [ parseInt(ids[0]), parseInt(ids[1]) ] } /** * 类Unix系列(主要是FreeBSD、OpenBSD等)没有/proc,似乎可以通过mount linprocfs /proc 进行兼容linux的挂载。 * /proc/PID/status就是为了方便程序解析而提供的。 */ async function async_parse_linux_status(pid, obj=null) { try { let stdata = await fsp.readFile(`/proc/${pid}/status`, {encoding: 'utf8'}) let stlines = stdata.split('\n').map(d => { let rd = d.trim() let key_ind = d.indexOf(':') let key = d.substring(0, key_ind).toLowerCase() let val = d.substring(key_ind+1).trim() return [key, val] }) let p = obj || {} let uids, gids stlines.forEach(st => { switch (st[0]) { case 'name': p.comm = st[1] p.name = st[1] break case 'vmrss': p.rss = parseInt(st[1]) break case 'vmpte': p.pte = parseInt(st[1]) break case 'pid': case 'ppid': p[st[0]] = parseInt(st[1]) break case 'state': p[st[0]] = st[1] break case 'nssid': p.session = parseInt(st[1]) break case 'nspgid': p.pgrp = parseInt(st[1]) break case 'uid': uids = parse_ids(st[1]) p.uid = uids[0] p.euid = uids[1] break case 'gid': gids = parse_ids(st[1]) p.gid = gids[0] p.egid = gids[1] break } }) let cmdline = await fsp.readFile(`/proc/${pid}/cmdline`, {encoding: 'utf8'}) p.cmdline = cmdline p.cmdsplit = cmdline.split('\x00').map(p => p.trim()).filter(p => p.length > 0) return p } catch (err) { return false } return false } function parse_linux_status(pid) { try { let stdata = fs.readFileSync(`/proc/${pid}/status`, {encoding: 'utf8'}) let stlines = stdata.split('\n').map(d => { let rd = d.trim() let key_ind = d.indexOf(':') let key = d.substring(0, key_ind).toLowerCase() let val = d.substring(key_ind+1).trim() return [key, val] }) let p = {} let uids, gids stlines.forEach(st => { switch (st[0]) { case 'name': p.comm = st[1] p.name = st[1] break case 'vmrss': p.rss = parseInt(st[1]) break case 'pid': case 'ppid': p[st[0]] = parseInt(st[1]) break case 'state': p[st[0]] = st[1] break case 'nssid': p.session = parseInt(st[1]) break case 'nspgid': p.pgrp = parseInt(st[1]) break case 'uid': uids = parse_ids(st[1]) p.uid = uids[0] p.euid = uids[1] break case 'gid': gids = parse_ids(st[1]) p.gid = gids[0] p.egid = gids[1] break } }) let cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, {encoding: 'utf8'}) p.cmdline = cmdline p.cmdsplit = cmdline.split('\x00').map(p => p.trim()).filter(p => p.length > 0) return p } catch (err) { return false } return false } /** * * 提供一个类似于ps命令的功能,输出结构提供进程列表,查询等功能。 * 最主要的一个功能是直接提供进程树结构,并可以查找一个进程下所有子进程。 * * */ function getAllChilds(pid, just_pid=false) { let childs = [] let chs = [] let pids = this.ptable[pid] || [] let p while (true) { chs = [] if (pids.length === 0) break for (let a of pids) { childs.push(just_pid ? a.pid : a) if (this.ptable[a.pid]) { chs.push(...this.ptable[a.pid]) } } pids = chs } return childs } function isChild(ppid, pid) { if (!this.table[pid] || !this.table[ppid]) return false let p = this.table[pid] while (p && this.table[p.ppid]) { if (p.ppid === ppid) { return this.table[p.ppid] || true } p = this.table[p.ppid] } return false } function getChildsByCMDLine(cmdline) { let csplit = cmdline.split('\x00') let args = csplit.slice(1).sort((a, b) => { return a > b ? 1 : -1 }) let cline = csplit[0] + ' ' + args.join(' ') let cmds = [] for (let k in this.table) { let p = this.table[k] let args = p.cmdsplit.slice(1).sort((a,b) => { return a > b ? 1 : -1 }) let p_cmdline = `${p.cmdsplit[0]} ${args.join(' ')}` if (p_cmdline === cline) { cmds.push(p) } } return cmds } /** * 根据名称和参数查找 * @param {object} nametable * @param {array} args * */ function getChildsByCMDArgs(nametable, args) { let argstable = {} let count = args.length args.forEach(x => { argstable[x] = true }) let cmds = [] for (let k in this.table) { let p = this.table[k] if (!nametable[p.cmdsplit[0]]) continue let args = p.cmdsplit.slice(1) let ac = 0 args.forEach(x => { argstable[x] && (ac++) }) ;(count <= 0 || ac >= count) && cmds.push(p) } return cmds } function getChildsByCOMM(name) { let cmds = [] for (let k in this.table) { let p = this.table[k] if (p.comm === name) { cmds.push(p) } } return cmds } function initProcessTreeData() { return { table: {}, ptable: {}, getAllChilds: getAllChilds, //检测pid是否为ppid的子进程 isChild: isChild, getChildsByCMDLine: getChildsByCMDLine, getChildsByCOMM: getChildsByCOMM, getChildsByCMDArgs: getChildsByCMDArgs } } function nps() { let processData = initProcessTreeData() if (process.platform !== 'linux') return processData let rlist = getProcList() let p, cur for (let pid of rlist) { p = parse_linux_status(pid) if (!p) continue processData.table[pid] = p if (!processData.ptable[p.ppid]) { processData.ptable[p.ppid] = [] } processData.ptable[p.ppid].push(p) } return processData } async function anps() { let processData = initProcessTreeData() if (process.platform !== 'linux') return processData let rlist = getProcList() let p, cur for (let pid of rlist) { p = await async_parse_linux_status(pid) if (!p) continue processData.table[pid] = p if (!processData.ptable[p.ppid]) { processData.ptable[p.ppid] = [] } processData.ptable[p.ppid].push(p) } return processData } module.exports = { nps, anps, parse_linux_status, async_parse_linux_status }