cdpc
Version:
child process management
395 lines (315 loc) • 8.26 kB
JavaScript
'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
}