UNPKG

the-shepherd

Version:
400 lines (374 loc) 12.2 kB
// Generated by CoffeeScript 2.5.1 (function() { var $, ChildProcess, echo, formatProcess, getChildrenOf, getParentsOf, getPortOwner, getProcessTable, getProcessTree, isChildOf, isPidAlive, isValidPid, is_child_of, killAllChildren, killProcessTree, lsof_argv, process_table, process_table_ts, ps_argv, refresh_process_table, refresh_process_table_if_needed, start, verbose, visitProcessTree, waitForPortOwner, warn; ({$, echo, warn, verbose} = require('../common')); ChildProcess = require("child_process"); // need to support some queries // Wait until a PID (or a child of PID) owns port N ps_argv = ["-eo", "uid,pid,ppid,pcpu,rss,command"]; lsof_argv = ["-Pni"]; // Process table entry: // 0: { pid: 0, ppid: 0, ports: [], pcpu: 0, rss: 0, command: 0 } process_table_ts = 0; process_table = {}; refresh_process_table_if_needed = function(cb) { if (Date.now() - process_table_ts > 1000) { return refresh_process_table(cb); } else { return cb(null, process_table); } }; refresh_process_table = function(cb, ports = true) { var buf, new_table, ps; new_table = {}; ps = ChildProcess.spawn('ps', ps_argv, { shell: false }); buf = ""; ps.stdout.on('data', function(data) { return buf += data.toString('utf8'); }); ps.on('error', cb); ps.on('close', function() { var _, command, i, len, line, lsof, lsof_buf, pcpu, pid, ppid, ref, rss, uid; ref = buf.split(/\n/); for (i = 0, len = ref.length; i < len; i++) { line = ref[i]; [_, uid, pid, ppid, pcpu, rss, ...command] = line.split(/ +/); command = command.join(' '); if ((pid != null ? pid.length : void 0) > 0 && pid !== "PID") { uid = parseInt(uid); pid = parseInt(pid); ppid = parseInt(ppid); pcpu = parseFloat(pcpu); rss = parseInt(rss); new_table[pid] = { uid, pid, ppid, pcpu, rss, command, ports: [] }; } } if (ports) { lsof = ChildProcess.spawn('lsof', lsof_argv, { shell: false }); lsof_buf = ""; lsof.stdout.on('data', function(data) { return lsof_buf += data.toString('utf8'); }); lsof.on('error', cb); return lsof.on('close', function() { var addr, dev, fd, j, len1, mode, name, proto, ref1, sz, type, user; ref1 = lsof_buf.split(/\n/); for (j = 0, len1 = ref1.length; j < len1; j++) { line = ref1[j]; [name, pid, user, fd, type, dev, sz, proto, addr, mode] = line.split(/ +/); if (mode === "(LISTEN)") { if (!(pid in new_table)) { new_table[pid] = { pid, ppid: 0, ports: [], pcpu: 0, rss: 0, command: "???" }; } new_table[pid].ports.push(addr); } } process_table_ts = Date.now(); return cb(null, process_table = new_table); }); } else { return cb(null, new_table); } }); return null; }; isChildOf = function(ppid, pid, cb) { return refresh_process_table_if_needed(function(err, procs) { if (err) { return cb(err); } return cb(null, is_child_of(procs, ppid, pid)); }); }; is_child_of = function(procs, ppid, pid) { var proc, ref; proc = procs[pid]; if (ppid === pid || ppid === proc.ppid) { return true; } if ((ref = proc.ppid) === proc.pid || ref === 0 || ref === 1 || ref === null || ref === (void 0) || ref === '' || ref === '0' || ref === '1') { return false; } return is_child_of(procs, proc.ppid, pid); }; visitProcessTree = (pid, visit) => { return refresh_process_table_if_needed((err, procs) => { var walk; procs[pid] && visit(procs[pid], 0); walk = (cursor, level) => { var _, proc, results; results = []; for (_ in procs) { proc = procs[_]; if (!((proc != null ? proc.ppid : void 0) === cursor)) { continue; } visit(proc, level); results.push(walk(proc.pid, level + 1)); } return results; }; return walk(pid, 1); }); }; formatProcess = (proc) => { return [proc.pid, '[', proc.pcpu.toFixed(1) + "%", $.commaize(Math.round(proc.rss / 1024)) + "MB", ']', $.stringTruncate(proc.command, 100, '...', '/'), proc.ports.length && ('[ ' + proc.ports.join(', ') + ' ]') || ""].join(' '); }; getChildrenOf = (pid, cb) => { return refresh_process_table_if_needed(function(err, procs) { var children, recurse; children = []; recurse = function(_pid) { var _, proc, results; results = []; for (_ in procs) { proc = procs[_]; if (!(proc.ppid === _pid)) { continue; } children.push(proc); // list them all depth-first results.push(recurse(proc.pid)); } return results; }; // start the recursion with our root process recurse(pid); return cb(null, children); }); }; isValidPid = function(pid) { return (pid != null) && isFinite(pid) && (!isNaN(pid)) && pid > 0; }; isPidAlive = function(pid, cb) { getProcessTable(function(err, table) { var _, proc; if (err) { return cb(err, false); } for (_ in table) { proc = table[_]; if (proc.pid === pid) { cb(null, true); } } return cb(null, false); }); return null; }; getParentsOf = (pid, cb) => { return refresh_process_table_if_needed(function(err, procs) { var parents, recurse; parents = []; recurse = function(proc) { var ppid; ppid = proc.ppid; if (isValidPid(ppid)) { parents.push(proc); return recurse(procs[ppid]); } }; // kick start the recursion recurse(procs[pid]); return cb(null, parents); }); }; getPortOwner = (port, cb) => { port = String(port); return refresh_process_table_if_needed((err, procs) => { var _, _port, i, len, proc, ref, this_port; if (err) { return cb(err); } for (_ in procs) { proc = procs[_]; ref = proc.ports; for (i = 0, len = ref.length; i < len; i++) { _port = ref[i]; this_port = _port.split(':')[1]; if (this_port === port) { return cb(null, proc); } } } return cb(null, null); }); }; waitForPortOwner = (target, port, timeout, cb) => { var _timeout, checkAgain, done, exit_handler, target_port; done = (err, proc) => { clearTimeout(_timeout); target.removeListener('exit', exit_handler); if (typeof cb === "function") { cb(err, proc); } return cb = null; }; _timeout = setTimeout((() => { return done('timeout'); }), timeout); target.on('exit', exit_handler = (() => { return done('exit'); })); target_port = String(port); (checkAgain = () => { refresh_process_table((err, procs) => { var _, _port, i, len, proc, ref, this_port; if (err) { return done(err); } for (_ in procs) { proc = procs[_]; ref = proc.ports; for (i = 0, len = ref.length; i < len; i++) { _port = ref[i]; this_port = _port.split(':')[1]; if (this_port === target_port) { verbose(`someone (pid ${proc.pid}) listening on target port`, target_port); if (is_child_of(procs, target.pid, proc.pid)) { return done(null, proc); } } } } (cb != null) && setTimeout(checkAgain, 400); return null; }); return null; })(); return null; }; getProcessTable = refresh_process_table; getProcessTree = (pid, cb) => { var ret; ret = []; getProcessTable((err, table) => { var recurse; recurse = (_pid) => { var _, proc, results; ret.push(_pid); results = []; for (_ in table) { proc = table[_]; if (proc.ppid === _pid) { results.push(recurse(proc.pid)); } } return results; }; recurse(pid); return cb(ret, table); }); return null; }; killAllChildren = (pid, signal, cb) => { var onePass; echo(`killAllChildren(${pid}, ${signal})`); // Do multiple passes over the process table, to make sure the PIDs are really gone return (onePass = () => { return getProcessTree(pid, (tree, table) => { var err, i, len; // filter because we dont want to kill the current pid, which would terminate the loop tree = tree.filter(function(id) { var ref; return (id !== process.pid) && (((ref = table[id]) != null ? ref.command : void 0) != null); }); verbose(`killAllChildren(${pid}, ${signal}):`, tree.map(function(id) { var ref; return `\nWill Kill: ${id} ${(ref = table[id]) != null ? ref.command : void 0}`; }).join("")); if (tree.length < 1) { cb(null); return; } // dont kill ourselves before our time for (i = 0, len = tree.length; i < len; i++) { pid = tree[i]; if (!(pid !== process.pid)) { continue; } verbose(`[process-slim] process.kill(${pid}, ${signal})`); try { process.kill(pid, signal); } catch (error) { err = error; warn(`[process-slim] process.kill(${pid}) failed: ${err}`); } } // do another pass, until the tree is only the root pid return setTimeout(onePass, 1000); }); })(); }; killProcessTree = (pid, signal, cb) => { var onePass; echo(`killProcessTree(${pid}, ${signal}) Starting`); // Do multiple passes over the process table, to make sure the PIDs are really gone return (onePass = () => { return getProcessTree(pid, (tree, table) => { var err, i, len; // filter because we dont want to kill the current pid, which would terminate the loop tree = tree.filter(function(id) { var ref; return (id !== process.pid) && (((ref = table[id]) != null ? ref.command : void 0) != null); }); verbose(`killProcessTree(${pid}, ${signal}):`, tree.map(function(id) { var ref; return `\nWill Kill: ${id} ${(ref = table[id]) != null ? ref.command : void 0}`; }).join("")); if (tree.length < 1) { cb(null); return; } // dont kill ourselves before our time for (i = 0, len = tree.length; i < len; i++) { pid = tree[i]; if (!(pid !== process.pid)) { continue; } verbose(`[process-slim] process.kill(${pid}, ${signal})`); try { process.kill(pid, signal); } catch (error) { err = error; warn(`[process-slim] process.kill(${pid}) failed: ${err}`); } } // do another pass, until the tree is only the root pid return setTimeout(onePass, 1000); }); })(); }; Object.assign(module.exports, {formatProcess, waitForPortOwner, visitProcessTree, getPortOwner, isChildOf, getProcessTable, killProcessTree, getParentsOf, getChildrenOf, killAllChildren, isPidAlive}); if (require.main === module) { start = Date.now(); getProcessTree(27824, function(list) { var i, len, pid, results; results = []; for (i = 0, len = list.length; i < len; i++) { pid = list[i]; results.push(console.log(pid)); } return results; }); } }).call(this);