strong-supervisor
Version:
supervisor and monitor for node.js applications
334 lines (280 loc) • 7.97 kB
JavaScript
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: strong-supervisor
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
/* eslint no-process-exit:0 */
;
// Exit on loss of parent process, if it had established an ipc control channel.
// We do this ASAP because we don't want child processes to leak, outliving
// their parent. If the parent has not established an 'ipc' channel to us, this
// will be a no-op, the disconnect event will never occur.
process.on('disconnect', function() {
process.exit(2);
});
var argv = process.argv;
var client = require('strong-control-channel/client');
var debug = require('../lib/debug')('runctl');
var fs = require('fs');
var npmls = require('strong-npm-ls');
var Parser = require('posix-getopt').BasicParser;
var path = require('path');
var util = require('util');
var version = require('../package.json').version;
var $0 = process.env.CMD ? process.env.CMD : path.basename(argv[1]);
var ADDR = 'runctl';
function error() {
console.error.apply(console, arguments);
process.exit(1);
}
function okay() {
console.log('OK');
process.exit(0);
}
var HELP = fs.readFileSync(require.resolve('./sl-runctl.txt'), 'utf-8')
.replace(/%MAIN%/g, $0)
.replace(/%ADDR%/g, ADDR);
var parser = new Parser([
':v(version)',
'h(help)',
'p:(path)',
'p:(port)',
'C:(control)',
].join(''), argv);
var option;
while ((option = parser.getopt()) !== undefined) {
switch (option.option) {
case 'v':
console.log(version);
process.exit(0);
break;
case 'h':
console.log(HELP);
process.exit(0);
break;
case 'p':
case 'C':
ADDR = option.optarg;
break;
default:
error('Invalid usage (near option \'%s\'), try `%s --help`.',
option.optopt, $0);
}
}
var optind = parser.optind();
var command = argv[optind++] || 'status';
var request = {}; // {cmd: , ...}: request to send
var display = okay; // override for command specific success message
function requiredArg() {
var arg = argv[optind++];
if (arg != null) return arg;
error('Invalid usage, missing argument for `%s`, try `%s --help`.',
command, $0);
}
function optionalArg(default_) {
var arg = argv[optind++];
return arg == null ? default_ : arg;
}
var commands = {
status: requestStatus,
'set-size': requestSetSize,
stop: requestStop,
restart: requestRestart,
ls: requestLs,
disconnect: requestDisconnect,
fork: requestFork,
'objects-start': requestObjectsStart,
'objects-stop': requestObjectsStop,
'cpu-start': requestCpuStart,
'cpu-stop': requestCpuStop,
'heap-snapshot': requestHeapSnapshot,
patch: requestPatch,
'env-get': requestEnvGet,
'env-set': requestEnvSet,
'env-unset': requestEnvUnSet,
'dbg-start': requestDebuggerStart,
'dbg-stop': requestDebuggerStop,
'dbg-status': requestDebuggerStatus,
};
var action = commands[command] || invalidCommand;
action();
function invalidCommand() {
error('Invalid command `%s`, try `%s --help`.', command, $0);
}
function requestStatus() {
request.cmd = 'status';
display = displayStatusResponse;
}
function displayStatusResponse(rsp) {
if (rsp.master) {
console.log('master pid: %d', rsp.master.pid);
}
console.log('worker count:', rsp.workers.length);
for (var i = 0; i < rsp.workers.length; i++) {
var worker = rsp.workers[i];
var id = worker.id;
delete worker.id;
console.log('worker id', id + ':', worker);
}
}
function requestSetSize() {
request.cmd = 'set-size';
request.size = requiredArg();
}
function requestStop() {
request.cmd = 'stop';
}
function requestRestart() {
request.cmd = 'restart';
}
function requestLs() {
var depth = +optionalArg(Number.MAX_VALUE);
request.cmd = 'npm-ls';
request.depth = depth;
display = function displayLsResponse(rsp) {
console.log(npmls.printable(rsp, depth));
};
}
function requestDisconnect() {
request.cmd = 'disconnect';
}
function requestFork() {
request.cmd = 'fork';
display = console.log;
}
function requestObjectsStart() {
request.cmd = 'start-tracking-objects';
request.target = requiredArg();
}
function requestObjectsStop() {
request.cmd = 'stop-tracking-objects';
request.target = requiredArg();
}
function requestCpuStart() {
request.cmd = 'start-cpu-profiling';
request.target = requiredArg();
request.timeout = optionalArg(0) | 0;
request.stallout = optionalArg(0) | 0;
display = function() {
console.log('Profiler started, use cpu-stop to get profile.');
};
}
function requestCpuStop() {
var target = requiredArg();
var name = optionalArg(util.format('node.%s', target));
request.cmd = 'stop-cpu-profiling';
request.target = target;
// .cpuprofile extention Required by Chrome
var filePath = path.resolve(name + '.cpuprofile');
display = function(res) {
if (!res.error) {
try {
fs.writeFileSync(filePath, res.profile);
} catch (err) {
res.error = err.message;
}
}
if (res.error) {
error('Unable to write CPU profile to `%s`: %s', filePath, res.error);
return;
}
console.log('CPU profile written to `%s`, load into Chrome Dev Tools',
filePath);
};
}
function requestHeapSnapshot() {
var target = requiredArg();
var name = optionalArg(util.format('node.%s', target));
request.cmd = 'heap-snapshot';
request.target = target;
var filePath = path.resolve(name + '.heapsnapshot');
display = function(res) {
if (!res.error) {
try {
fs.writeFileSync(filePath, res.profile);
} catch (err) {
res.error = err.message;
}
}
if (res.error) {
error('Unable to write heap to `%s`: %s', filePath, res.error);
return;
}
console.log('Heap written to `%s`, load into Chrome Dev Tools',
filePath);
};
}
function requestPatch() {
var target = requiredArg();
var file = requiredArg();
request.cmd = 'patch';
request.target = target;
request.patch = JSON.parse(fs.readFileSync(file));
}
function requestEnvGet() {
request.target = optionalArg(0) | 0;
request.cmd = 'env-get';
display = function dumpEnv(rsp) {
if (rsp.error || !rsp.env) {
console.error('Unable to get env:', rsp.error || 'unknown');
} else {
Object.keys(rsp.env).sort().forEach(function(k) {
console.log('%s=%s', k, rsp.env[k]);
});
}
};
}
function requestEnvSet() {
request.cmd = 'env-set';
request.env = {};
argv.slice(optind++).forEach(function(kv) {
var pair = kv.split('=').map(function(p) {
return p.trim();
});
if (pair[0]) {
request.env[pair[0]] = pair[1];
}
});
}
function requestEnvUnSet() {
request.cmd = 'env-unset';
request.env = argv.slice(optind++);
}
function requestDebuggerStart() {
request.cmd = 'dbg-start';
request.target = requiredArg();
display = function debuggerStarted(rsp) {
if (rsp.error || !rsp.port) {
console.error('Unable to start the debugger:', rsp.error || 'unknown');
} else {
console.log('Debugger listening on port %s', rsp.port);
}
};
}
function requestDebuggerStop() {
request.cmd = 'dbg-stop';
request.target = requiredArg();
}
function requestDebuggerStatus() {
request.cmd = 'dbg-status';
request.target = requiredArg();
display = function printDebuggerStatus(rsp) {
if (rsp.status.running) {
console.log('Debugger listening on port %s', rsp.status.port);
} else {
console.log('Debugger is disabled.');
}
};
}
debug('addr: %j, request: %j', ADDR, request);
client.request(ADDR, request, response);
function response(er, rsp) {
if (er) {
error('Communication error (%s), check master is listening', er.message);
}
if (rsp.error) {
error('Command %s failed with: %s', request.cmd, rsp.error);
}
display(rsp);
process.exit(0);
}