/*! * Cluster - cli * Copyright (c) 2011 LearnBoost * MIT Licensed */ /** * Module dependencies. */ var fs = require('fs') , Log = require('log'); /** * Commands. */ var commands = []; /** * Adds a command-line interface to your cluster. * * This plugin requires that you use `pidfiles()` * above `cli()`, so that the pidfile directory * is exposed. * * Examples: * * cluster(server) * .use(cluster.pidfiles()) * .use(cluster.cli()) * .listen(3000); * * Once set up our server script serves as both * the master, and the CLI. For example we may * still launch the server(s) as shown below. * * $ nohup node server.js & * * However now we may also utilize commands * provided by this plugin. * * $ node server.js status * * master 3281 dead * worker 0 3282 dead * * For more command information use `--help`. * * $ node server.js --help * * @return {Function} * @api public */ exports = module.exports = function(){ return function(master){ requirePIDs(master); // augment master master.killall = function(sig){ var pid = master.pidof('master'); try { // signal master process.kill(pid, sig); } catch (err) { if ('ESRCH' != err.code) throw err; // signal children individually master.workerpids().forEach(function(pid){ try { process.kill(pid, sig); } catch (err) { if ('ESRCH' != err.code) throw err; } }); } }; var args = process.argv.slice(2) , len = commands.length , command , arg; // parse arguments while (args.length) { arg = args.shift(); for (var i = 0; i < len; ++i) { command = commands[i]; if (~command.flags.indexOf(arg)) { command.callback(master); master.preventDefault = true; } } } } }; /** * Define command `name` with the given callback `fn(master)` * and a short `desc`. * * @param {String} name * @param {Function} fn * @param {String} desc * @return {Object} exports for chaining * @api public */ var define = exports.define = function(name, fn, desc){ commands.push({ flags: name.split(/ *, */) , desc: desc , callback: fn }); return exports; }; /** * Report master / worker status based on * the PID files saved by the pidfiles() * plugin. */ define('-s, --status, status', function(master){ var dir = master.pidfiles , files = fs.readdirSync(dir); // null signal failed previous // to this release if (process.version < 'v0.4.1') { console.log('status will not work with node < 0.4.1'); console.log('due to SIGTERM globbering the null signal'); process.exit(1); } console.log(); // only pids files.filter(function(file){ return file.match(/\.pid$/); // check status }).forEach(function(file){ var name = file.replace('.pid', '') , pid = master.pidof(name) , name = name.replace('.', ' ') , color , status; try { process.kill(pid, 0); status = 'alive'; color = '36'; } catch (err) { if ('ESRCH' == err.code) { color = '31'; status = 'dead'; } else { throw err; } } console.log(' %s\033[90m %d\033[0m \033[' + color + 'm%s\033[0m' , name , pid , status); }); console.log(); }, 'Output cluster status'); /** * Restart workers. */ define('-r, --restart, restart', function(master){ master.killall('SIGUSR2'); }, 'Restart master by sending the SIGUSR2 signal'); /** * Graceful shutdown. */ define('-g, --shutdown, shutdown', function(master){ master.killall('SIGQUIT'); }, 'Graceful shutdown by sending the SIGQUIT signal'); /** * Hard shutdown. */ define('-S, --stop, stop', function(master){ master.killall('SIGTERM'); }, 'Hard shutdown by sending the SIGTERM signal'); /** * Display help information. */ define('-h, --help, help', function(master){ console.log('\n Usage: node \n'); commands.forEach(function(command){ console.log(' ' + command.flags.join(', ') + '\n ' + '\033[90m' + command.desc + '\033[0m' + '\n'); }); console.log(); }, 'Show help information'); /** * Output cluster version. */ define('-v, --version', function(master){ console.log(require('../cluster').version); }, 'Output cluster version'); /** * Require `pidfiles()` plugin. * * @param {Master} master * @api private */ function requirePIDs(master) { if (master.pidfiles) return; throw new Error('cli() plugin requires pidfiles(), please add pidfiles() before cli()'); }