the-shepherd
Version:
Control a herd of wild processes.
400 lines (374 loc) • 12.2 kB
JavaScript
// 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);