cli.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*!
  2. * Cluster - cli
  3. * Copyright (c) 2011 LearnBoost <dev@learnboost.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var fs = require('fs')
  10. , Log = require('log');
  11. /**
  12. * Commands.
  13. */
  14. var commands = [];
  15. /**
  16. * Adds a command-line interface to your cluster.
  17. *
  18. * This plugin requires that you use `pidfiles()`
  19. * above `cli()`, so that the pidfile directory
  20. * is exposed.
  21. *
  22. * Examples:
  23. *
  24. * cluster(server)
  25. * .use(cluster.pidfiles())
  26. * .use(cluster.cli())
  27. * .listen(3000);
  28. *
  29. * Once set up our server script serves as both
  30. * the master, and the CLI. For example we may
  31. * still launch the server(s) as shown below.
  32. *
  33. * $ nohup node server.js &
  34. *
  35. * However now we may also utilize commands
  36. * provided by this plugin.
  37. *
  38. * $ node server.js status
  39. *
  40. * master 3281 dead
  41. * worker 0 3282 dead
  42. *
  43. * For more command information use `--help`.
  44. *
  45. * $ node server.js --help
  46. *
  47. * @return {Function}
  48. * @api public
  49. */
  50. exports = module.exports = function(){
  51. return function(master){
  52. requirePIDs(master);
  53. // augment master
  54. master.killall = function(sig){
  55. var pid = master.pidof('master');
  56. try {
  57. // signal master
  58. process.kill(pid, sig);
  59. } catch (err) {
  60. if ('ESRCH' != err.code) throw err;
  61. // signal children individually
  62. master.workerpids().forEach(function(pid){
  63. try {
  64. process.kill(pid, sig);
  65. } catch (err) {
  66. if ('ESRCH' != err.code) throw err;
  67. }
  68. });
  69. }
  70. };
  71. var args = process.argv.slice(2)
  72. , len = commands.length
  73. , command
  74. , arg;
  75. // parse arguments
  76. while (args.length) {
  77. arg = args.shift();
  78. for (var i = 0; i < len; ++i) {
  79. command = commands[i];
  80. if (~command.flags.indexOf(arg)) {
  81. command.callback(master);
  82. master.preventDefault = true;
  83. }
  84. }
  85. }
  86. }
  87. };
  88. /**
  89. * Define command `name` with the given callback `fn(master)`
  90. * and a short `desc`.
  91. *
  92. * @param {String} name
  93. * @param {Function} fn
  94. * @param {String} desc
  95. * @return {Object} exports for chaining
  96. * @api public
  97. */
  98. var define = exports.define = function(name, fn, desc){
  99. commands.push({
  100. flags: name.split(/ *, */)
  101. , desc: desc
  102. , callback: fn
  103. });
  104. return exports;
  105. };
  106. /**
  107. * Report master / worker status based on
  108. * the PID files saved by the pidfiles()
  109. * plugin.
  110. */
  111. define('-s, --status, status', function(master){
  112. var dir = master.pidfiles
  113. , files = fs.readdirSync(dir);
  114. // null signal failed previous
  115. // to this release
  116. if (process.version < 'v0.4.1') {
  117. console.log('status will not work with node < 0.4.1');
  118. console.log('due to SIGTERM globbering the null signal');
  119. process.exit(1);
  120. }
  121. console.log();
  122. // only pids
  123. files.filter(function(file){
  124. return file.match(/\.pid$/);
  125. // check status
  126. }).forEach(function(file){
  127. var name = file.replace('.pid', '')
  128. , pid = master.pidof(name)
  129. , name = name.replace('.', ' ')
  130. , color
  131. , status;
  132. try {
  133. process.kill(pid, 0);
  134. status = 'alive';
  135. color = '36';
  136. } catch (err) {
  137. if ('ESRCH' == err.code) {
  138. color = '31';
  139. status = 'dead';
  140. } else {
  141. throw err;
  142. }
  143. }
  144. console.log(' %s\033[90m %d\033[0m \033[' + color + 'm%s\033[0m'
  145. , name
  146. , pid
  147. , status);
  148. });
  149. console.log();
  150. }, 'Output cluster status');
  151. /**
  152. * Restart workers.
  153. */
  154. define('-r, --restart, restart', function(master){
  155. master.killall('SIGUSR2');
  156. }, 'Restart master by sending the SIGUSR2 signal');
  157. /**
  158. * Graceful shutdown.
  159. */
  160. define('-g, --shutdown, shutdown', function(master){
  161. master.killall('SIGQUIT');
  162. }, 'Graceful shutdown by sending the SIGQUIT signal');
  163. /**
  164. * Hard shutdown.
  165. */
  166. define('-S, --stop, stop', function(master){
  167. master.killall('SIGTERM');
  168. }, 'Hard shutdown by sending the SIGTERM signal');
  169. /**
  170. * Display help information.
  171. */
  172. define('-h, --help, help', function(master){
  173. console.log('\n Usage: node <file> <command>\n');
  174. commands.forEach(function(command){
  175. console.log(' '
  176. + command.flags.join(', ')
  177. + '\n '
  178. + '\033[90m' + command.desc + '\033[0m'
  179. + '\n');
  180. });
  181. console.log();
  182. }, 'Show help information');
  183. /**
  184. * Output cluster version.
  185. */
  186. define('-v, --version', function(master){
  187. console.log(require('../cluster').version);
  188. }, 'Output cluster version');
  189. /**
  190. * Require `pidfiles()` plugin.
  191. *
  192. * @param {Master} master
  193. * @api private
  194. */
  195. function requirePIDs(master) {
  196. if (master.pidfiles) return;
  197. throw new Error('cli() plugin requires pidfiles(), please add pidfiles() before cli()');
  198. }