├── examples ├── auto-save.js ├── killfast.js ├── modulechild.js ├── env_args.js ├── echo.rb ├── bashscript.sh ├── echo.coffee ├── stop-after5.js ├── killslow.js ├── moduleparent.js ├── echo.php ├── kill-slow.js ├── env.js ├── killtoofast.js ├── child.js-pm2.json ├── child.js ├── sendmsg.js ├── throw.js ├── kill-not-so-fast.js ├── cwd.js ├── child-pm2.json ├── require.js ├── child2.js ├── harmony.js ├── human_event.js ├── leak.js ├── args.js ├── exec_watch.json ├── json.js ├── echokill.js ├── echo.js ├── expose_method.js ├── child-pm2-v2.json ├── graceful-exit.js ├── exit.js ├── custom_action_with_params.js ├── custom_action.js ├── keymetrics-load.js ├── auto-bench.js ├── load-me.js ├── http-trace.js └── keymetrics-test.js ├── .gitattributes ├── .gitignore ├── index.js ├── test ├── fixtures │ ├── path1 │ │ ├── iminpath1.js │ │ └── path2 │ │ │ └── iminpath2.js │ ├── throw.js │ ├── bashscript.sh │ ├── cron.js │ ├── echo.coffee │ ├── killnotsofast.js │ ├── env.js │ ├── echo-pm2.json │ ├── echoto-pm2.json │ ├── echo-to-pm2.json │ ├── harmony.json │ ├── quit.js │ ├── killtoofast.js │ ├── max-mem.json │ ├── echo.js │ ├── server-watch.js │ ├── server-watch.bak.js │ ├── server.js │ ├── events │ │ ├── custom_action.js │ │ ├── own_event.js │ │ └── custom_action_with_params.js │ ├── signal.js │ ├── multi-echo.json │ ├── big-array.js │ ├── child.js │ ├── cluster-pm2.json │ ├── network.js │ ├── harmony.js │ ├── args.js │ ├── env.json │ ├── env-refreshed.json │ ├── no_cwd_change.json │ ├── change_cwd.json │ ├── all.json │ ├── big-array-es6.js │ ├── graceful-exit-no-listen.js │ ├── graceful-exit-send.js │ ├── graceful-exit.js │ ├── all2.json │ └── ecosystem.json ├── mocha.opts ├── programmatic │ ├── child.js │ ├── cwd.js │ ├── monit.mocha.js │ ├── interactor.daemonizer.mocha.js │ ├── satan.mocha.js │ ├── deprecated │ │ └── interactor.mocha.js │ ├── god.mocha.js │ └── programmatic.js ├── benchmarks │ ├── result.monit │ ├── monit-daemon.sh │ └── monit.sh ├── bash │ ├── cli-ux.sh │ ├── right-exit-code.sh │ ├── interact.sh │ ├── gracefulReload3.sh │ ├── gracefulReload2.sh │ ├── json_file.sh │ ├── fork.sh │ ├── env-refresh.sh │ ├── reset.sh │ ├── gracefulReload.sh │ ├── harmony.sh │ ├── log-custom.sh │ ├── include.sh │ ├── log-reload.sh │ ├── signal.sh │ ├── infinite_loop.sh │ ├── reload.sh │ ├── cli2.sh │ ├── misc.sh │ ├── watch.sh │ └── cli.sh ├── helpers │ ├── plan.js │ └── apps.js ├── index.sh ├── README.md └── main.sh ├── pres ├── pm2-v2.png ├── Drawing1.png ├── pm2-htop.png ├── pm2-list.png ├── pm2-logs.png ├── pm2-monit.png ├── pm2.20d3ef.png ├── pm2-resurect.png └── top-logo-wo.png ├── apps ├── no-name-echo.json ├── auto-kill-echo.json ├── default-path-echo.json ├── killfast.json ├── args.json ├── cron-pm2.json ├── multi-pm2.json ├── cluster-pm2.json ├── echo-pm2.json ├── all-pm2.json └── env-pm2.json ├── lib ├── interpreter.json ├── Interactor │ ├── Tools.js │ ├── Cipher.js │ ├── WatchDog.js │ ├── Filter.js │ ├── ReverseInteractor.js │ ├── Daemon.js │ └── PushInteractor.js ├── scripts │ ├── pm2.service │ ├── pm2 │ ├── pm2-init.sh │ ├── pm2-init-amazon.sh │ └── pm2-init-centos.sh ├── custom_options.sh ├── sample.json ├── HttpInterface.js ├── Log.js ├── God │ ├── ClusterMode.js │ ├── ForkMode.js │ ├── Methods.js │ └── Reload.js ├── Watcher.js ├── Monit.js ├── ProcessContainer.js ├── Common.js └── CliUx.js ├── .travis.yml ├── scripts ├── postinstall.sh ├── ping.js ├── preinstall.sh └── kill.js ├── .editorconfig ├── CONTRIBUTING.md ├── constants.js ├── package.json └── CHANGELOG.md /examples/auto-save.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /examples/killfast.js: -------------------------------------------------------------------------------- 1 | 2 | process.exit(1); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.log 4 | *.pid 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/CLI.js'); 2 | -------------------------------------------------------------------------------- /examples/modulechild.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | console.log(module.parent); 4 | -------------------------------------------------------------------------------- /examples/env_args.js: -------------------------------------------------------------------------------- 1 | 2 | console.log(process.env.PASSED_VARIABLE); 3 | -------------------------------------------------------------------------------- /test/fixtures/path1/iminpath1.js: -------------------------------------------------------------------------------- 1 | 2 | console.log(process.cwd()); 3 | -------------------------------------------------------------------------------- /test/fixtures/throw.js: -------------------------------------------------------------------------------- 1 | 2 | throw new Error('Exit error message'); 3 | -------------------------------------------------------------------------------- /examples/echo.rb: -------------------------------------------------------------------------------- 1 | 2 | while 1 do 3 | puts "lol" 4 | sleep 1 5 | end 6 | -------------------------------------------------------------------------------- /pres/pm2-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2-v2.png -------------------------------------------------------------------------------- /test/fixtures/path1/path2/iminpath2.js: -------------------------------------------------------------------------------- 1 | 2 | console.log(process.cwd()); 3 | -------------------------------------------------------------------------------- /pres/Drawing1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/Drawing1.png -------------------------------------------------------------------------------- /pres/pm2-htop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2-htop.png -------------------------------------------------------------------------------- /pres/pm2-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2-list.png -------------------------------------------------------------------------------- /pres/pm2-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2-logs.png -------------------------------------------------------------------------------- /pres/pm2-monit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2-monit.png -------------------------------------------------------------------------------- /pres/pm2.20d3ef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2.20d3ef.png -------------------------------------------------------------------------------- /examples/bashscript.sh: -------------------------------------------------------------------------------- 1 | while true; do 2 | ls -l 3 | sleep 5 4 | done 5 | 6 | -------------------------------------------------------------------------------- /pres/pm2-resurect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/pm2-resurect.png -------------------------------------------------------------------------------- /pres/top-logo-wo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tes/PM2/master/pres/top-logo-wo.png -------------------------------------------------------------------------------- /apps/no-name-echo.json: -------------------------------------------------------------------------------- 1 | { 2 | "script" : "examples/echo.js", 3 | "max" : "1" 4 | } 5 | -------------------------------------------------------------------------------- /examples/echo.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | setInterval (-> console.log 'ok'), 500 -------------------------------------------------------------------------------- /examples/stop-after5.js: -------------------------------------------------------------------------------- 1 | 2 | setTimeout(function() { 3 | process.exit(0); 4 | }, 5000); 5 | -------------------------------------------------------------------------------- /test/fixtures/bashscript.sh: -------------------------------------------------------------------------------- 1 | while true; do 2 | ls -l 3 | sleep 5 4 | done 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/cron.js: -------------------------------------------------------------------------------- 1 | 2 | setInterval(function() { 3 | console.log('ok'); 4 | }, 500); 5 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --reporter spec 3 | --timeout 30000000 4 | --slow 300 -------------------------------------------------------------------------------- /test/fixtures/echo.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | setInterval (-> console.log 'ok'), 500 -------------------------------------------------------------------------------- /examples/killslow.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | setTimeout(function() { 4 | throw new Error('ok'); 5 | }, 1100); 6 | -------------------------------------------------------------------------------- /test/fixtures/killnotsofast.js: -------------------------------------------------------------------------------- 1 | setInterval(function() { 2 | console.log('ALIVE'); 3 | }, 500); 4 | -------------------------------------------------------------------------------- /examples/moduleparent.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var a =require('./modulechild.js'); 4 | console.log(module.children); 5 | -------------------------------------------------------------------------------- /test/fixtures/env.js: -------------------------------------------------------------------------------- 1 | setInterval(function() { 2 | console.log(process.env.TEST_VARIABLE); 3 | }, 100); 4 | -------------------------------------------------------------------------------- /examples/echo.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/echo-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "echo", 3 | "script" : "echo.js", 4 | "options": [""] 5 | } 6 | -------------------------------------------------------------------------------- /examples/kill-slow.js: -------------------------------------------------------------------------------- 1 | 2 | setTimeout(function() { 3 | console.log('exit'); 4 | process.exit(1); 5 | }, 1000); 6 | -------------------------------------------------------------------------------- /test/fixtures/echoto-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "echoto", 3 | "script" : "echoto.js", 4 | "options": [""] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/echo-to-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "echo-to", 3 | "script" : "echo-to.js", 4 | "options": [""] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/harmony.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "ES6", 3 | "script" : "harmony.js", 4 | "node_args" : "--harmony" 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/quit.js: -------------------------------------------------------------------------------- 1 | 2 | console.log('HEY!'); 3 | 4 | setTimeout(function() { 5 | process.exit(1); 6 | }, 3000); 7 | -------------------------------------------------------------------------------- /apps/auto-kill-echo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "auto-kill", 3 | "script" : "./examples/echokill.js", 4 | "max" : "10" 5 | } 6 | -------------------------------------------------------------------------------- /apps/default-path-echo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "echo-default", 3 | "script" : "examples/echo.js", 4 | "max" : "1" 5 | } 6 | -------------------------------------------------------------------------------- /examples/env.js: -------------------------------------------------------------------------------- 1 | 2 | setInterval(function() { 3 | console.log('env TEST_VARIABLE = ', process.env.NODE_ENV); 4 | }, 1000); 5 | -------------------------------------------------------------------------------- /test/fixtures/killtoofast.js: -------------------------------------------------------------------------------- 1 | 2 | setInterval(function() { 3 | console.log('BOUM'); 4 | process.exit(1); 5 | }, 30); 6 | -------------------------------------------------------------------------------- /test/fixtures/max-mem.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "max_mem", 3 | "script" : "big-array.js", 4 | "max_memory_restart" : "19" 5 | } 6 | -------------------------------------------------------------------------------- /examples/killtoofast.js: -------------------------------------------------------------------------------- 1 | 2 | console.log('im a kamikazy'); 3 | 4 | setInterval(function() { 5 | console.log('BOUM'); 6 | process.exit(1); 7 | }, 30); 8 | -------------------------------------------------------------------------------- /apps/killfast.json: -------------------------------------------------------------------------------- 1 | { 2 | "min_uptime" : "100", 3 | "max_restarts" : "400", 4 | "name" : "auto-kill", 5 | "script" : "./examples/killfast.js" 6 | } 7 | -------------------------------------------------------------------------------- /examples/child.js-pm2.json: -------------------------------------------------------------------------------- 1 | {"script":"child.js","name":"child.js","instances":10,"error_file":"errfile.log","out_file":"outfile.log","pid_file":"pidfile.pid"} 2 | -------------------------------------------------------------------------------- /lib/interpreter.json: -------------------------------------------------------------------------------- 1 | { 2 | ".sh": "bash", 3 | ".py": "python", 4 | ".rb": "ruby", 5 | ".php": "php", 6 | ".pl" : "perl", 7 | ".js" : "node" 8 | } 9 | -------------------------------------------------------------------------------- /examples/child.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | 4 | http.createServer(function(req, res) { 5 | res.writeHead(200); 6 | res.end('hoy'); 7 | }).listen(8000); 8 | -------------------------------------------------------------------------------- /test/fixtures/echo.js: -------------------------------------------------------------------------------- 1 | 2 | setInterval(function() { 3 | console.log('ok'); 4 | }, 100); 5 | 6 | setInterval(function() { 7 | console.error('thisnok'); 8 | }, 100); 9 | -------------------------------------------------------------------------------- /examples/sendmsg.js: -------------------------------------------------------------------------------- 1 | 2 | setInterval(function() { 3 | process.send({ 4 | type : 'miami', 5 | data : { msg : 'i can communicate with others'} 6 | }); 7 | }, 1000); 8 | -------------------------------------------------------------------------------- /examples/throw.js: -------------------------------------------------------------------------------- 1 | 2 | setTimeout(function() { 3 | console.log('log message from echo auto kill'); 4 | throw new Error('Exitasdsadasdsda unacepted !!'); 5 | }, 2000); 6 | -------------------------------------------------------------------------------- /test/fixtures/server-watch.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | http.createServer(function(req, res) { 4 | res.writeHead(200); 5 | res.end('hey'); 6 | }).listen(8000); 7 | -------------------------------------------------------------------------------- /test/fixtures/server-watch.bak.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | http.createServer(function(req, res) { 4 | res.writeHead(200); 5 | res.end('hey'); 6 | }).listen(8000); 7 | -------------------------------------------------------------------------------- /test/fixtures/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | http.createServer(function(req, res) { 4 | res.writeHead(200); 5 | res.end("hello world\n"); 6 | }).listen(8020); 7 | -------------------------------------------------------------------------------- /test/programmatic/child.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | 4 | http.createServer(function(req, res) { 5 | res.writeHead(200); 6 | res.end('hoy'); 7 | }).listen(8000); 8 | -------------------------------------------------------------------------------- /test/programmatic/cwd.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | 4 | http.createServer(function(req, res) { 5 | res.writeHead(200); 6 | res.end(__dirname); 7 | }).listen(8000); 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | - development 6 | - communication-protocol 7 | node_js: 8 | - "0.11" 9 | - "0.10" 10 | -------------------------------------------------------------------------------- /examples/kill-not-so-fast.js: -------------------------------------------------------------------------------- 1 | 2 | console.log('start'); 3 | 4 | setTimeout(function() { 5 | console.log('exit'); 6 | throw new Error('Exitasdsadasdsda unacepted !!'); 7 | }, 300); 8 | -------------------------------------------------------------------------------- /test/fixtures/events/custom_action.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | 4 | axm.action('refresh:db', function(reply) { 5 | console.log('Refreshing'); 6 | reply({success : true}); 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/signal.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | setInterval(function() { 4 | console.log('ok'); 5 | }, 1000); 6 | 7 | process.on('SIGUSR2', function () { 8 | console.log('SIGUSR2'); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/cwd.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | console.log(process.env.PWD); 4 | console.log(process.cwd()); 5 | console.log(__dirname); 6 | 7 | require('./echo.js'); 8 | 9 | console.log(process.cwd()); 10 | 11 | -------------------------------------------------------------------------------- /lib/Interactor/Tools.js: -------------------------------------------------------------------------------- 1 | 2 | var Stringify = require('json-stringify-safe'); 3 | 4 | var Tools = {}; 5 | 6 | Tools.serialize = function(data) { 7 | return JSON.parse(Stringify(data)); 8 | }; 9 | -------------------------------------------------------------------------------- /test/fixtures/multi-echo.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "echo2", 3 | "script" : "echo.js", 4 | "max" : "1" 5 | }, { 6 | "name" : "echo3", 7 | "script" : "echo.js", 8 | "max" : "1" 9 | }] 10 | -------------------------------------------------------------------------------- /test/fixtures/big-array.js: -------------------------------------------------------------------------------- 1 | 2 | var obj = {}; 3 | var i = 0; 4 | 5 | setInterval(function() { 6 | obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); 7 | i++; 8 | }, 2); 9 | -------------------------------------------------------------------------------- /test/fixtures/child.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | var i = 0; 4 | 5 | http.createServer(function(req, res) { 6 | res.writeHead(200); 7 | res.end("hello world\n" + i++); 8 | }).listen(8004); 9 | -------------------------------------------------------------------------------- /apps/args.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "echo", 3 | "script" : "./examples/args.js", 4 | "instances" : "1", 5 | "args" : "['--toto=heya coco', '-d', '1']", 6 | "cron_restart" : "* * * * * *" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/cluster-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "serv-clust", 3 | "script" : "server.js", 4 | "max" : "10", 5 | "instances" : "4", 6 | "env_test" : { 7 | "NODE_ENV" : "TEST" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/network.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | var i = 0; 4 | 5 | http.createServer(function(req, res) { 6 | res.writeHead(200); 7 | res.end("hello world\n" + i++); 8 | }).listen(8004); 9 | -------------------------------------------------------------------------------- /apps/cron-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "echo", 3 | "script" : "./examples/args.js", 4 | "instances" : "1", 5 | "args" : "['--toto=heya coco', '-d', '1']", 6 | "cron_restart" : "* * * * * *" 7 | } 8 | -------------------------------------------------------------------------------- /examples/child-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "script":"examples/child.js", 3 | "name":"SERVERONE", 4 | "instances":10, 5 | "error_file":"errfile.log", 6 | "out_file":"outfile.log", 7 | "pid_file":"pidfile.pid" 8 | } 9 | -------------------------------------------------------------------------------- /examples/require.js: -------------------------------------------------------------------------------- 1 | 2 | var util = require('util'); 3 | 4 | console.log(util.inspect(require.main)); 5 | setInterval(function() { 6 | console.log(util.inspect(require.main)); 7 | }, 8000); 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/benchmarks/result.monit: -------------------------------------------------------------------------------- 1 | ========= Fri Aug 22 14:11:29 EDT 2014 2 | 14:11:35 61012 3 | ========= Fri Aug 22 14:11:45 EDT 2014 4 | 14:11:50 59984 5 | ========= Fri Aug 22 14:12:47 EDT 2014 6 | 14:12:52 59464 7 | -------------------------------------------------------------------------------- /apps/multi-pm2.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "echo", 3 | "script" : "./examples/args.js", 4 | "instances" : "1", 5 | "args" : "['--toto=heya coco', '-d', '1']", 6 | "cron_restart" : "* * * * * *" 7 | }] 8 | -------------------------------------------------------------------------------- /examples/child2.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | axm.http(); 4 | var http = require('http'); 5 | 6 | http.createServer(function(req, res) { 7 | res.writeHead(200); 8 | res.end('hoy'); 9 | }).listen(8000); 10 | -------------------------------------------------------------------------------- /examples/harmony.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , s = new Set() 3 | ; 4 | 5 | s.add('a'); 6 | 7 | assert.ok(s.has('a')); 8 | 9 | setInterval(function() { 10 | console.log(s.has('a')); 11 | }, 1000); 12 | -------------------------------------------------------------------------------- /examples/human_event.js: -------------------------------------------------------------------------------- 1 | var axm = require('axm'); 2 | 3 | setInterval(function() { 4 | axm.emit('content:page:created', { 5 | msg : 'A CMS page has been created', 6 | user : 'Francois Debiole' 7 | }); 8 | }, 1000); 9 | -------------------------------------------------------------------------------- /examples/leak.js: -------------------------------------------------------------------------------- 1 | 2 | var leak = []; 3 | 4 | setInterval(function() { 5 | for (var i = 0; i < 10; i++) { 6 | var str = i.toString() + " on a stick, short and stout!"; 7 | leak.push(str); 8 | } 9 | }, 50); 10 | -------------------------------------------------------------------------------- /test/fixtures/events/own_event.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | 4 | setInterval(function() { 5 | axm.emit('user:register', { 6 | user : 'toto@gmail.com', 7 | mail : 'hey@gmail.com' 8 | }); 9 | }, 200); 10 | -------------------------------------------------------------------------------- /test/fixtures/harmony.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , s = new Set() 3 | ; 4 | 5 | s.add('a'); 6 | 7 | assert.ok(s.has('a')); 8 | 9 | setInterval(function() { 10 | console.log(s.has('a')); 11 | }, 1000); 12 | -------------------------------------------------------------------------------- /test/fixtures/args.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | if (process.argv.indexOf('-d') == -1 || process.argv.indexOf('-a') == -1) { 4 | process.exit(); 5 | } else { 6 | setInterval(function() { 7 | console.log('ok'); 8 | }, 500); 9 | } 10 | -------------------------------------------------------------------------------- /examples/args.js: -------------------------------------------------------------------------------- 1 | 2 | process.argv.forEach(function (val, index, array) { 3 | console.log(index + ': ' + val); 4 | }); 5 | 6 | setInterval(function() { 7 | console.log('HERE ARE MY ARGS !!! = ', process.argv); 8 | }, 800); 9 | 10 | -------------------------------------------------------------------------------- /examples/exec_watch.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "script":"examples/child.js", 3 | "name":"SERVERONE", 4 | "instances":10, 5 | "watch" : true, 6 | "error_file":"errfile.log", 7 | "out_file":"outfile.log", 8 | "pid_file":"pidfile.pid" 9 | }] 10 | -------------------------------------------------------------------------------- /examples/json.js: -------------------------------------------------------------------------------- 1 | 2 | setInterval(function() { 3 | console.log({ 4 | hey : 'hay', 5 | ho : { 6 | si : 'si', 7 | ca : ['boum'] 8 | } 9 | }); 10 | var a = {a: 'a', b: 'b'}; 11 | console.log(a); 12 | }, 1000); 13 | -------------------------------------------------------------------------------- /apps/cluster-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "script" : "./examples/child.js", 3 | "error_file" : "errLog.log", 4 | "out_file" : "outLog.log", 5 | "pid_file" : "child", 6 | "instances" : "4", 7 | "min_uptime" : "10", 8 | "max_restarts" : "4" 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/env.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "env", 3 | "script" : "./env.js", 4 | "out_file" : "out-env.log", 5 | "merge_logs" : true, 6 | "env": { 7 | "NODE_ENV": "production", 8 | "TEST_VARIABLE": "YES" 9 | } 10 | }] 11 | -------------------------------------------------------------------------------- /examples/echokill.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | setInterval(function() { 4 | console.log('log message from echo auto kill'); 5 | }, 800); 6 | 7 | setTimeout(function() { 8 | console.error('error message, killing my self'); 9 | process.exit(10); 10 | }, 3000); 11 | -------------------------------------------------------------------------------- /examples/echo.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | setInterval(function() { 4 | console.log('log message from echo.js'); 5 | }, 1500); 6 | 7 | setTimeout(function() { 8 | setInterval(function() { 9 | console.error('err msg from echo.js'); 10 | }, 1500); 11 | }, 750); 12 | -------------------------------------------------------------------------------- /test/fixtures/env-refreshed.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "env", 3 | "script" : "./env.js", 4 | "out_file" : "out-env.log", 5 | "merge_logs" : true, 6 | "env": { 7 | "NODE_ENV": "production", 8 | "TEST_VARIABLE": "HEYYYY" 9 | } 10 | }] 11 | -------------------------------------------------------------------------------- /test/fixtures/no_cwd_change.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "iminpath1", 3 | "script" : "iminpath1.js", 4 | "out_file" : "./iminpath1.log" 5 | },{ 6 | "name" : "iminpath2", 7 | "script" : "iminpath2.js", 8 | "out_file" : "./iminpath2.log" 9 | }] 10 | -------------------------------------------------------------------------------- /scripts/postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | `command -v node || command -v nodejs` ./scripts/ping.js 4 | if [ $? -eq 0 ] 5 | then 6 | bash ./scripts/kill.js 7 | if [ $? -eq 0 ] 8 | then 9 | ./bin/pm2 resurrect 10 | fi 11 | exit 0; 12 | else 13 | exit 0; 14 | fi 15 | -------------------------------------------------------------------------------- /apps/echo-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "script" : "examples/echo.js", 3 | "error_file" : "errEcho.log", 4 | "out_file" : "outEcho.log", 5 | "name" : "ok", 6 | "pid_file" : "echo.pid", 7 | "max" : "1", 8 | "exec_mode" : "cluster_mode", 9 | "port" : "9001", 10 | "env_variable" : "TOTO" 11 | } 12 | -------------------------------------------------------------------------------- /apps/all-pm2.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "echo", 3 | "script" : "./examples/echo.js", 4 | "instances" : "1" 5 | },{ 6 | "name" : "api", 7 | "script" : "./examples/child.js", 8 | "instances" : "4", 9 | "error_file" : "./examples/child-err.log", 10 | "out_file" : "./examples/child-out.log" 11 | }] 12 | -------------------------------------------------------------------------------- /test/fixtures/change_cwd.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "iminpath1", 3 | "script" : "iminpath1.js", 4 | "cwd" : "path1", 5 | "out_file" : "./iminpath1.log" 6 | },{ 7 | "name" : "iminpath2", 8 | "script" : "iminpath2.js", 9 | "cwd" : "path1/path2", 10 | "out_file" : "./iminpath2.log" 11 | }] 12 | -------------------------------------------------------------------------------- /apps/env-pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "script" : "examples/env.js", 3 | "error_file" : "errEcho.log", 4 | "out_file" : "outEcho.log", 5 | "name" : "ok", 6 | "pid_file" : "echo.pid", 7 | "max" : "1", 8 | "exec_mode" : "cluster_mode", 9 | "port" : "9001", 10 | "env_variable" : "TOTO", 11 | "TEST_VARIABLE" : "YESSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSIR" 12 | } 13 | -------------------------------------------------------------------------------- /examples/expose_method.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Example of usage : https://github.com/Unitech/pm2/pull/214 4 | */ 5 | process.on("message", function (msg) { 6 | console.log('got message', msg); 7 | if ( "type" in msg && msg.type === "god:heap" ) { 8 | var heap = process.memoryUsage().heapUsed; 9 | process.send({type:"process:heap", heap:heap}); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/all.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "echo", 3 | "script" : "./echo.js", 4 | "instances" : "1" 5 | },{ 6 | "name" : "child", 7 | "script" : "./child.js", 8 | "instances" : "4", 9 | "error_file" : "./child-err.log", 10 | "out_file" : "./child-out.log" 11 | },{ 12 | "name" : "api-2", 13 | "script" : "./server.js", 14 | "instances" : "3" 15 | }] 16 | -------------------------------------------------------------------------------- /lib/scripts/pm2.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PM2 next gen process manager for Node.js 3 | After=network.target remote-fs.target 4 | 5 | [Service] 6 | Type=forking 7 | User=%USER% 8 | 9 | ExecStart=%PM2_PATH% resurrect 10 | ExecReload=%PM2_PATH% reload all 11 | 12 | ExecStop=%PM2_PATH% dump 13 | ExecStop=%PM2_PATH% delete all 14 | ExecStop=%PM2_PATH% kill 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /test/benchmarks/monit-daemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while [ true ] 4 | do 5 | PM2_PID=`pgrep "pm2: Daemon" -o` 6 | 7 | # Run garbage collector 8 | kill -SIGILL $PM2_PID 9 | sleep 5 10 | 11 | FILE="/proc/$PM2_PID/smaps" 12 | Rss=`echo 0 $(cat $FILE | grep Rss | awk '{print $2}' | sed 's#^#+#') | bc;` 13 | 14 | echo `date +%H:%M:%S` $Rss >> $RESULT_FILE 15 | sleep 100 16 | done 17 | -------------------------------------------------------------------------------- /scripts/ping.js: -------------------------------------------------------------------------------- 1 | 2 | var cst = require('../constants.js'); 3 | var fs = require('fs'); 4 | 5 | try { 6 | var pm2_pid = fs.readFileSync(cst.PM2_PID_FILE_PATH); 7 | } catch(e) { 8 | process.exit(1); 9 | } 10 | 11 | if (pm2_pid) { 12 | try { 13 | process.kill(parseInt(pm2_pid), 0); 14 | console.log('PM2 online'); 15 | process.exit(0); 16 | } 17 | catch (err) { 18 | process.exit(1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/child-pm2-v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps" : [{ 3 | "script":"examples/child.js", 4 | "name":"API-web", 5 | "instances":3 6 | },{ 7 | "script":"examples/echo.js", 8 | "name":"API-io", 9 | "instances":2 10 | }], 11 | "deploy" : { 12 | "production" : { 13 | "user" : "node", 14 | "host" : "212.83.163.168", 15 | "repo" : "git@github.com:Unitech/eip-vitrine.git", 16 | "path" : "/var/www/test-deploy" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/big-array-es6.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var obj = {}; 4 | var i = 0; 5 | 6 | setInterval(function() { 7 | obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); 8 | i++; 9 | }, 2); 10 | 11 | 12 | (function testHarmony() { 13 | // 14 | // Harmony test 15 | // 16 | try { 17 | var assert = require('assert') 18 | , s = new Set(); 19 | s.add('a'); 20 | assert.ok(s.has('a')); 21 | console.log('● ES6 mode'.green); 22 | } catch(e) {} 23 | })(); 24 | -------------------------------------------------------------------------------- /test/fixtures/graceful-exit-no-listen.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Example of graceful exit that does not listen 4 | * 5 | * $ pm2 gracefulReload all 6 | */ 7 | 8 | process.on('message', function(msg) { 9 | if (msg == 'shutdown') { 10 | console.log('Closing all connections...'); 11 | setTimeout(function() { 12 | console.log('Finished closing connections'); 13 | process.exit(0); 14 | }, 1500); 15 | } 16 | }); 17 | 18 | setInterval(function () 19 | { 20 | console.log('tick'); 21 | }, 4000); 22 | -------------------------------------------------------------------------------- /test/bash/cli-ux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | 7 | echo -e "\033[1mRunning tests:\033[0m" 8 | 9 | which wrk 10 | spec "You should have wrk benchmark in your /usr/bin" 11 | 12 | killall node 13 | 14 | cd $file_path 15 | $pm2 start cluster-pm2.json 16 | $pm2 start cluster-pm2.json -f 17 | $pm2 start cluster-pm2.json -f 18 | $pm2 start cluster-pm2.json -f 19 | spec "start cluster" 20 | 21 | wrk -c 500 -t 500 -d 8 http://localhost:8020 &> /dev/null & 22 | $pm2 monit 23 | $pm2 list 24 | $pm2 stop 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # JavaScript 14 | [*.js] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | # YAML 19 | [*.yml] 20 | indent_style = space 21 | indent_size = 3 22 | 23 | # JSON 24 | [*.json] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | # SHELL 29 | [*.sh] 30 | indent_style = space 31 | indent_size = 2 32 | -------------------------------------------------------------------------------- /test/bash/right-exit-code.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo -e "\033[1mRIGHT EXIT CODES:\033[0m" 9 | 10 | 11 | $pm2 kill 12 | 13 | $pm2 restart BULLSHIT 14 | ispec "Unknown process = error exit" 15 | 16 | $pm2 restart 666 17 | ispec "Unknown process = error exit" 18 | 19 | $pm2 restart all 20 | ispec "No process = error exit" 21 | 22 | $pm2 stop all 23 | ispec "No process = error exit" 24 | 25 | $pm2 delete 10 26 | ispec "No process = error exit" 27 | 28 | $pm2 delete toto 29 | ispec "No process = error exit" 30 | -------------------------------------------------------------------------------- /test/fixtures/graceful-exit-send.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Example of graceful exit that does not listen but sends 'online' 4 | * 5 | * $ pm2 gracefulReload all 6 | */ 7 | 8 | process.on('message', function(msg) { 9 | if (msg == 'shutdown') { 10 | console.log('Closing all connections...'); 11 | setTimeout(function() { 12 | console.log('Finished closing connections'); 13 | process.exit(0); 14 | }, 1500); 15 | } 16 | }); 17 | 18 | setInterval(function () 19 | { 20 | console.log('tick'); 21 | }, 4000); 22 | 23 | setTimeout(function () 24 | { 25 | process.send('online'); 26 | }, 2000); 27 | -------------------------------------------------------------------------------- /test/fixtures/graceful-exit.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Example of graceful exit 4 | * 5 | * $ pm2 gracefulReload all 6 | */ 7 | 8 | process.on('message', function(msg) { 9 | if (msg == 'shutdown') { 10 | console.log('Closing all connections...'); 11 | setTimeout(function() { 12 | console.log('Finished closing connections'); 13 | process.exit(0); 14 | }, 1500); 15 | } 16 | }); 17 | 18 | var http = require('http'); 19 | 20 | http.createServer(function(req, res) { 21 | res.writeHead(200); 22 | res.end('hey'); 23 | }).listen(8000, function() { 24 | console.log('listening'); 25 | }); 26 | -------------------------------------------------------------------------------- /test/fixtures/all2.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name" : "echo", 3 | "script" : "./test/fixtures/echo.js", 4 | "instances" : "1", 5 | "env_production" : { 6 | "NODE_ENV" : "production", 7 | "TOTO" : "heymoto" 8 | }, 9 | "env_test" : { 10 | "NODE_ENV" : "test", 11 | "TOTO" : "heyamota" 12 | } 13 | },{ 14 | "name" : "child", 15 | "script" : "./test/fixtures/child.js", 16 | "instances" : "1", 17 | "error_file" : "./child-err.log", 18 | "out_file" : "./child-out.log" 19 | },{ 20 | "name" : "api-2", 21 | "script" : "./test/fixtures/server.js", 22 | "instances" : "2" 23 | }] 24 | -------------------------------------------------------------------------------- /test/bash/interact.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | cd $file_path 6 | 7 | echo -e "\033[1mRunning Interaction tests:\033[0m" 8 | 9 | $pm2 killInteract 10 | 11 | $pm2 interact 12 | 13 | $pm2 interact XXX2 XXX3 homeloc 14 | 15 | $pm2 updatePM2 16 | 17 | $pm2 killInteract 18 | 19 | $pm2 interact 20 | 21 | $pm2 infoInteract 22 | 23 | $pm2 infoInteract | grep "XXX2" 24 | spec "Should have XXX2 has public key" 25 | 26 | $pm2 infoInteract | grep "XXX3" 27 | spec "Should have XXX3 has public key" 28 | 29 | $pm2 list 30 | 31 | $pm2 killInteract 32 | $pm2 kill 33 | -------------------------------------------------------------------------------- /examples/graceful-exit.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Example of graceful exit 4 | * 5 | * $ pm2 gracefulReload all 6 | */ 7 | 8 | process.on('message', function(msg) { 9 | if (msg == 'shutdown') { 10 | console.log('Closing all connections...'); 11 | setTimeout(function() { 12 | console.log('Finished closing connections'); 13 | process.exit(0); 14 | }, 1500); 15 | } 16 | }); 17 | 18 | var http = require('http'); 19 | 20 | http.createServer(function(req, res) { 21 | res.writeHead(200); 22 | console.log('got'); 23 | res.end('hey'); 24 | }).listen(8000, function() { 25 | console.log('listening'); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/exit.js: -------------------------------------------------------------------------------- 1 | 2 | // process.on('exit', function() { 3 | // console.log('About to exit.'); 4 | // }); 5 | 6 | // process.on('uncaughtException', function(err) { 7 | // console.log('Caught exception: ' + err); 8 | // }); 9 | 10 | // process.on('SIGINT', function() { 11 | // console.log('Got SIGINT. Press Control-D to exit.'); 12 | // process.exit(1); 13 | // }); 14 | 15 | var worker = require('cluster').worker; 16 | 17 | worker.on('disconnect', function() { 18 | console.log('exiting'); 19 | }); 20 | 21 | 22 | setInterval(function() { 23 | }, 1); 24 | 25 | setInterval(function() { 26 | console.log('ok'); 27 | }, 2000); 28 | console.log('ok'); 29 | -------------------------------------------------------------------------------- /test/benchmarks/monit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RESULT_FILE=result.monit 4 | 5 | export RESULT_FILE=$RESULT_FILE 6 | 7 | launch() { 8 | echo "========= `date`" >> $RESULT_FILE 9 | nohup ./monit-daemon.sh &> monit.log & 10 | } 11 | 12 | ppkill() { 13 | pkill -f monit-daemon.sh ; pkill -f sleep 14 | } 15 | 16 | case "$1" in 17 | start) 18 | launch 19 | ;; 20 | kill) 21 | ppkill 22 | ;; 23 | stop) 24 | ppkill 25 | ;; 26 | restart) 27 | ppkill 28 | launch 29 | ;; 30 | *) 31 | echo "Usage: {start|kill|stop|restart}" 32 | exit 1 33 | ;; 34 | esac 35 | exit $RETVAL 36 | -------------------------------------------------------------------------------- /scripts/preinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Check if user is logged as root and that pm2 command is available 5 | # 6 | 7 | if ( [ "$EUID" -eq 0 ] || [ "$USER" == "root" ] ) && ! ( env | grep "unsafe-perm" ); 8 | then 9 | echo "##### PM2 INSTALLATION" 10 | echo "#" 11 | echo "#" 12 | echo "# As you run PM2 as root, to update PM2 automatically" 13 | echo "# you must add the --unsafe-perm flag." 14 | echo "#" 15 | echo "# $ npm install pm2 -g --unsafe-perm" 16 | echo "#" 17 | echo "# Else run the installation as a non root user" 18 | echo "#" 19 | echo "#" 20 | echo "#" 21 | echo "######" 22 | echo "" 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /examples/custom_action_with_params.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | 4 | axm.action('refresh:db', { comment : 'Refresh the database' }, function(reply) { 5 | console.log('Refreshing'); 6 | reply({success : true}); 7 | }); 8 | 9 | axm.action('chanme:ladb', { comment : 'Refresh la BIG database' }, function(reply) { 10 | console.log('Refreshing BIG DB'); 11 | reply({success : true}); 12 | }); 13 | 14 | axm.action('rm:rf', { comment : 'Delete moi ca plus vite que ca !' }, function(reply) { 15 | console.log('RMING RFING'); 16 | reply({success : true}); 17 | }); 18 | 19 | axm.action('rm:roff', function(reply) { 20 | console.log('RMING RFING'); 21 | reply({success : true}); 22 | }); 23 | -------------------------------------------------------------------------------- /test/helpers/plan.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | 4 | /** 5 | * Description 6 | * @method Plan 7 | * @param {} count 8 | * @param {} done 9 | * @return 10 | */ 11 | function Plan(count, done) { 12 | this.done = done; 13 | this.count = count; 14 | } 15 | 16 | /** 17 | * Description 18 | * @method ok 19 | * @param {} expression 20 | * @return 21 | */ 22 | Plan.prototype.ok = function(expression) { 23 | assert(expression); 24 | 25 | if (this.count === 0) { 26 | assert(false, 'Too many assertions called'); 27 | } else { 28 | this.count--; 29 | } 30 | 31 | if (this.count === 0) { 32 | this.done(); 33 | } 34 | }; 35 | 36 | module.exports = Plan; 37 | -------------------------------------------------------------------------------- /test/fixtures/events/custom_action_with_params.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | 4 | axm.action('refresh:db', { comment : 'Refresh the database' }, function(reply) { 5 | console.log('Refreshing'); 6 | reply({success : true}); 7 | }); 8 | 9 | axm.action('chanme:ladb', { comment : 'Refresh la BIG database' }, function(reply) { 10 | console.log('Refreshing BIG DB'); 11 | reply({success : true}); 12 | }); 13 | 14 | axm.action('rm:rf', { comment : 'Delete moi ca plus vite que ca !' }, function(reply) { 15 | console.log('RMING RFING'); 16 | reply({success : true}); 17 | }); 18 | 19 | axm.action('rm:roff', function(reply) { 20 | console.log('RMING RFING'); 21 | reply({success : true}); 22 | }); 23 | -------------------------------------------------------------------------------- /lib/custom_options.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This permits to pass options to node 5 | # Usefull if you want to activate the --harmony options for example 6 | # 7 | # export PM2_NODE_OPTIONS='--harmony' 8 | 9 | # 10 | # Will modify basepath 11 | # 12 | # export PM2_MODIFY_REQUIRE=false 13 | # 14 | 15 | # 16 | # This it's the timeout used for the process to graceful exit everything 17 | # if it doesnt kill itself 18 | # 19 | # export PM2_GRACEFUL_TIMEOUT=4000 20 | 21 | # 22 | # Change port for `pm2 web` 23 | # 24 | # export PM2_API_PORT=9615 25 | 26 | # 27 | # If you want to run pm2 in debug mode 28 | # 29 | # export DEBUG=false 30 | 31 | 32 | # export PM2_RPC_PORT=6666 33 | # export PM2_PUB_PORT=6667 34 | # export PM2_BIND_ADDR='localhost' 35 | # PM2_PID_DIR 36 | # PM2_LOG_DIR 37 | -------------------------------------------------------------------------------- /lib/scripts/pm2: -------------------------------------------------------------------------------- 1 | #!/sbin/runscript 2 | 3 | extra_started_commands="reload" 4 | 5 | PM2=%PM2_PATH% 6 | USER=%USER% 7 | 8 | export PATH=$PATH:%NODE_PATH% 9 | export PM2_HOME="%HOME_PATH%" 10 | 11 | super() { 12 | su - $USER -c "PATH=$PATH; $*" 13 | } 14 | 15 | depend() { 16 | use net localmount 17 | after bootmisc 18 | } 19 | 20 | start() { 21 | ebegin "Starting PM2" 22 | 23 | super $PM2 resurrect 24 | 25 | eend $? 26 | } 27 | 28 | stop() { 29 | ebegin "Stopping PM2" 30 | 31 | super $PM2 dump 32 | super $PM2 delete all 33 | super $PM2 kill 34 | 35 | eend 36 | } 37 | 38 | reload() { 39 | ebegin "Reloading PM2" 40 | 41 | super $PM2 reload all 42 | 43 | eend $? 44 | } 45 | 46 | status() { 47 | ebegin "Status for PM2" 48 | 49 | super $PM2 list 50 | 51 | eend $? 52 | } 53 | -------------------------------------------------------------------------------- /examples/custom_action.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | 4 | 5 | 6 | 7 | 8 | axm.action('refresh:db2', {comment : 'Refresh main database'}, function(reply) { 9 | 10 | axm.emit('user:register', { 11 | user : 'Alex registered', 12 | email : 'thorustor@gmail.com' 13 | }); 14 | 15 | reply({success : true}); 16 | }); 17 | 18 | 19 | 20 | axm.action('hello', {comment : 'Refresh main database'}, function(reply) { 21 | console.log('Refreshing'); 22 | reply({success : true}); 23 | }); 24 | 25 | axm.action('refresh:db3', {comment : 'Comment'}, function(reply) { 26 | throw new Error('asdadsadsasd'); 27 | reply({success : false}); 28 | }); 29 | 30 | axm.action('refresh:db', {comment : 'Refresh main database'}, function(reply) { 31 | console.log('Refreshing'); 32 | reply({success : true}); 33 | }); 34 | -------------------------------------------------------------------------------- /test/bash/gracefulReload3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo "################## GRACEFUL RELOAD 3 ###################" 9 | 10 | ############### 11 | 12 | echo "Launching" 13 | $pm2 start graceful-exit-send.js -i 2 --name="graceful3" -o "grace3.log" -e "grace-err3.log" 14 | should 'should start processes' 'online' 2 15 | 16 | OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` 17 | cat /dev/null > $OUT_LOG 18 | 19 | #### Graceful reload name 20 | $pm2 gracefulReload graceful3 21 | 22 | OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l` 23 | [ $OUT -eq 1 ] || fail "Process that sends 'online' not restarted gracefuly" 24 | success "Process that sends 'online' restarted gracefuly" 25 | 26 | $pm2 kill 27 | -------------------------------------------------------------------------------- /test/bash/gracefulReload2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo "################## GRACEFUL RELOAD 2 ###################" 9 | 10 | ############### 11 | $pm2 kill 12 | 13 | echo "Launching" 14 | $pm2 start graceful-exit-no-listen.js -i 2 --name="graceful2" -o "grace2.log" -e "grace-err2.log" 15 | should 'should start processes' 'online' 2 16 | 17 | OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` 18 | cat /dev/null > $OUT_LOG 19 | 20 | #### Graceful reload name 21 | $pm2 gracefulReload graceful2 22 | 23 | OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l` 24 | [ $OUT -eq 1 ] || fail "Non-listening process not restarted gracefuly" 25 | success "Non-listening process restarted gracefuly" 26 | 27 | $pm2 kill 28 | -------------------------------------------------------------------------------- /test/index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | alias mocha='../node_modules/mocha/bin/mocha' 4 | pm2="`type -P node` `pwd`/bin/pm2" 5 | 6 | 7 | 8 | function fail { 9 | echo -e "######## \033[31m ✘ $1\033[0m" 10 | $pm2 kill 11 | exit 1 12 | } 13 | 14 | function success { 15 | echo -e "\033[32m------------> ✔ $1\033[0m" 16 | $pm2 kill 17 | } 18 | 19 | function spec { 20 | [ $? -eq 0 ] || fail "$1" 21 | success "$1" 22 | } 23 | 24 | $pm2 kill 25 | 26 | export DEBUG="pm2:*" 27 | 28 | mocha ./test/programmatic/god.mocha.js 29 | spec "God test" 30 | mocha ./test/programmatic/satan.mocha.js 31 | spec "Satan test" 32 | mocha ./test/programmatic/programmatic.js 33 | spec "Programmatic test" 34 | mocha ./test/programmatic/interactor.daemonizer.mocha.js 35 | spec "Interactor daemonizer test" 36 | 37 | echo "########## PROGRAMMATIC TEST DONE #########" 38 | -------------------------------------------------------------------------------- /lib/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps" : [{ 3 | "name" : "API", 4 | "script" : "app.js", 5 | "env": { 6 | "COMMON_VARIABLE": "true" 7 | }, 8 | "env_production" : { 9 | "NODE_ENV": "production" 10 | } 11 | },{ 12 | "name" : "WEB", 13 | "script" : "web.js" 14 | }], 15 | "deploy" : { 16 | "production" : { 17 | "user" : "node", 18 | "host" : "212.83.163.1", 19 | "ref" : "origin/master", 20 | "repo" : "git@github.com:repo.git", 21 | "path" : "/var/www/production", 22 | "post-deploy" : "pm2 startOrRestart ecosystem.json --env production" 23 | }, 24 | "dev" : { 25 | "user" : "node", 26 | "host" : "212.83.163.1", 27 | "ref" : "origin/master", 28 | "repo" : "git@github.com:repo.git", 29 | "path" : "/var/www/development", 30 | "post-deploy" : "pm2 startOrRestart ecosystem.json --env dev" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/ecosystem.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps" : [{ 3 | "name" : "API", 4 | "script" : "app.js", 5 | "env": { 6 | "COMMON_VARIABLE": "true" 7 | }, 8 | "env_production" : { 9 | "NODE_ENV": "production" 10 | } 11 | },{ 12 | "name" : "WEB", 13 | "script" : "web.js" 14 | }], 15 | "deploy" : { 16 | "production" : { 17 | "user" : "node", 18 | "host" : "212.83.163.1", 19 | "ref" : "origin/master", 20 | "repo" : "git@github.com:repo.git", 21 | "path" : "/var/www/production", 22 | "post-deploy" : "pm2 startOrRestart ecosystem.json --env production" 23 | }, 24 | "dev" : { 25 | "user" : "node", 26 | "host" : "212.83.163.1", 27 | "ref" : "origin/master", 28 | "repo" : "git@github.com:repo.git", 29 | "path" : "/var/www/development", 30 | "post-deploy" : "pm2 startOrRestart ecosystem.json --env dev" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/bash/json_file.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | cd $file_path 6 | 7 | echo -e "\033[1mRunning tests for json files :\033[0m" 8 | 9 | $pm2 start all.json 10 | should 'should start processes' 'online' 8 11 | 12 | $pm2 stop all.json 13 | should 'should stop processes' 'stopped' 8 14 | 15 | $pm2 delete all.json 16 | should 'should start processes' 'online' 0 17 | 18 | $pm2 start all.json 19 | should 'should start processes' 'online' 8 20 | 21 | $pm2 restart all.json 22 | should 'should stop processes' 'online' 8 23 | should 'should all script been restarted one time' 'restart_time: 1' 8 24 | 25 | # 26 | # CWD OPTION 27 | # 28 | 29 | $pm2 kill 30 | 31 | $pm2 start change_cwd.json 32 | sleep 1 33 | should 'should start 2 processes' 'online' 2 34 | 35 | $pm2 delete all 36 | 37 | $pm2 start no_cwd_change.json 38 | sleep 1 39 | should 'should not start 2 processes because of paths' 'online' 0 40 | -------------------------------------------------------------------------------- /test/bash/fork.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | ########### Fork mode 9 | $pm2 start echo.js -x 10 | should 'should has forked app' 'fork_mode' 1 11 | 12 | $pm2 restart echo.js 13 | should 'should has forked app' 'restart_time: 1' 1 14 | 15 | ########### Fork mode 16 | $pm2 kill 17 | 18 | $pm2 start bashscript.sh -x --interpreter bash 19 | should 'should has forked app' 'fork_mode' 1 20 | 21 | ########### Auto Detective Interpreter In Fork mode 22 | 23 | $pm2 kill 24 | 25 | $pm2 start echo.coffee -x --interpreter coffee 26 | should 'should has forked app' 'fork_mode' 1 27 | 28 | ### Dump resurrect should be ok 29 | $pm2 dump 30 | 31 | $pm2 kill 32 | 33 | #should 'should has forked app' 'fork' 0 34 | 35 | $pm2 resurrect 36 | should 'should has forked app' 'fork_mode' 1 37 | 38 | ## Delete 39 | 40 | $pm2 list 41 | 42 | $pm2 delete 0 43 | should 'should has delete process' 'fork_mode' 0 44 | -------------------------------------------------------------------------------- /examples/keymetrics-load.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var pm2 = require('..'); 4 | 5 | pm2.connect(function() { 6 | pm2.delete('all', function() { 7 | pm2.start('examples/human_event.js', function() { 8 | pm2.start('examples/child.js', {instances:2},function() { 9 | pm2.start('examples/kill-not-so-fast.js', { 10 | instances:10, 11 | minUptime: 0, 12 | maxRestarts : 0 13 | }, function() { 14 | pm2.start('examples/auto-save.js', {execMode : 'fork', watch:true, force : true}, function() { 15 | pm2.start('examples/custom_action_with_params.js', function() { 16 | //pm2.start('examples/auto-bench.js', {instances : 'max'}, function() { 17 | pm2.start('examples/throw.js', {name:'auto-throw'}, function() { 18 | pm2.disconnect(function() { process.exit(1); }); 19 | }); 20 | }); 21 | 22 | }); 23 | }); 24 | }); 25 | 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/bash/env-refresh.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | cd $file_path 6 | 7 | echo -e "\033[1mENV REFRESH\033[0m" 8 | 9 | # 10 | # Restart via CLI 11 | # 12 | TEST_VARIABLE='hello1' $pm2 start env.js -o out-env.log --merge-logs --name "env" 13 | >out-env.log 14 | 15 | sleep 0.5 16 | grep "hello1" out-env.log &> /dev/null 17 | spec "should contain env variable" 18 | 19 | TEST_VARIABLE='89hello89' $pm2 restart env 20 | 21 | sleep 0.5 22 | grep "89hello89" out-env.log &> /dev/null 23 | spec "should contain refreshed environment variable" 24 | 25 | $pm2 delete all 26 | 27 | # HEYYYY 28 | 29 | # 30 | # Restart via JSON 31 | # 32 | 33 | $pm2 start env.json 34 | >out-env.log 35 | 36 | sleep 0.5 37 | grep "YES" out-env.log &> /dev/null 38 | spec "should contain env variable" 39 | 40 | $pm2 restart env-refreshed.json 41 | >out-env.log 42 | 43 | sleep 0.5 44 | grep "HEYYYY" out-env.log &> /dev/null 45 | spec "should contain refreshed env variable via json" 46 | -------------------------------------------------------------------------------- /test/bash/reset.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bash 3 | 4 | SRC=$(cd $(dirname "$0"); pwd) 5 | source "${SRC}/include.sh" 6 | 7 | cd $file_path 8 | $pm2 kill 9 | 10 | echo "################## RESET ###################" 11 | 12 | # 13 | # BY ID 14 | # 15 | $pm2 start echo.js 16 | should 'should restarted be one for all' 'restart_time: 0' 1 17 | 18 | $pm2 restart 0 19 | should 'should process restarted' 'restart_time: 1' 1 20 | 21 | $pm2 reset 0 22 | should 'should process reseted' 'restart_time: 0' 1 23 | 24 | # 25 | # BY NAME 26 | # 27 | $pm2 start echo.js -i 4 -f 28 | should 'should restarted be one for all' 'restart_time: 0' 5 29 | 30 | $pm2 restart echo 31 | should 'should process restarted' 'restart_time: 1' 5 32 | 33 | $pm2 reset echo 34 | should 'should process reseted' 'restart_time: 0' 5 35 | 36 | 37 | # 38 | # ALL 39 | # 40 | $pm2 restart all 41 | $pm2 restart all 42 | $pm2 restart all 43 | should 'should process restarted' 'restart_time: 3' 5 44 | 45 | $pm2 reset all 46 | should 'should process reseted' 'restart_time: 0' 5 47 | -------------------------------------------------------------------------------- /test/bash/gracefulReload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo "################## GRACEFUL RELOAD ###################" 9 | 10 | ############### 11 | 12 | echo "Launching" 13 | $pm2 start graceful-exit.js -i 4 --name="graceful" -o "grace.log" -e "grace-err.log" 14 | should 'should start processes' 'online' 4 15 | 16 | OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` 17 | cat /dev/null > $OUT_LOG 18 | 19 | #### Graceful reload all 20 | 21 | $pm2 gracefulReload all 22 | 23 | OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l` 24 | [ $OUT -eq 1 ] || fail "Process not restarted gracefuly" 25 | success "Process restarted gracefuly" 26 | 27 | 28 | cat /dev/null > $OUT_LOG 29 | 30 | #### Graceful reload name 31 | $pm2 gracefulReload graceful 32 | 33 | OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l` 34 | [ $OUT -eq 1 ] || fail "Process not restarted gracefuly" 35 | success "Process restarted gracefuly" 36 | 37 | $pm2 kill 38 | -------------------------------------------------------------------------------- /test/bash/harmony.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | $pm2 kill 8 | 9 | echo "################ HARMONY ES6" 10 | 11 | $pm2 start harmony.js 12 | sleep 2 13 | $pm2 list 14 | should 'should FAIL when not passing harmony option to V8' 'restart_time: 0' 0 15 | $pm2 list 16 | $pm2 delete all 17 | 18 | $pm2 start harmony.js --node-args="--harmony" 19 | sleep 2 20 | $pm2 list 21 | should 'should not fail when passing node-args=harmony opts in CLUSTERMODE' 'restart_time: 0' 1 22 | $pm2 delete all 23 | 24 | echo "################ HARMONY / NODEARGS ES6 FORK MODE" 25 | 26 | $pm2 start harmony.js --node-args="--harmony" -x 27 | sleep 2 28 | $pm2 list 29 | should 'should not fail when passing node-args=harmony opts in FORKMODE' 'restart_time: 0' 1 30 | $pm2 delete all 31 | 32 | echo "################## NODE ARGS VIA JSON" 33 | 34 | $pm2 start harmony.json 35 | sleep 2 36 | $pm2 list 37 | should 'should not fail when passing harmony option to V8 via node_args in JSON files' 'restart_time: 0' 1 38 | 39 | $pm2 delete all 40 | -------------------------------------------------------------------------------- /test/bash/log-custom.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bash 3 | 4 | SRC=$(cd $(dirname "$0"); pwd) 5 | source "${SRC}/include.sh" 6 | 7 | cd $file_path 8 | 9 | $pm2 kill 10 | 11 | 12 | # CLUSTERMODE YYYY 13 | $pm2 start echo.js --log-date-format "YYYY" -o out-rel.log --merge-logs 14 | 15 | >out-rel.log 16 | 17 | sleep 2 18 | 19 | grep "2014" out-rel.log 20 | spec "Should have written year in log file according to format YYYY" 21 | 22 | rm out-rel.log 23 | 24 | $pm2 delete all 25 | 26 | # CLUSTERMODE Wrong format 27 | $pm2 start echo.js --log-date-format "YYYY asdsd asd asd sad asd " -o out-rel.log --merge-logs 28 | 29 | sleep 1 30 | should 'should has not restarted' 'restart_time: 0' 1 31 | spec "Should have not fail with random format" 32 | 33 | rm out-rel.log 34 | 35 | $pm2 delete all 36 | 37 | 38 | # CLUSTERMODE YYYY 39 | $pm2 start echo.js --log-date-format "YYYY" -o out-rel.log --merge-logs -x 40 | 41 | >out-rel.log 42 | 43 | sleep 2 44 | 45 | grep "2014" out-rel.log 46 | spec "Should have written year in log file according to format YYYY" 47 | 48 | rm out-rel.log 49 | 50 | $pm2 delete all 51 | -------------------------------------------------------------------------------- /lib/Interactor/Cipher.js: -------------------------------------------------------------------------------- 1 | 2 | var crypto = require('crypto'); 3 | 4 | const CIPHER_ALGORITHM = 'aes256'; 5 | 6 | var Cipher = module.exports = {}; 7 | 8 | /** 9 | * Description 10 | * @method decipherMessage 11 | * @param {} msg 12 | * @return ret 13 | */ 14 | Cipher.decipherMessage = function(msg, key) { 15 | var ret = {}; 16 | 17 | try { 18 | var decipher = crypto.createDecipher(CIPHER_ALGORITHM, key); 19 | var decipheredMessage = decipher.update(msg, 'hex', 'binary'); 20 | decipheredMessage += decipher.final("binary"); 21 | ret = JSON.parse(decipheredMessage); 22 | } catch(e) { 23 | return null; 24 | } 25 | 26 | return ret; 27 | } 28 | 29 | /** 30 | * Description 31 | * @method cipherMessage 32 | * @param {} data 33 | * @param {} key 34 | * @return 35 | */ 36 | Cipher.cipherMessage = function(data, key) { 37 | try { 38 | var cipher = crypto.createCipher(CIPHER_ALGORITHM, key); 39 | var cipheredData = cipher.update(data, "binary", "hex"); 40 | cipheredData += cipher.final("hex"); 41 | return cipheredData; 42 | } catch(e) { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/bash/include.sh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # cli-test: Tests for god 4 | # 5 | # (C) 2013 Unitech.io Inc. 6 | # MIT LICENSE 7 | # 8 | 9 | # Yes, we have tests in bash. How mad science is that? 10 | 11 | # export PM2_RPC_PORT=4242 12 | # export PM2_PUB_PORT=4243 13 | 14 | node="`type -P node`" 15 | nodeVersion="`$node -v`" 16 | 17 | pm2="`type -P node` `pwd`/bin/pm2" 18 | 19 | script="echo" 20 | 21 | file_path="test/fixtures" 22 | 23 | $pm2 kill 24 | 25 | # Determine wget / curl 26 | which wget > /dev/null 27 | if [ $? -eq 0 ] 28 | then 29 | http_get="wget" 30 | else 31 | echo -e "\033[31mYou need wget to run this test \033[0m"; 32 | exit 1; 33 | fi 34 | 35 | function fail { 36 | echo -e "######## \033[31m ✘ $1\033[0m" 37 | exit 1 38 | } 39 | 40 | function success { 41 | echo -e "\033[32m------------> ✔ $1\033[0m" 42 | } 43 | 44 | function spec { 45 | [ $? -eq 0 ] || fail "$1" 46 | success "$1" 47 | } 48 | 49 | function ispec { 50 | [ $? -eq 1 ] || fail "$1" 51 | success "$1" 52 | } 53 | 54 | function should { 55 | sleep 0.5 56 | OUT=`$pm2 prettylist | grep -o "$2" | wc -l` 57 | [ $OUT -eq $3 ] || fail "$1" 58 | success "$1" 59 | } 60 | -------------------------------------------------------------------------------- /examples/auto-bench.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var http = require('http'); 4 | 5 | http.createServer(function(req, res) { 6 | res.writeHead(200); 7 | res.end('transaction'); 8 | }).listen(9923); 9 | 10 | 11 | setInterval(function() { 12 | request(); 13 | }, Math.floor((Math.random() * 1000))); 14 | 15 | 16 | function makeid() 17 | { 18 | var text = ""; 19 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 20 | 21 | for( var i=0; i < 5; i++ ) 22 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 23 | 24 | return text; 25 | } 26 | 27 | function request(path) { 28 | var options = { 29 | hostname: '127.0.0.1' 30 | ,port: 9923 31 | ,path: path || '/users' 32 | ,method: 'GET' 33 | ,headers: { 'Content-Type': 'application/json' } 34 | }; 35 | 36 | var req = http.request(options, function(res) { 37 | res.setEncoding('utf8'); 38 | res.on('data', function (data) { 39 | console.log(data); // I can't parse it because, it's a string. why? 40 | }); 41 | }); 42 | req.on('error', function(e) { 43 | console.log('problem with request: ' + e.message); 44 | }); 45 | req.end(); 46 | } 47 | -------------------------------------------------------------------------------- /test/bash/log-reload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo -e "\033[1mRunning tests:\033[0m" 9 | 10 | $pm2 kill 11 | 12 | >out-rel.log 13 | 14 | $pm2 start echo.js -o out-rel.log --merge-logs 15 | 16 | $pm2 reloadLogs 17 | 18 | sleep 1 19 | 20 | grep "Reloading log..." out-rel.log 21 | 22 | spec "Should have started the reloading action" 23 | 24 | rm out-rel.log 25 | 26 | ## FORK MODE 27 | 28 | $pm2 kill 29 | 30 | $pm2 start echo.js -o out-rel.log -e err-rel.log -x --merge-logs 31 | 32 | sleep 0.5 33 | 34 | grep "ok" out-rel.log 35 | spec "Should have written te right stuff in out log in fork mode" 36 | 37 | grep "thisnok" err-rel.log 38 | spec "Should have written te right stuff in err log in fork mode" 39 | 40 | rm out-rel.log 41 | rm err-rel.log 42 | 43 | $pm2 reloadLogs 44 | spec "Should have reloaded logs via CLI" 45 | 46 | sleep 1 47 | 48 | grep "ok" out-rel.log 49 | spec "(RELOADED) Should have written the right stuff in out log in fork mode" 50 | 51 | grep "thisnok" err-rel.log 52 | spec "(RELOADED) Should have written the right stuff in err log in fork mode" 53 | 54 | rm out-rel.log 55 | rm err-rel.log 56 | -------------------------------------------------------------------------------- /test/bash/signal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo -e "\033[1mRunning tests:\033[0m" 9 | 10 | $pm2 kill 11 | 12 | # 13 | # Signal feature 14 | # 15 | $pm2 start signal.js -i 2 16 | # get the log file and the id. 17 | OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` 18 | cat /dev/null > $OUT_LOG 19 | 20 | $pm2 sendSignal SIGUSR2 signal.js 21 | sleep 1 22 | 23 | OUT=`grep "SIGUSR2" "$OUT_LOG" | wc -l` 24 | [ $OUT -eq 1 ] || fail "Signal not received by the process name" 25 | success "Processes sucessfully receives the signal" 26 | 27 | $pm2 stop signal.js 28 | 29 | # Send a process by id 30 | $pm2 start signal.js 31 | 32 | sleep 1 33 | # get the log file and the id. 34 | OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` 35 | ID=`$pm2 prettylist | grep -E "pm_id:" | sed "s/.*pm_id: \([^,]*\),/\1/"` 36 | 37 | cat /dev/null > $OUT_LOG 38 | 39 | $pm2 sendSignal SIGUSR2 $ID 40 | 41 | OUT=`grep "SIGUSR2" "$OUT_LOG" | wc -l` 42 | [ $OUT -eq 1 ] || fail "Signal not received by the process name" 43 | success "Processes sucessfully receives the signal" 44 | -------------------------------------------------------------------------------- /examples/load-me.js: -------------------------------------------------------------------------------- 1 | 2 | var stop = false; 3 | 4 | /** 5 | * Description 6 | * @method add 7 | * @param {} a 8 | * @param {} b 9 | * @return sum 10 | */ 11 | function add(a, b) { 12 | while (a.length < b.length) a.unshift(0); 13 | while (a.length > b.length) b.unshift(0); 14 | var carry = 0, sum = [] 15 | for (var i = a.length - 1; i >= 0; i--) { 16 | var s = a[i] + b[i] + carry; 17 | if (s >= 10) { 18 | s = s - 10; 19 | carry = 1; 20 | } else { 21 | carry = 0; 22 | } 23 | sum.unshift(s); 24 | } 25 | if (carry) 26 | sum.unshift(carry); 27 | return sum; 28 | } 29 | 30 | /** 31 | * Description 32 | * @method fib 33 | * @param {} n 34 | * @return CallExpression 35 | */ 36 | function fib(n) { 37 | var f1 = [0]; 38 | var f2 = [1]; 39 | 40 | while (n--) { 41 | var f3 = add(f1, f2) 42 | if (stop) return false; 43 | f1 = f2; 44 | f2 = f3; 45 | } 46 | return f1.join(""); 47 | } 48 | 49 | 50 | var axm = require('axm'); 51 | 52 | axm.action('load:start', function(reply) { 53 | fib(50000); 54 | reply({success : true}); 55 | }); 56 | 57 | 58 | axm.action('load:stop', function(reply) { 59 | stop = true; 60 | reply({success : true}); 61 | }); 62 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Installing development version 3 | 4 | ```bash 5 | $ npm install git://github.com/Unitech/pm2.git#development -g 6 | ``` 7 | 8 | # Redhat 9 | 10 | ``` 11 | $ sudo yum install git wget emacs 12 | $ sudo yum groupinstall "Development Tools" 13 | $ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh 14 | $ # put .bash_profile content to .bashrc 15 | $ source .bashrc 16 | $ nvm install v0.11.10 17 | $ nvm alias default 0.11.10 18 | $ npm install pm2 -g 19 | $ # OR 20 | $ npm install git://github.com/Unitech/pm2.git#development -g 21 | ``` 22 | 23 | # CentOS 24 | 25 | ``` 26 | $ yum install git wget emacs 27 | $ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh 28 | $ 29 | ``` 30 | 31 | ## Remove init script 32 | 33 | sudo update-rc.d -f pm2-init.sh remove 34 | ``` 35 | $ chkconfig --del pm2-init.sh 36 | $ chkconfig --add pm2-init.sh 37 | ``` 38 | 39 | gyp WARN EACCES user "root" does not have permission to create dev dir : 40 | https://github.com/TooTallNate/node-gyp/issues/126 41 | -> add --unsafe-perm 42 | 43 | # .pm2 44 | 45 | ``` 46 | $ sudo sh -c 'echo "export PM2_HOME=/var/" >> /etc/profile' 47 | $ sudo mkdir /var/.pm2; chown -R tknew:tknew /var/.pm2 48 | ``` 49 | -------------------------------------------------------------------------------- /test/bash/infinite_loop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | 9 | echo "Starting infinite loop tests" 10 | 11 | $pm2 start killtoofast.js --name unstable-process 12 | 13 | echo -n "Waiting for process to restart too many times and pm2 to stop it" 14 | 15 | for (( i = 0; i <= 50; i++ )); do 16 | sleep 0.1 17 | echo -n "." 18 | done 19 | 20 | 21 | $pm2 list 22 | should 'should has stopped unstable process' 'errored' 1 23 | 24 | $pm2 kill 25 | 26 | echo "Start infinite loop tests for restart|reload" 27 | 28 | cp killnotsofast.js killthen.js 29 | 30 | $pm2 start killthen.js --name killthen 31 | 32 | $pm2 list 33 | 34 | should 'should killthen alive for a long time' 'online' 1 35 | 36 | # Replace killthen file with the fast quit file 37 | 38 | sleep 15 39 | cp killtoofast.js killthen.js 40 | 41 | echo "Restart with unstable process" 42 | 43 | $pm2 list 44 | 45 | $pm2 restart all # pm2 reload should also work here 46 | 47 | for (( i = 0; i <= 80; i++ )); do 48 | sleep 0.1 49 | echo -n "." 50 | done 51 | 52 | $pm2 list 53 | 54 | should 'should has stoped unstable process' 'errored' 1 55 | 56 | rm killthen.js 57 | 58 | $pm2 list 59 | 60 | $pm2 kill 61 | -------------------------------------------------------------------------------- /examples/http-trace.js: -------------------------------------------------------------------------------- 1 | 2 | var axm = require('axm'); 3 | axm.http(); 4 | 5 | var http = require('http'); 6 | 7 | http.createServer(function(req, res) { 8 | res.writeHead(200); 9 | res.end('transaction'); 10 | }).listen(9010); 11 | 12 | setInterval(function() { 13 | request(['/user', '/bla', '/user/lol/delete', '/POST/POST'][Math.floor((Math.random() * 4))]); 14 | }, 1000); 15 | 16 | function makeid() 17 | { 18 | var text = ""; 19 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 20 | 21 | for( var i=0; i < 5; i++ ) 22 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 23 | 24 | return text; 25 | } 26 | 27 | function request(path) { 28 | var options = { 29 | hostname: '127.0.0.1' 30 | ,port: 9010 31 | ,path: path || '/users' 32 | ,method: 'GET' 33 | ,headers: { 'Content-Type': 'application/json' } 34 | }; 35 | 36 | var req = http.request(options, function(res) { 37 | res.setEncoding('utf8'); 38 | res.on('data', function (data) { 39 | console.log(data); // I can't parse it because, it's a string. why? 40 | }); 41 | }); 42 | req.on('error', function(e) { 43 | console.log('problem with request: ' + e.message); 44 | }); 45 | req.end(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/keymetrics-test.js: -------------------------------------------------------------------------------- 1 | 2 | var pm2 = require('..'); 3 | 4 | pm2.connect(function() { 5 | pm2.delete('all', function() { 6 | pm2.start('examples/human_event.js', function() { 7 | pm2.start('examples/child.js', {instances:2},function() { 8 | pm2.start('examples/custom_action.js', function() { 9 | pm2.start('examples/custom_action.js', {execMode : 'fork', force : true}, function() { 10 | pm2.start('examples/auto-save.js', {execMode : 'fork', watch:true, force : true}, function() { 11 | pm2.start('examples/custom_action_with_params.js', function() { 12 | pm2.start('examples/auto-save.js', {watch : true,force:true, name :'auto-save-modify'}, function() { 13 | pm2.start('examples/http-trace.js', {name:'trace'}, function() { 14 | //pm2.start('examples/auto-bench.js', {instances : 'max'}, function() { 15 | pm2.start('examples/throw.js', {name:'auto-throw'}, function() { 16 | pm2.disconnect(function() { process.exit(1); }); 17 | }); 18 | //}); 19 | }); 20 | }); 21 | }); 22 | }); 23 | }); 24 | }); 25 | }); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/bash/include.sh" 5 | 6 | echo "####################### DEBUG ############################" 7 | echo "PM2 Command = " $pm2 8 | echo "Node version = " $nodeVersion 9 | $node -e "var os = require('os'); console.log('arch : %s\nplatform : %s\nrelease : %s\ntype : %s\nmem : %d', os.arch(), os.platform(), os.release(), os.type(), os.totalmem())" 10 | echo "###################### !DEBUG! ###########################" 11 | 12 | export DEBUG="pm2:*" 13 | 14 | bash ./test/bash/cli.sh 15 | spec "CLI basic test" 16 | bash ./test/bash/watch.sh 17 | spec "Watch feature" 18 | bash ./test/bash/json_file.sh 19 | spec "JSON file test" 20 | bash ./test/bash/harmony.sh 21 | spec "Harmony test" 22 | bash ./test/bash/log-custom.sh 23 | spec "Custom log timestamp" 24 | bash ./test/bash/reload.sh 25 | spec "Reload" 26 | bash ./test/bash/right-exit-code.sh 27 | spec "Verification exit code" 28 | bash ./test/bash/log-reload.sh 29 | spec "Log reload" 30 | bash ./test/bash/gracefulReload.sh 31 | spec "gracefulReload system 1" 32 | bash ./test/bash/gracefulReload2.sh 33 | spec "gracefulReload system 2" 34 | bash ./test/bash/gracefulReload3.sh 35 | spec "gracefulReload system 3" 36 | bash ./test/bash/cli2.sh 37 | spec "Second hard cli tests" 38 | bash ./test/bash/misc.sh 39 | spec "MISC features" 40 | bash ./test/bash/fork.sh 41 | spec "Fork verified" 42 | bash ./test/bash/infinite_loop.sh 43 | spec "Infinite loop stop" 44 | bash ./test/bash/env-refresh.sh 45 | spec "Environment refresh on restart" 46 | bash ./test/bash/reset.sh 47 | spec "Reset meta" 48 | 49 | $pm2 kill 50 | -------------------------------------------------------------------------------- /test/helpers/apps.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | 4 | var APPS = {}; 5 | 6 | /** 7 | * Description 8 | * @method forkPM2 9 | * @return pm2 10 | */ 11 | APPS.forkPM2 = function() { 12 | var pm2 = require('child_process').fork('lib/Satan.js', [], { 13 | env : process.env 14 | }); 15 | return pm2; 16 | }; 17 | 18 | /** 19 | * Description 20 | * @method launchApp 21 | * @param {} ipm2 22 | * @param {} script 23 | * @param {} name 24 | * @param {} cb 25 | * @return 26 | */ 27 | APPS.launchApp = function(ipm2, script, name, cb) { 28 | ipm2.rpc.prepare({ 29 | pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/' + script), 30 | pm_err_log_path : path.resolve(process.cwd(), 'test/' + name + 'err.log'), 31 | pm_out_log_path : path.resolve(process.cwd(), 'test/' + name + '.log'), 32 | pm_pid_path : path.resolve(process.cwd(), 'test/child'), 33 | exec_mode : 'cluster_mode', 34 | name : name 35 | }, cb); 36 | }; 37 | 38 | /** 39 | * Description 40 | * @method launchAppFork 41 | * @param {} ipm2 42 | * @param {} script 43 | * @param {} name 44 | * @param {} cb 45 | * @return 46 | */ 47 | APPS.launchAppFork = function(ipm2, script, name, cb) { 48 | ipm2.rpc.prepare({ 49 | pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/' + script), 50 | pm_err_log_path : path.resolve(process.cwd(), 'test/errLogasdasd.log'), 51 | pm_out_log_path : path.resolve(process.cwd(), 'test/outLogasdasd.log'), 52 | pm_pid_path : path.resolve(process.cwd(), 'test/child'), 53 | exec_mode : 'fork_mode', 54 | name : name 55 | }, cb); 56 | }; 57 | 58 | module.exports = APPS; 59 | -------------------------------------------------------------------------------- /test/bash/reload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo "################## RELOAD ###################" 9 | 10 | ############### 11 | $pm2 kill 12 | 13 | echo "Reloading" 14 | $pm2 start child.js -i 4 15 | should 'should start processes' 'online' 4 16 | $pm2 restart all 17 | should 'should restarted be one for all' 'restart_time' 4 18 | $pm2 restart child.js 19 | should 'should restart a second time (BY SCRIPT NAME)' 'restart_time: 2' 4 20 | 21 | $pm2 restart child 22 | should 'should restart a third time (BY NAME)' 'restart_time: 3' 4 23 | sleep 0.5 24 | $pm2 reload all 25 | sleep 0.5 26 | should 'should RELOAD a fourth time' 'restart_time: 4' 4 27 | 28 | ############### CLUSTER STUFF 29 | $pm2 kill 30 | 31 | echo "Reloading" 32 | $pm2 start child.js -i 4 33 | should 'should start processes' 'online' 4 34 | 35 | $pm2 start network.js -i 4 36 | should 'should has 8 online apps' 'online' 8 37 | 38 | should 'should has 4 api online' 'network.js' 4 39 | should 'should has 4 child.js online' 'child.js' 4 40 | 41 | $pm2 reload all 42 | should 'should reload all' 'restart_time' 8 43 | 44 | $pm2 reload child.js 45 | should 'should reload only child.js' 'restart_time: 2' 4 46 | 47 | $pm2 reload network.js 48 | should 'should reload network.js' 'restart_time: 2' 8 49 | 50 | ############### BLOCKING STUFF 51 | 52 | # this is not a networked application 53 | $pm2 start echo.js 54 | should 'should has 8 online apps' 'online' 9 55 | 56 | $pm2 reload echo 57 | should 'should not hang and fallback to restart behaviour' 'restart_time' 9 58 | 59 | 60 | 61 | #$pm2 web 62 | #$pm2 reload all 63 | $pm2 kill 64 | -------------------------------------------------------------------------------- /lib/scripts/pm2-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # chkconfig: 2345 98 02 3 | # 4 | # description: PM2 next gen process manager for Node.js 5 | # processname: pm2 6 | # 7 | ### BEGIN INIT INFO 8 | # Provides: pm2 9 | # Required-Start: $local_fs $remote_fs 10 | # Required-Stop: $local_fs $remote_fs 11 | # Should-Start: $network 12 | # Should-Stop: $network 13 | # Default-Start: 2 3 4 5 14 | # Default-Stop: 0 1 6 15 | # Short-Description: PM2 init script 16 | # Description: PM2 is the next gen process manager for Node.js 17 | ### END INIT INFO 18 | 19 | NAME=pm2 20 | PM2=%PM2_PATH% 21 | USER=%USER% 22 | 23 | export PATH=$PATH:%NODE_PATH% 24 | export PM2_HOME="%HOME_PATH%" 25 | 26 | super() { 27 | su - $USER -c "PATH=$PATH; $*" 28 | } 29 | 30 | start() { 31 | echo "Starting $NAME" 32 | super $PM2 resurrect 33 | } 34 | 35 | stop() { 36 | super $PM2 dump 37 | super $PM2 delete all 38 | super $PM2 kill 39 | } 40 | 41 | restart() { 42 | echo "Restarting $NAME" 43 | stop 44 | start 45 | } 46 | 47 | reload() { 48 | echo "Reloading $NAME" 49 | super $PM2 reload all 50 | } 51 | 52 | status() { 53 | echo "Status for $NAME:" 54 | super $PM2 list 55 | RETVAL=$? 56 | } 57 | 58 | case "$1" in 59 | start) 60 | start 61 | ;; 62 | stop) 63 | stop 64 | ;; 65 | status) 66 | status 67 | ;; 68 | restart) 69 | restart 70 | ;; 71 | reload) 72 | reload 73 | ;; 74 | force-reload) 75 | reload 76 | ;; 77 | *) 78 | echo "Usage: {start|stop|status|restart|reload|force-reload}" 79 | exit 1 80 | ;; 81 | esac 82 | exit $RETVAL 83 | -------------------------------------------------------------------------------- /lib/scripts/pm2-init-amazon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # pm2 Process manager for NodeJS 4 | # 5 | # chkconfig: 345 80 20 6 | # 7 | # description: PM2 next gen process manager for Node.js 8 | # processname: pm2 9 | # 10 | ### BEGIN INIT INFO 11 | # Provides: pm2 12 | # Required-Start: $local_fs $remote_fs 13 | # Required-Stop: $local_fs $remote_fs 14 | # Should-Start: $network 15 | # Should-Stop: $network 16 | # Default-Start: 2 3 4 5 17 | # Default-Stop: 0 1 6 18 | # Short-Description: PM2 init script 19 | # Description: PM2 is the next gen process manager for Node.js 20 | ### END INIT INFO 21 | 22 | NAME=pm2 23 | PM2=%PM2_PATH% 24 | USER=%USER% 25 | 26 | export PATH=$PATH:%NODE_PATH% 27 | export HOME="%HOME_PATH%" 28 | 29 | lockfile="/var/lock/subsys/pm2-init.sh" 30 | 31 | start() { 32 | echo "Starting $NAME" 33 | $PM2 resurrect 34 | retval=$? 35 | [ $retval -eq 0 ] && touch $lockfile 36 | } 37 | 38 | stop() { 39 | echo "Stopping $NAME" 40 | $PM2 dump 41 | $PM2 delete all 42 | $PM2 kill 43 | rm -f $lockfile 44 | } 45 | 46 | restart() { 47 | echo "Restarting $NAME" 48 | stop 49 | start 50 | } 51 | 52 | reload() { 53 | echo "Reloading $NAME" 54 | $PM2 reload all 55 | } 56 | 57 | status() { 58 | echo "Status for $NAME:" 59 | $PM2 list 60 | RETVAL=$? 61 | } 62 | 63 | case "$1" in 64 | start) 65 | start 66 | ;; 67 | stop) 68 | stop 69 | ;; 70 | status) 71 | status 72 | ;; 73 | restart) 74 | restart 75 | ;; 76 | reload) 77 | reload 78 | ;; 79 | *) 80 | echo "Usage: {start|stop|status|restart|reload}" 81 | exit 1 82 | ;; 83 | esac 84 | exit $RETVAL 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PM2 2 | 3 | ## Fire an issue 4 | 5 | When you got an issue by using pm2, you will fire an issue on [github](https://github.com/Unitech/pm2). We'll be glad to help or to fix it but the more data you give the most fast it would be resolved. 6 | Please try following these rules it will make the task easier for you and for us: 7 | 8 | #### 1. Search through issues if it hasn't been resolved yet 9 | #### 2. Make sure that you provide following informations: 10 | - pm2 version `pm2 --version` 11 | - nodejs version `node --version` 12 | - operating system 13 | 14 | #### 3. Provide details about your issue: 15 | - What are the steps that brought me to the issue? 16 | - How may I reproduce this? (this isn't easy in some cases) 17 | - Are you using a cluster module? Are you trying to catch SIGTERM signals? With `code` if possible. 18 | 19 | #### 4. Think global 20 | If your issue is too specific we might not be able to help and stackoverflow might be a better place to seak for an answer 21 | 22 | #### 5. Be clear and format issues with [markdown](http://daringfireball.net/projects/markdown/) 23 | Note that we might understand english, german and french 24 | 25 | #### 6. Use debugging functions: 26 | 27 | ```DEBUG=pm2:* PM2_DEBUG=true ./bin/pm2 --no-daemon start my-buggy-thing.js``` 28 | 29 | If your issue is flagged as `need data` be sure that there won't be any upgrade unless we can have enough data to reproduce. 30 | 31 | ## Pull-Requests 32 | 33 | 1. Fork pm2 34 | 2. Create a different branch to do your fixes/improvements if it's core-related. 35 | 3. Please add unit tests! There are lots of tests take examples from there! 36 | 4. Try to be as clear as possible in your commits 37 | 5. Pull request on pm2 from your branch 38 | -------------------------------------------------------------------------------- /lib/scripts/pm2-init-centos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # pm2 Process manager for NodeJS 4 | # 5 | # chkconfig: 345 80 20 6 | # 7 | # description: PM2 next gen process manager for Node.js 8 | # processname: pm2 9 | # 10 | ### BEGIN INIT INFO 11 | # Provides: pm2 12 | # Required-Start: $local_fs $remote_fs 13 | # Required-Stop: $local_fs $remote_fs 14 | # Should-Start: $network 15 | # Should-Stop: $network 16 | # Default-Start: 2 3 4 5 17 | # Default-Stop: 0 1 6 18 | # Short-Description: PM2 init script 19 | # Description: PM2 is the next gen process manager for Node.js 20 | ### END INIT INFO 21 | 22 | NAME=pm2 23 | PM2=%PM2_PATH% 24 | USER=%USER% 25 | 26 | export PATH=$PATH:%NODE_PATH% 27 | export PM2_HOME="%HOME_PATH%" 28 | 29 | lockfile="/var/lock/subsys/pm2-init.sh" 30 | 31 | super() { 32 | su - $USER -c "PATH=$PATH; $*" 33 | } 34 | 35 | start() { 36 | echo "Starting $NAME" 37 | super $PM2 resurrect 38 | retval=$? 39 | [ $retval -eq 0 ] && touch $lockfile 40 | } 41 | 42 | stop() { 43 | echo "Stopping $NAME" 44 | super $PM2 dump 45 | super $PM2 delete all 46 | super $PM2 kill 47 | rm -f $lockfile 48 | } 49 | 50 | restart() { 51 | echo "Restarting $NAME" 52 | stop 53 | start 54 | } 55 | 56 | reload() { 57 | echo "Reloading $NAME" 58 | super $PM2 reload all 59 | } 60 | 61 | status() { 62 | echo "Status for $NAME:" 63 | super $PM2 list 64 | RETVAL=$? 65 | } 66 | 67 | case "$1" in 68 | start) 69 | start 70 | ;; 71 | stop) 72 | stop 73 | ;; 74 | status) 75 | status 76 | ;; 77 | restart) 78 | restart 79 | ;; 80 | reload) 81 | reload 82 | ;; 83 | *) 84 | echo "Usage: {start|stop|status|restart|reload}" 85 | exit 1 86 | ;; 87 | esac 88 | exit $RETVAL 89 | -------------------------------------------------------------------------------- /scripts/kill.js: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ':' // Hack to pass parameters to Node before running this file 4 | ':' //; [ -f ~/.pm2/custom_options.sh ] && . ~/.pm2/custom_options.sh || : ; exec "`command -v node || command -v nodejs`" $PM2_NODE_OPTIONS "$0" "$@" 5 | 6 | var cst = require('../constants.js'); 7 | var fs = require('fs'); 8 | var pm2 = require('..'); 9 | 10 | function killEverything() { 11 | var pm2_pid = null; 12 | var interactor_pid = null; 13 | 14 | try { 15 | pm2_pid = fs.readFileSync(cst.PM2_PID_FILE_PATH); 16 | } catch(e) { 17 | console.log('PM2 pid file EEXIST'); 18 | process.exit(1); 19 | } 20 | 21 | try { 22 | interactor_pid = fs.readFileSync(cst.INTERACTOR_PID_PATH); 23 | } catch(e) { 24 | } 25 | 26 | if (interactor_pid) { 27 | try { 28 | console.log('Killing interactor'); 29 | process.kill(interactor_pid); 30 | } 31 | catch (err) { 32 | } 33 | } 34 | 35 | if (pm2_pid) { 36 | try { 37 | console.log('Killing PM2'); 38 | process.kill(pm2_pid); 39 | } 40 | catch (err) { 41 | } 42 | } 43 | 44 | setTimeout(function() { 45 | process.exit(0); 46 | }, 100); 47 | } 48 | 49 | 50 | var fallback = require('pm2-rpc-fallback').fallback; 51 | 52 | fallback(cst, function(err, data) { 53 | if (err && err.online) { 54 | // Right RPC communcation 55 | console.log('Stopping PM2'); 56 | pm2.connect(function() { 57 | pm2.updatePM2(function() { 58 | pm2.disconnect(function() { 59 | return process.exit(1); 60 | }); 61 | }); 62 | }); 63 | return false; 64 | } 65 | else if (err && err.offline) { 66 | console.log('PM2 already offline'); 67 | return process.exit(0); 68 | } 69 | else if (err) { 70 | return killEverything(); 71 | } 72 | if (data) { 73 | console.log('Killing old PM2'); 74 | return killEverything(); 75 | } 76 | return false; 77 | }); 78 | -------------------------------------------------------------------------------- /lib/HttpInterface.js: -------------------------------------------------------------------------------- 1 | // 2 | // PM2 Monit and Server web interface 3 | // Disserve JSON in light way 4 | // by Strzelewicz Alexandre 5 | // 6 | 7 | var http = require('http'); 8 | var os = require('os'); 9 | var Satan = require('./Satan'); 10 | var urlT = require('url'); 11 | var cst = require('../constants.js'); 12 | 13 | // Start daemon 14 | // 15 | // Usually it would be is started in the parent process already, 16 | // but if I run "node HttpInterface" directly, I would probably 17 | // like it to be not daemonized 18 | Satan.start(true); 19 | 20 | http.createServer(function (req, res) { 21 | // Add CORS headers to allow browsers to fetch data directly 22 | res.setHeader('Access-Control-Allow-Origin', '*'); 23 | res.setHeader('Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With'); 24 | res.setHeader('Access-Control-Allow-Methods', 'GET'); 25 | 26 | // We always send json 27 | res.setHeader('Content-Type','application/json'); 28 | 29 | var path = urlT.parse(req.url).pathname; 30 | 31 | console.log('Access on PM2 monit point %s', path); 32 | 33 | 34 | if (path == '/') { 35 | // Main monit route 36 | Satan.executeRemote('getMonitorData', {}, function(err, data_proc) { 37 | var data = { 38 | system_info: { hostname: os.hostname(), 39 | uptime: os.uptime() 40 | }, 41 | monit: { loadavg: os.loadavg(), 42 | total_mem: os.totalmem(), 43 | free_mem: os.freemem(), 44 | cpu: os.cpus(), 45 | interfaces: os.networkInterfaces() 46 | }, 47 | processes: data_proc 48 | }; 49 | 50 | res.statusCode = 200; 51 | res.write(JSON.stringify(data)); 52 | return res.end(); 53 | }); 54 | } 55 | else { 56 | // 404 57 | res.statusCode = 404; 58 | res.write(JSON.stringify({err : '404'})); 59 | return res.end(); 60 | } 61 | }).listen(cst.WEB_INTERFACE); 62 | -------------------------------------------------------------------------------- /test/bash/cli2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | ############# TEST 9 | 10 | echo -e "\033[1mRunning tests:\033[0m" 11 | 12 | $pm2 kill 13 | spec "kill daemon" 14 | 15 | echo "---- Start an app, stop it, if state stopped and started, restart stopped app" 16 | $pm2 start echo.js 17 | spec "Should start an app by script.js" 18 | $pm2 stop echo.js 19 | spec "Should stop an app by script.js" 20 | $pm2 restart echo.js 21 | spec "Should restart an app by script.js (TRANSITIONAL STATE)" 22 | 23 | ############### 24 | $pm2 delete all 25 | 26 | echo "---- BY_NAME Start an app, stop it, if state stopped and started, restart stopped app" 27 | 28 | $pm2 start echo.js --name gege 29 | should 'should app be online' 'online' 1 30 | $pm2 stop gege 31 | should 'should app be stopped' 'stopped' 1 32 | $pm2 restart gege 33 | should 'should app be online once restart called' 'online' 1 34 | 35 | ############### 36 | $pm2 delete all 37 | 38 | echo "Start an app, start it one more time, if started, throw message" 39 | $pm2 start echo.js 40 | $pm2 start echo.js 41 | ispec "Should not re start app" 42 | 43 | ########### DELETED STUFF BY ID 44 | $pm2 kill 45 | 46 | $pm2 start echo.js 47 | $pm2 delete 0 48 | should 'should has been deleted process by id' "name: 'echo'" 0 49 | 50 | ########### DELETED STUFF BY NAME 51 | $pm2 delete all 52 | 53 | $pm2 start echo.js --name test 54 | $pm2 delete test 55 | should 'should has been deleted process by name' "name: 'test'" 0 56 | 57 | ########### DELETED STUFF BY SCRIPT 58 | $pm2 delete all 59 | 60 | $pm2 start echo.js 61 | $pm2 delete echo.js 62 | $pm2 list 63 | should 'should has been deleted process by script' "name: 'echo'" 0 64 | 65 | 66 | ########### OPTIONS OUTPUT FILES 67 | $pm2 kill 68 | 69 | $pm2 start echo.js -o outech.log -e errech.log --name gmail -i 10 70 | sleep 0.5 71 | cat outech-0.log > /dev/null 72 | spec "file outech-0.log exist" 73 | cat errech-0.log > /dev/null 74 | spec "file errech-0.log exist" 75 | -------------------------------------------------------------------------------- /lib/Log.js: -------------------------------------------------------------------------------- 1 | // 2 | // Display a file in streaming 3 | // 4 | var fs = require('fs'); 5 | 6 | var colors = [ 7 | '\x1B[34m', // blue 8 | '\x1B[36m', // cyan 9 | '\x1B[32m', // green 10 | '\x1B[35m', // magenta 11 | '\x1B[31m', // red 12 | '\x1B[90m', // grey 13 | '\x1B[33m' // yellow 14 | ]; 15 | 16 | var gl_idx = 0; 17 | var db = []; 18 | 19 | var Log = module.exports = {}; 20 | 21 | /** 22 | * Description 23 | * @method stream 24 | * @param {} path 25 | * @param {} title 26 | * @return 27 | */ 28 | Log.stream = function(path, title) { 29 | if (title === undefined) 30 | title = gl_idx; 31 | 32 | try { 33 | var currSize = fs.statSync(path).size - 1000; 34 | currSize = currSize > 0 ? currSize : 0; 35 | } catch(e) { 36 | if (e.code == 'ENOENT') 37 | console.log('%s with %s file not found', title, path); 38 | return false; 39 | } 40 | 41 | var odb = db[title] = {color : colors[gl_idx++ % colors.length], l : 0}; 42 | 43 | var _stream = function() { 44 | fs.stat(path, function(err, stat) { 45 | var prevSize = stat.size; 46 | 47 | if (currSize > prevSize) return true; 48 | 49 | var rstream = fs.createReadStream(path, { 50 | encoding : 'utf8', 51 | start : currSize, 52 | end : prevSize 53 | }); 54 | 55 | rstream.on('data', function(data) { 56 | print_data(odb, title, data); 57 | }); 58 | 59 | currSize = stat.size; 60 | return true; 61 | }); 62 | } 63 | 64 | _stream() 65 | 66 | fs.watch(path, function(ev, filename) { 67 | if (ev == 'rename') 68 | return console.error('Renaming file ?'); 69 | 70 | _stream() 71 | return true; 72 | }); 73 | }; 74 | 75 | // 76 | // Privates 77 | // 78 | /** 79 | * Description 80 | * @method print_data 81 | * @param {} odb 82 | * @param {} title 83 | * @param {} data 84 | * @return 85 | */ 86 | function print_data(odb, title, data) { 87 | var lines = data.split('\n'); 88 | 89 | lines.forEach(function(l) { 90 | if (l) 91 | console.log(odb.color + '[%s]\x1B[39m %s', title, l); 92 | }); 93 | } 94 | -------------------------------------------------------------------------------- /lib/Interactor/WatchDog.js: -------------------------------------------------------------------------------- 1 | 2 | var pm2 = require('../..'); 3 | var debug = require('debug')('interface:watchdog'); 4 | 5 | process.env.PM2_AGENT_ONLINE = true; 6 | 7 | var WatchDog = module.exports = { 8 | start : function(p) { 9 | var self = this; 10 | this.ipm2 = p.conf.ipm2; 11 | this.relaunching = false; 12 | 13 | /** 14 | * Handle PM2 connection state changes 15 | */ 16 | this.ipm2.on('ready', function() { 17 | console.log('[WATCHDOG] Connected to PM2'); 18 | self.relaunching = false; 19 | self.autoDump(); 20 | }); 21 | 22 | this.ipm2.on('reconnecting', function() { 23 | console.log('[WATCHDOG] PM2 is disconnected - Relaunching PM2'); 24 | 25 | if (self.relaunching === true) return console.log('[WATCHDOG] Already relaunching PM2'); 26 | self.relaunching = true; 27 | 28 | if (self.dump_interval) 29 | clearInterval(self.dump_interval); 30 | 31 | return WatchDog.resurrect(); 32 | }); 33 | }, 34 | resurrect : function() { 35 | var self = this; 36 | 37 | console.log('[WATCHDOG] Trying to launch PM2 #1'); 38 | 39 | 40 | pm2.connect(function() { 41 | console.log('[WATCHDOG] PM2 successfully launched. Resurrecting processes'); 42 | 43 | pm2.resurrect(function(err) { 44 | if (err) { 45 | self.relaunching = false; 46 | return console.error('[WATCHDOG] Error when resurrect'); 47 | } 48 | console.log('PM2 has been resurrected'); 49 | self.relaunching = false; 50 | pm2.disconnect(function() {}); 51 | return false; 52 | }); 53 | return false; 54 | }); 55 | }, 56 | autoDump : function() { 57 | var self = this; 58 | 59 | this.dump_interval = setInterval(function() { 60 | pm2.connect(function() { 61 | if (self.relaunching == true) return false; 62 | 63 | pm2.dump(function(err) { 64 | if (err) return console.error('[WATCHDOG] Error when dumping'); 65 | debug('PM2 process list dumped'); 66 | pm2.disconnect(function() {}); 67 | return false; 68 | }); 69 | }); 70 | }, 5000); 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /test/bash/misc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | echo -e "\033[1mRunning tests:\033[0m" 9 | 10 | # 11 | # Max memory auto restart option 12 | # 13 | # -max-memory-restart option && maxMemoryRestart (via JSON file) 14 | # 15 | $pm2 start big-array.js --max-memory-restart 19 16 | sleep 7 17 | $pm2 list 18 | should 'process should been restarted' 'restart_time: 0' 0 19 | 20 | $pm2 delete all 21 | 22 | # 23 | # Via JSON 24 | # 25 | $pm2 start max-mem.json 26 | sleep 7 27 | $pm2 list 28 | should 'process should been restarted' 'restart_time: 0' 0 29 | 30 | $pm2 delete all 31 | 32 | $pm2 start env.js 33 | 34 | OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` 35 | 36 | cat /dev/null > $OUT_LOG 37 | 38 | sleep 1 39 | 40 | OUT=`cat $OUT_LOG | head -n 1` 41 | 42 | if [ $OUT = "undefined" ] 43 | then 44 | success "environment variable not defined" 45 | else 46 | fail "environment defined ? wtf ?" 47 | fi 48 | 49 | $pm2 delete all 50 | 51 | $pm2 start env.json 52 | 53 | cat /dev/null > $OUT_LOG 54 | 55 | sleep 1 56 | 57 | OUT=`cat $OUT_LOG | head -n 1` 58 | 59 | if [ "$OUT" = "undefined" ] 60 | then 61 | fail "environment variable hasnt been defined" 62 | else 63 | success "environment variable successfully defined" 64 | fi 65 | 66 | 67 | ##################### 68 | # Merge logs option # 69 | ##################### 70 | $pm2 kill 71 | 72 | rm outmerge* 73 | 74 | $pm2 start echo.js -i 4 -o outmerge.log 75 | 76 | cat outmerge.log > /dev/null 77 | ispec 'file outmerge.log should not exist' 78 | 79 | cat outmerge-0.log > /dev/null 80 | spec 'file outmerge-0.log should exist' 81 | 82 | rm outmerge* 83 | 84 | ############ Now with --merge option 85 | 86 | $pm2 kill 87 | 88 | rm outmerge* 89 | 90 | $pm2 start echo.js -i 4 -o outmerge.log --merge-logs 91 | 92 | cat outmerge.log > /dev/null 93 | spec 'file outmerge.log should exist' 94 | 95 | cat outmerge-0.log > /dev/null 96 | ispec 'file outmerge-0.log should not exist' 97 | 98 | rm outmerge* 99 | 100 | ########### coffee cluster test 101 | $pm2 delete all 102 | 103 | $pm2 start echo.coffee 104 | 105 | should 'process should not have been restarted' 'restart_time: 0' 1 106 | should 'process should be online' "status: 'online'" 1 107 | -------------------------------------------------------------------------------- /lib/God/ClusterMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @file Cluster execution functions related 5 | * @author Alexandre Strzelewicz 6 | * @project PM2 7 | */ 8 | 9 | var cluster = require('cluster'); 10 | var fs = require('fs'); 11 | var cst = require('../../constants.js'); 12 | var util = require('util'); 13 | var Common = require('../Common'); 14 | 15 | /** 16 | * Description 17 | * @method exports 18 | * @param {} God 19 | * @return 20 | */ 21 | module.exports = function ClusterMode(God) { 22 | 23 | /** 24 | * For Node apps - Cluster mode 25 | * It will wrap the code and enable load-balancing mode 26 | * @method nodeApp 27 | * @param {} env_copy 28 | * @param {} cb 29 | * @return Literal 30 | */ 31 | God.nodeApp = function nodeApp(env_copy, cb){ 32 | var clu = null; 33 | 34 | // if (fs.existsSync(env_copy.pm_exec_path) == false) { 35 | // return cb(God.logAndGenerateError('Script ' + env_copy.pm_exec_path + ' missing'), {}); 36 | // } 37 | 38 | console.log('Entering in node wrap logic (cluster_mode) for script %s', env_copy.pm_exec_path); 39 | 40 | if (env_copy.node_args && Array.isArray(env_copy.node_args)) { 41 | cluster.settings.execArgv = env_copy.node_args; 42 | } 43 | 44 | try { 45 | clu = cluster.fork(env_copy); 46 | } catch(e) { 47 | God.logAndGenerateError(e); 48 | return cb(e); 49 | } 50 | 51 | clu.pm2_env = env_copy; 52 | 53 | // Receive message from child 54 | clu.on('message', function cluMessage(msg) { 55 | var proc_data = Common.serialize(clu); 56 | 57 | switch (msg.type) { 58 | case 'process:exception': 59 | God.bus.emit('process:exception', {process : proc_data, data : msg, err : msg.err}); 60 | break; 61 | case 'log:out': 62 | God.bus.emit('log:out', {process : proc_data, data : msg.data}); 63 | break; 64 | case 'log:err': 65 | God.bus.emit('log:err', {process : proc_data, data : msg.data}); 66 | break; 67 | case 'human_event': 68 | God.bus.emit('human_event', {process : proc_data, data : util._extend(msg, {type:msg.name})}); 69 | break; 70 | default: // Permits to send message to external from the app 71 | God.bus.emit(msg.type ? msg.type : 'process:msg', {process : proc_data, data : msg }); 72 | } 73 | }); 74 | 75 | return cb(null, clu); 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /lib/Watcher.js: -------------------------------------------------------------------------------- 1 | var chokidar = require('chokidar'); 2 | var p = require('path'); 3 | var util = require('util'); 4 | var log = require('debug')('pm2:watch') 5 | 6 | module.exports = { 7 | watchers: [], 8 | /** 9 | * Watch folder for changes and restart 10 | * @method watch 11 | * @param {Object} pm2_env pm2 app environnement 12 | * @return MemberExpression 13 | */ 14 | watch: function(pm2_env) { 15 | if(this.watchers[pm2_env.pm_id]) { 16 | delete this.watchers[pm2_env.pm_id]; 17 | } 18 | 19 | //setup the combined RegEx 20 | var ignored; 21 | if(pm2_env.ignoreWatch){ 22 | var combined = pm2_env.ignoreWatch.reduce(function(previous, current) { 23 | return previous + '|' + current; 24 | }); 25 | 26 | ignored = new RegExp(combined); 27 | } else { 28 | ignored = /[\/\\]\.|node_modules/ 29 | } 30 | 31 | var watch; 32 | 33 | // check if pm2_env.watch is an array or a string 34 | 35 | if(pm2_env.watch && 36 | (util.isArray(pm2_env.watch) || typeof pm2_env.watch == 'string' || pm2_env.watch instanceof String)) { 37 | 38 | watch = pm2_env.watch; 39 | 40 | } else { 41 | watch = p.dirname(pm2_env.pm_exec_path); 42 | } 43 | 44 | this.watchers[pm2_env.pm_id] = 45 | 46 | chokidar.watch( 47 | 48 | watch, 49 | 50 | { 51 | ignored: ignored, 52 | persistent: false, 53 | ignoreInitial: true, 54 | 55 | } 56 | ) 57 | .on('all', function(event, path) { 58 | var self = this; 59 | 60 | if(self.restarting === true) { 61 | log('Already restarting skipping'); 62 | return; 63 | } else { 64 | 65 | self.restarting = true; 66 | 67 | require('./God').restartProcessId({id:pm2_env.pm_id}, function(err, list) { 68 | self.restarting = false; 69 | 70 | if(err) { 71 | log('Error while restarting', err); 72 | } else { 73 | log('Process restarted'); 74 | } 75 | }); 76 | 77 | } 78 | }); 79 | 80 | 81 | 82 | return this.watchers[pm2_env.pm_id]; 83 | 84 | }, 85 | /** 86 | * Description 87 | * @method close 88 | * @param {} id 89 | * @return 90 | */ 91 | close: function(id) { 92 | if(this.watchers[id]) { 93 | return this.watchers[id].close(); 94 | } else { 95 | return false; 96 | } 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /test/programmatic/monit.mocha.js: -------------------------------------------------------------------------------- 1 | var p = require('path') 2 | , root = p.resolve(__dirname, '../../') 3 | , fixtures = p.join(root, 'test/fixtures/') 4 | // , spawn = require('child_process').spawn 5 | , Spawner = require('promise-spawner') 6 | , async = require('async') 7 | 8 | , bin = p.join(root, '/bin/pm2') 9 | , pm2 = require(p.join(root, 'index.js')) 10 | , ids = [] 11 | 12 | var timeout = function(cb, time) { 13 | return function() { 14 | setTimeout(cb, time || 2000) 15 | } 16 | } 17 | 18 | describe('Monitor', function() { 19 | 20 | before(function(cb) { 21 | pm2.connect(function() { 22 | pm2.delete('all', function(err, ret) { 23 | cb() 24 | }) 25 | }) 26 | }) 27 | 28 | 29 | after(function(cb) { 30 | pm2.killDaemon(function() { 31 | pm2.disconnect(function() { 32 | cb() 33 | }) 34 | }); 35 | }) 36 | 37 | 38 | it('should start', function() { 39 | 40 | var modifiers = { 41 | out: function(d) { return d }, 42 | err: 'error: ' 43 | } 44 | 45 | var spawner = new Spawner(modifiers) 46 | 47 | //spawner gives you global streams from spawned stdout and stderr 48 | spawner.out.pipe(process.stdout) 49 | spawner.err.pipe(process.stdout) 50 | 51 | spawner 52 | .spawn(bin + ' monit') 53 | .catch(function(code) { 54 | console.log('Script failed with code ', code) 55 | process.exit(code) 56 | }) 57 | .then(function(code) { 58 | if(this.data.err) { 59 | console.log(this.data.err) 60 | } 61 | 62 | process.exit(code) 63 | }) 64 | 65 | }) 66 | 67 | it('should start monitoring', function(cb) { 68 | 69 | var paths = [p.join(fixtures, 'quit.js'), p.join(fixtures, 'killtoofast.js'), p.join(fixtures, 'server.js'), p.join(fixtures, 'echo.js')] 70 | 71 | async.eachSeries(paths, function(item, next) { 72 | 73 | pm2.start(item, {}, function(err, data) { 74 | if(err) 75 | throw err 76 | 77 | ids.push(data[0].pm2_env.pm_id); 78 | 79 | setTimeout(function() { 80 | next(); 81 | }, 2000) 82 | }) 83 | 84 | }, cb) 85 | 86 | }) 87 | 88 | it('should delete', function(cb) { 89 | pm2.delete(ids[3], timeout(cb)) 90 | }) 91 | 92 | it('should stop', function(cb) { 93 | pm2.stop(ids[2], timeout(cb)) 94 | }) 95 | 96 | it('should restart', function(cb) { 97 | pm2.restart(ids[1], timeout(cb)) 98 | }) 99 | 100 | after(function() { 101 | pm2.connect(function() { 102 | pm2.delete('all', function(err, ret) { 103 | process.exit(0) 104 | }) 105 | }) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | // 2 | // Modifying these values break tests and can break 3 | // pm2-interface module (because of ports) 4 | // 5 | 6 | var p = require('path'); 7 | var fs = require('fs'); 8 | var util = require('util'); 9 | 10 | var HOME = process.env.PM2_HOME || process.env.HOME; 11 | 12 | // Dont change this or the pm2 could not load custom_options.sh because of 13 | // header in bin/pm2 14 | var DEFAULT_FILE_PATH = p.resolve(HOME, '.pm2'); 15 | 16 | var default_conf = { 17 | DEFAULT_FILE_PATH : DEFAULT_FILE_PATH, 18 | PM2_LOG_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'pm2.log'), 19 | PM2_PID_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'pm2.pid'), 20 | DEFAULT_PID_PATH : p.join(DEFAULT_FILE_PATH, 'pids'), 21 | DEFAULT_LOG_PATH : p.join(DEFAULT_FILE_PATH, 'logs'), 22 | DUMP_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'dump.pm2'), 23 | 24 | SAMPLE_CONF_FILE : p.join('..', 'lib', 'custom_options.sh'), 25 | PM2_CONF_FILE : p.join(DEFAULT_FILE_PATH, 'custom_options.sh'), 26 | 27 | DAEMON_BIND_HOST : process.env.PM2_BIND_ADDR || 'localhost', 28 | DAEMON_RPC_PORT : parseInt(process.env.PM2_RPC_PORT) || 6666, // RPC commands 29 | DAEMON_PUB_PORT : parseInt(process.env.PM2_PUB_PORT) || 6667, // Realtime events 30 | 31 | CODE_UNCAUGHTEXCEPTION : 100, 32 | 33 | CONCURRENT_ACTIONS : 1, 34 | GRACEFUL_TIMEOUT : parseInt(process.env.PM2_GRACEFUL_TIMEOUT) || 8000, 35 | 36 | DEBUG : process.env.PM2_DEBUG || false, 37 | WEB_INTERFACE : parseInt(process.env.PM2_API_PORT) || 9615, 38 | MODIFY_REQUIRE : process.env.PM2_MODIFY_REQUIRE || false, 39 | PREFIX_MSG : '\x1B[32m[PM2] \x1B[39m', 40 | PREFIX_MSG_ERR : '\x1B[31m[PM2] [ERROR] \x1B[39m', 41 | SAMPLE_FILE_PATH : '../lib/sample.json', 42 | 43 | CENTOS_STARTUP_SCRIPT : '../lib/scripts/pm2-init-centos.sh', 44 | UBUNTU_STARTUP_SCRIPT : '../lib/scripts/pm2-init.sh', 45 | SYSTEMD_STARTUP_SCRIPT: '../lib/scripts/pm2.service', 46 | AMAZON_STARTUP_SCRIPT: '../lib/scripts/pm2-init-amazon.sh', 47 | GENTOO_STARTUP_SCRIPT: '../lib/scripts/pm2', 48 | 49 | SUCCESS_EXIT : 0, 50 | ERROR_EXIT : 1, 51 | 52 | ONLINE_STATUS : 'online', 53 | STOPPED_STATUS : 'stopped', 54 | STOPPING_STATUS : 'stopping', 55 | LAUNCHING_STATUS : 'launching', 56 | ERRORED_STATUS : 'errored', 57 | ONE_LAUNCH_STATUS : 'one-launch-status', 58 | 59 | REMOTE_PORT : 41624, 60 | REMOTE_REVERSE_PORT : 43554, 61 | REMOTE_HOST : 's1.keymetrics.io', 62 | INTERACTION_CONF : p.join(DEFAULT_FILE_PATH, 'agent.json'), 63 | SEND_INTERVAL : 1000, 64 | 65 | INTERACTOR_LOG_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'agent.log'), 66 | INTERACTOR_PID_PATH : p.join(DEFAULT_FILE_PATH, 'agent.pid'), 67 | 68 | INTERACTOR_RPC_PORT : parseInt(process.env.PM2_INTERACTOR_PORT) || 6668 69 | }; 70 | 71 | module.exports = default_conf; 72 | -------------------------------------------------------------------------------- /lib/Interactor/Filter.js: -------------------------------------------------------------------------------- 1 | 2 | var os = require('os'); 3 | 4 | var Filter = {}; 5 | 6 | Filter.getProcessID = function(machine_name, name, id) { 7 | return machine_name + ':' + name + ':' + id; 8 | }; 9 | 10 | Filter.status = function(processes, conf) { 11 | if (!processes) return null; 12 | 13 | var filter_procs = []; 14 | 15 | processes.forEach(function(proc) { 16 | filter_procs.push({ 17 | pid : proc.pid, 18 | name : proc.pm2_env.name, 19 | interpreter : proc.pm2_env.exec_interpreter, 20 | restart_time : proc.pm2_env.restart_time, 21 | created_at : proc.pm2_env.created_at, 22 | exec_mode : proc.pm2_env.exec_mode, 23 | watching : proc.pm2_env.watch, 24 | unstable_restarts : proc.pm2_env.unstable_restarts, 25 | pm_uptime : proc.pm2_env.pm_uptime, 26 | status : proc.pm2_env.status, 27 | pm_id : proc.pm2_env.pm_id, 28 | cpu : Math.floor(proc.monit.cpu) || 0, 29 | memory : Math.floor(proc.monit.memory) || 0, 30 | process_id : Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name, proc.pm2_env.pm_id), 31 | axm_actions : proc.pm2_env.axm_actions || [] 32 | }); 33 | }); 34 | 35 | return { 36 | process : filter_procs, 37 | server : { 38 | loadavg : os.loadavg(), 39 | total_mem : os.totalmem(), 40 | free_mem : os.freemem(), 41 | cpu : os.cpus(), 42 | hostname : os.hostname(), 43 | uptime : os.uptime(), 44 | type : os.type(), 45 | platform : os.platform(), 46 | arch : os.arch(), 47 | interaction : conf.REVERSE_INTERACT, 48 | pm2_version : conf.PM2_VERSION 49 | } 50 | }; 51 | }; 52 | 53 | Filter.monitoring = function(processes, conf) { 54 | if (!processes) return null; 55 | 56 | var filter_procs = {}; 57 | 58 | processes.forEach(function(proc) { 59 | filter_procs[Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name,proc.pm2_env.pm_id)] = [proc.monit.cpu, proc.monit.memory]; 60 | }); 61 | 62 | return { 63 | loadavg : os.loadavg(), 64 | total_mem : os.totalmem(), 65 | free_mem : os.freemem(), 66 | processes : filter_procs 67 | }; 68 | }; 69 | 70 | Filter.pruneProcessObj = function(process, machine_name) { 71 | return { 72 | pm_id : process.pm2_env.pm_id, 73 | name : process.pm2_env.name, 74 | state : process.pm2_env.status, 75 | server : machine_name 76 | }; 77 | }; 78 | 79 | Filter.processState = function(data) { 80 | var state = { 81 | state : data.process.pm2_env.status, 82 | name : data.process.pm2_env.name, 83 | pm_id : data.process.pm2_env.pm_id, 84 | restart_time : data.process.pm2_env.restart_time, 85 | uptime : data.process.pm2_env.uptime 86 | }; 87 | return state; 88 | }; 89 | 90 | module.exports = Filter; 91 | -------------------------------------------------------------------------------- /test/programmatic/interactor.daemonizer.mocha.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var fs = require('fs'); 3 | var os = require('os'); 4 | var cst = require('../../constants'); 5 | var interactorDaemonizer = require('../../lib/Interactor/InteractorDaemonizer'); 6 | 7 | describe('Daemonizer interactor', function() { 8 | before(function(done) { 9 | delete process.env.PM2_SECRET_KEY; 10 | delete process.env.PM2_PUBLIC_KEY; 11 | 12 | try { 13 | fs.unlinkSync(cst.INTERACTION_CONF); 14 | } catch(e) { 15 | } 16 | done(); 17 | }); 18 | 19 | it('should try get set keys but get error because nothing exposed', function(done) { 20 | interactorDaemonizer.getSetKeys(null, null, null, function(err, data) { 21 | err.msg.should.not.be.null; 22 | done(); 23 | }); 24 | }); 25 | 26 | it('should work with env variables and create file', function(done) { 27 | process.env.PM2_SECRET_KEY = 'XXXS'; 28 | process.env.PM2_PUBLIC_KEY = 'XXXP'; 29 | 30 | interactorDaemonizer.getSetKeys(null, null, null, function(err, data) { 31 | should(err).be.null; 32 | data.secret_key.should.eql('XXXS'); 33 | data.public_key.should.eql('XXXP'); 34 | try { 35 | fs.statSync(cst.INTERACTION_CONF); 36 | } catch(e) { 37 | return done(e); 38 | } 39 | 40 | delete process.env.PM2_SECRET_KEY; 41 | delete process.env.PM2_PUBLIC_KEY; 42 | return done(); 43 | }); 44 | }); 45 | 46 | it('should retrieve data from file without env variable', function(done) { 47 | interactorDaemonizer.getSetKeys(null, null, null, function(err, data) { 48 | should(err).be.null; 49 | data.secret_key.should.eql('XXXS'); 50 | data.public_key.should.eql('XXXP'); 51 | return done(); 52 | }); 53 | }); 54 | 55 | it('should set new keys and write in configuration file', function(done) { 56 | interactorDaemonizer.getSetKeys('XXXS2', 'XXXP2', null, function(err, data) { 57 | should(err).be.null; 58 | data.secret_key.should.eql('XXXS2'); 59 | data.public_key.should.eql('XXXP2'); 60 | 61 | var interaction_conf = JSON.parse(fs.readFileSync(cst.INTERACTION_CONF)); 62 | interaction_conf.secret_key.should.eql('XXXS2'); 63 | interaction_conf.public_key.should.eql('XXXP2'); 64 | interaction_conf.machine_name.should.eql(os.hostname()); 65 | return done(); 66 | }); 67 | }); 68 | 69 | it('should work with object passed instead of direct params', function(done) { 70 | interactorDaemonizer.getSetKeys({ 71 | secret_key : 'XXXS3', 72 | public_key : 'XXXP3' 73 | }, function(err, data) { 74 | should(err).be.null; 75 | data.secret_key.should.eql('XXXS3'); 76 | data.public_key.should.eql('XXXP3'); 77 | 78 | var interaction_conf = JSON.parse(fs.readFileSync(cst.INTERACTION_CONF)); 79 | interaction_conf.secret_key.should.eql('XXXS3'); 80 | interaction_conf.public_key.should.eql('XXXP3'); 81 | interaction_conf.machine_name.should.eql(os.hostname()); 82 | return done(); 83 | }); 84 | }); 85 | 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /test/bash/watch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | cd $file_path 7 | 8 | function waituntil { 9 | for (( i = 0; i <= $1; i++ )); do 10 | sleep 0.2 11 | echo -n "." 12 | done 13 | echo "" 14 | } 15 | 16 | echo -e "\033[1mRunning tests:\033[0m" 17 | 18 | ##################### 19 | # Watch for changes # 20 | ##################### 21 | 22 | >server-watch.js 23 | 24 | $pm2 kill 25 | 26 | cp server-watch.bak.js server-watch.js 27 | 28 | $pm2 start server-watch.js --watch 29 | 30 | should 'process should be watched' 'watch: true' 1 31 | 32 | echo "console.log('test');" >> server-watch.js 33 | 34 | sleep 1 35 | 36 | cat server-watch.js 37 | $pm2 list 38 | 39 | should 'process should have been restarted' 'restart_time: 1' 1 40 | should 'process should be online' "status: 'online'" 1 41 | 42 | $pm2 kill 43 | 44 | sleep 1 45 | 46 | rm server-watch.js 47 | 48 | ############### 49 | 50 | # Script should fail but be started again on next change 51 | # Sadly travis has an issue with that test, it's working and tested with node v0.11.10 52 | # Feel free to uncomment and report to http://github.com/Unitech/pm2/issues 53 | 54 | # cp server-watch.bak.js server-watch.js 55 | 56 | # $pm2 start --watch server-watch.js 57 | 58 | # echo "setTimeout(function() { process.exit(0) }, 1)" > server-watch.js 59 | 60 | # for (( i = 0; i <= 30; i++ )); do 61 | # sleep 0.2 62 | # echo -n "." 63 | # done 64 | 65 | # $pm2 list 66 | # should 'should have stopped unstable process' 'errored' 1 67 | 68 | # cp server-watch.bak.js server-watch.js 69 | 70 | # for (( i = 0; i <= 10; i++ )); do 71 | # sleep 0.2 72 | # echo -n "." 73 | # done 74 | 75 | # $pm2 list 76 | # should 'should start the errored process again while putting file back' 'online' 1 77 | 78 | # $pm2 kill 79 | # rm server-watch.js 80 | 81 | ############### 82 | 83 | cp server-watch.bak.js server-watch.js 84 | 85 | $pm2 start --watch server-watch.js 86 | 87 | $pm2 restart 0 88 | 89 | should 'process should be watched' 'watch: true' 1 90 | 91 | $pm2 stop --watch 0 92 | 93 | should 'process should have stopped beeing watched' 'watch: false' 1 94 | 95 | echo "setInterval(function() { console.log('still ok'); }, 100);" > server-watch.js 96 | 97 | should 'process should not have been restarted on file change' 'restart_time: 1' 1 98 | 99 | cp server-watch.bak.js server-watch.js 100 | 101 | $pm2 restart 0 102 | 103 | should 'process should restart and not be watched' 'watch: false' 1 104 | 105 | #$pm2 restart --watch 0 106 | #should 'process should be watched' 'watch: true' 1 107 | 108 | $pm2 kill 109 | 110 | rm server-watch.js 111 | ############# 112 | # JSON test # 113 | ############# 114 | # we've already seen before that "watch: true" is really watching when changing a file 115 | 116 | # $pm2 start --watch all.json 117 | 118 | # should 'processes should be watched' 'watch: true' 8 119 | 120 | # $pm2 stop --watch all 121 | 122 | # should 'processes should have stop being watched' 'watch: false' 8 123 | 124 | # $pm2 restart --watch all 125 | # should 'processes should be watched' 'watch: true' 8 126 | 127 | # $pm2 kill 128 | 129 | ########## 130 | # delete # 131 | ########## 132 | 133 | cp server-watch.bak.js server-watch.js 134 | 135 | $pm2 start server-watch.js --watch 136 | $pm2 stop 0 137 | $pm2 delete 0 138 | 139 | echo "setTimeout(function() { console.log('watch me!') })" >> server-watch.js 140 | 141 | waituntil 10 142 | 143 | should 'process should not have been restarted' 'watch: true' 0 144 | 145 | $pm2 kill 146 | rm server-watch.js 147 | -------------------------------------------------------------------------------- /test/programmatic/satan.mocha.js: -------------------------------------------------------------------------------- 1 | 2 | var should = require('should'); 3 | var assert = require('better-assert'); 4 | var path = require('path'); 5 | var pm2 = require('../..'); 6 | 7 | var Satan = require('../../lib/Satan'); 8 | 9 | describe('Satan', function() { 10 | 11 | after(function(done) { 12 | pm2.delete('all', function(err, ret) { 13 | pm2.disconnect(done); 14 | }); 15 | }); 16 | 17 | it('should start Satan interaction', function(done) { 18 | Satan.start(function(err) { 19 | should(err).be.null; 20 | done(); 21 | }); 22 | }); 23 | 24 | it('should auto instancy itself, fire event and kill daemon', function(done) { 25 | Satan = require('../../lib/Satan'); 26 | Satan.start(); 27 | process.once('satan:client:ready', function() { 28 | console.log('Client ready'); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('should have right properties', function() { 34 | Satan.should.have.property('remoteWrapper'); 35 | Satan.should.have.property('start'); 36 | Satan.should.have.property('launchRPC'); 37 | Satan.should.have.property('executeRemote'); 38 | Satan.should.have.property('launchDaemon'); 39 | Satan.should.have.property('getExposedMethods'); 40 | Satan.should.have.property('pingDaemon'); 41 | Satan.should.have.property('killDaemon'); 42 | }); 43 | 44 | 45 | describe('DAEMON', function() { 46 | it('should have the right exposed methods via RPC', function(done) { 47 | Satan.getExposedMethods(function(err, methods) { 48 | assert(err == null); 49 | methods.should.have.property('prepare'); 50 | methods.should.have.property('getMonitorData'); 51 | methods.should.have.property('getSystemData'); 52 | methods.should.have.property('stopProcessId'); 53 | methods.should.have.property('stopAll'); 54 | methods.should.have.property('stopProcessName'); 55 | methods.should.have.property('killMe'); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('should get an empty process list', function(done) { 61 | Satan.executeRemote('getMonitorData', {}, function(err, res) { 62 | assert(res.length === 0); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('should get an empty process list from system data', function(done) { 68 | Satan.executeRemote('getSystemData', {}, function(err, res) { 69 | assert(res.processes.length === 0); 70 | done(); 71 | }); 72 | }); 73 | 74 | 75 | it('should launch a process', function(done) { 76 | Satan.executeRemote('prepare', { 77 | pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/echo.js'), 78 | pm_err_log_path : path.resolve(process.cwd(), 'test/errLog.log'), 79 | pm_out_log_path : path.resolve(process.cwd(), 'test/outLog.log'), 80 | pm_pid_path : path.resolve(process.cwd(), 'test/child'), 81 | name : 'toto', 82 | exec_mode : 'cluster', 83 | instances : 4 84 | }, function(err, procs) { 85 | assert(err == null); 86 | assert(procs.length == 4); 87 | done(); 88 | }); 89 | }); 90 | 91 | it('should list 4 processes', function(done) { 92 | Satan.executeRemote('getMonitorData', {}, function(err, res) { 93 | assert(res.length === 4); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('should list 4 processes via system data', function(done) { 99 | Satan.executeRemote('getSystemData', {}, function(err, res) { 100 | assert(res.processes.length === 4); 101 | done(); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /lib/Interactor/ReverseInteractor.js: -------------------------------------------------------------------------------- 1 | 2 | var debug = require('debug')('interface:driver'); // Interface 3 | var nssocket = require('nssocket'); 4 | 5 | var Cipher = require('./Cipher.js'); 6 | 7 | var ReverseInteract = module.exports = { 8 | start : function(p) { 9 | if (!p.port || !p.host) throw new Error('port or host not declared in PullInteractor'); 10 | if (!p.conf || !p.conf.ipm2) throw new Error('ipm2 ĩs not initialized'); 11 | 12 | var self = this; 13 | 14 | this.conf = p.conf; 15 | this.ipm2 = p.conf.ipm2; 16 | this.socket = new nssocket.NsSocket({ 17 | type: 'tcp4' 18 | }); 19 | 20 | /** 21 | * Full duplex connect to AXM 22 | */ 23 | console.log('[REV] Connecting to %s:%s', p.host, p.port); 24 | 25 | this.socket.on('error', function(dt) { 26 | console.error('[REV] Error', dt); 27 | }); 28 | 29 | this.socket.on('close', function(dt) { 30 | console.log('[REV] Connection closed'); 31 | setTimeout(function() { 32 | console.log('[REV] Retrying to connect'); 33 | self.socket.connect(p.port, p.host); 34 | }, 2000); 35 | }); 36 | 37 | this.socket.on('start', function() { 38 | console.log('[REV] Connected to %s:%s', p.host, p.port); 39 | }); 40 | 41 | this.socket.connect(p.port, p.host); 42 | 43 | this.onMessage(); 44 | }, 45 | onMessage : function() { 46 | if (!this.socket) return console.error('Reverse interaction not initialized'); 47 | var self = this; 48 | 49 | /** 50 | * 'ask' event receive to identify local PM2 51 | */ 52 | this.socket.data('ask', function(raw_msg) { 53 | if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') { 54 | // Dont cipher data in test environment 55 | self.socket.send('ask:rep', { 56 | success : true, 57 | machine_name : self.conf.MACHINE_NAME, 58 | public_key : self.conf.PUBLIC_KEY 59 | }); 60 | } else { 61 | var ciphered_data = Cipher.cipherMessage(JSON.stringify({ 62 | machine_name : self.conf.MACHINE_NAME 63 | }), self.conf.SECRET_KEY); 64 | 65 | if (!ciphered_data) 66 | return console.error('Got wrong ciphering data %s %s', self.conf.MACHINE_NAME, self.conf.SECRET_KEY); 67 | 68 | self.socket.send('ask:rep', { 69 | data : ciphered_data, 70 | public_key : self.conf.PUBLIC_KEY 71 | }); 72 | } 73 | return false; 74 | }); 75 | 76 | this.socket.data('trigger:action', function(raw_msg) { 77 | var msg = {}; 78 | 79 | if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') 80 | msg = raw_msg; 81 | else 82 | msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY); 83 | 84 | console.log('New remote action %s triggered for process %s', msg.action_name, msg.process_id); 85 | self.ipm2.rpc.msgProcess({ 86 | id : msg.process_id, 87 | msg : msg.action_name 88 | }, function(err, data) { 89 | if (err) { 90 | return self.socket.send('trigger:action:failure', { 91 | success : false, 92 | err : err, 93 | id : msg.process_id, 94 | action_name : msg.action_name 95 | }); 96 | } 97 | console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s', 98 | msg.process_id, msg.action_name); 99 | 100 | return self.socket.send('trigger:action:success', { 101 | success : true, 102 | id : msg.process_id, 103 | action_name : msg.action_name 104 | }); 105 | }); 106 | }); 107 | return false; 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm2", 3 | "preferGlobal": "true", 4 | "version": "0.10.4", 5 | "os": [ 6 | "!win32" 7 | ], 8 | "engines": { 9 | "node": ">=0.10" 10 | }, 11 | "directories": { 12 | "bin": "./bin", 13 | "lib": "./lib", 14 | "example": "./examples" 15 | }, 16 | "author": { 17 | "name": "Strzelewicz Alexandre", 18 | "email": "as@unitech.io", 19 | "url": "http://pm2.io" 20 | }, 21 | "maintainers": [ 22 | "tknew ", 24 | "Alex Kocharin ", 25 | "achingbrain " 26 | ], 27 | "contributors": [ 28 | { 29 | "name": "Alex Kocharin", 30 | "email": "alex@kocharin.ru" 31 | }, 32 | { 33 | "name": "Soyuka", 34 | "email": "soyuka@gmail.com" 35 | }, 36 | { 37 | "name": "Soyuka", 38 | "email": "soyuka@gmail.com" 39 | }, 40 | { 41 | "name": "Xu Jingxin", 42 | "email": "sailxjx@gmail.com" 43 | }, 44 | { 45 | "name": "Ben Postlethwaite", 46 | "email": "post.ben.here@gmail.com" 47 | }, 48 | { 49 | "name": "Devo.ps", 50 | "email": "contact@devo.ps" 51 | }, 52 | { 53 | "name": "Bret Copeland", 54 | "email": "bret@atlantisflight.org" 55 | }, 56 | { 57 | "name": "John Hurliman", 58 | "email": "jhurliman@jhurliman.org" 59 | }, 60 | { 61 | "name": "TruongSinh Tran-Nguyen", 62 | "email": "i@truongsinh.pro" 63 | }, 64 | { 65 | "name": "Michael Hueuberger", 66 | "email": "michael.heuberger@binarykitchen.com" 67 | } 68 | ], 69 | "homepage": "https://github.com/Unitech/pm2", 70 | "description": "Modern CLI process manager for Node apps with a builtin load-balancer. Perfectly designed for microservices architecture.", 71 | "main": "index.js", 72 | "scripts": { 73 | "test": "NODE_ENV=test bash test/index.sh && bash test/main.sh", 74 | "preinstall" : "bash ./scripts/preinstall.sh", 75 | "postinstall" : "bash ./scripts/postinstall.sh" 76 | }, 77 | "keywords": [ 78 | "cli", 79 | "fault tolerant", 80 | "sysadmin", 81 | "tools", 82 | "pm2", 83 | "logs", 84 | "log", 85 | "json", 86 | "express", 87 | "hapi", 88 | "kraken", 89 | "reload", 90 | "microservice", 91 | "programmatic", 92 | "harmony", 93 | "node-pm2", 94 | "production", 95 | "keymetrics", 96 | "deploy", 97 | "deployment", 98 | "daemon", 99 | "supervisor", 100 | "nodemon", 101 | "pm2.io", 102 | "ghost", 103 | "ghost production", 104 | "monitoring", 105 | "process manager", 106 | "forever", 107 | "forever-monitor", 108 | "keep process alive", 109 | "process configuration", 110 | "clustering", 111 | "cluster cli", 112 | "cluster", 113 | "cron", 114 | "devops", 115 | "dev ops" 116 | ], 117 | "bin": { 118 | "pm2": "./bin/pm2" 119 | }, 120 | "dependencies": { 121 | "async": "~0.9.0", 122 | "axm": "~0.1.7", 123 | "axon": "~2.0.0", 124 | "chalk": "~0.5.1", 125 | "chokidar": "0.8.2", 126 | "cli-table": "~0.3.0", 127 | "coffee-script": "~1.7.1", 128 | "colors": "~0.6.2", 129 | "commander": "~2.3.0", 130 | "cron": "~1.0.4", 131 | "debug": "~1.0.2", 132 | "eventemitter2": "~0.4.14", 133 | "json-stringify-safe": "~5.0.0", 134 | "nssocket": "~0.5.1", 135 | "punt" : "~2.2.0", 136 | "pidusage": "~0.1.0", 137 | "pm2-axon-rpc": "~0.2.1", 138 | "pm2-deploy": "~0.0.4", 139 | "pm2-interface" : "~1.1.0", 140 | "pm2-multimeter": "~0.1.2", 141 | "pm2-rpc-fallback" : "~2.8.0", 142 | "moment" : "~2.8.2", 143 | "uid-number": "~0.0.5" 144 | }, 145 | "devDependencies": { 146 | "mocha": "^1.20.1", 147 | "should": "^4.0.0", 148 | "better-assert": "^1.0.0", 149 | "promise-spawner": "^0.0.3" 150 | }, 151 | "optionalDependencies": { 152 | "pm2-logs" : "~0.1.1", 153 | "ikt" : "git+http://ikt.pm2.io/ikt.git#master" 154 | }, 155 | "bugs": { 156 | "url": "https://github.com/Unitech/pm2/issues" 157 | }, 158 | "repository": { 159 | "type": "git", 160 | "url": "git://github.com/Unitech/pm2.git" 161 | }, 162 | "license": "AGPLv3" 163 | } 164 | -------------------------------------------------------------------------------- /lib/Interactor/Daemon.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var ipm2 = require('pm2-interface'); 4 | var rpc = require('pm2-axon-rpc'); 5 | var axon = require('axon'); 6 | var debug = require('debug')('interface:driver'); // Interface 7 | var chalk = require('chalk'); 8 | 9 | var pkg = require('../../package.json'); 10 | 11 | var cst = require('../../constants.js'); 12 | var Cipher = require('./Cipher.js'); 13 | var Filter = require('./Filter.js'); 14 | var ReverseInteractor = require('./ReverseInteractor.js'); 15 | var PushInteractor = require('./PushInteractor.js'); 16 | var WatchDog = require('./WatchDog.js'); 17 | 18 | var Daemon = { 19 | connectToPM2 : function() { 20 | return ipm2({bind_host: 'localhost'}); 21 | }, 22 | activateRPC : function() { 23 | console.log('Launching Interactor exposure'); 24 | 25 | var self = this; 26 | var rep = axon.socket('rep'); 27 | var daemon_server = new rpc.Server(rep); 28 | var sock = rep.bind(cst.INTERACTOR_RPC_PORT); 29 | 30 | daemon_server.expose({ 31 | kill : function(cb) { 32 | console.log('Killing interactor'); 33 | cb(null); 34 | setTimeout(function() { process.exit(cst.SUCCESS_EXIT) }, 50); 35 | }, 36 | getInfos : function(cb) { 37 | return cb(null, { 38 | machine_name : self.opts.MACHINE_NAME, 39 | public_key : self.opts.PUBLIC_KEY, 40 | secret_key : self.opts.SECRET_KEY, 41 | remote_host : cst.REMOTE_HOST, 42 | remote_port : cst.REMOTE_PORT, 43 | reverse_interaction : self.opts.REVERSE_INTERACT 44 | }); 45 | } 46 | }); 47 | return daemon_server; 48 | }, 49 | validateData : function() { 50 | var opts = {}; 51 | 52 | opts.MACHINE_NAME = process.env.PM2_MACHINE_NAME; 53 | opts.PUBLIC_KEY = process.env.PM2_PUBLIC_KEY; 54 | opts.SECRET_KEY = process.env.PM2_SECRET_KEY; 55 | opts.REVERSE_INTERACT = JSON.parse(process.env.PM2_REVERSE_INTERACT); 56 | opts.PM2_VERSION = pkg.version; 57 | if (!opts.MACHINE_NAME) { 58 | console.error('You must provide a PM2_MACHINE_NAME environment variable'); 59 | process.exit(cst.ERROR_EXIT); 60 | } 61 | else if (!opts.PUBLIC_KEY) { 62 | console.error('You must provide a PM2_PUBLIC_KEY environment variable'); 63 | process.exit(cst.ERROR_EXIT); 64 | } 65 | else if (!opts.SECRET_KEY) { 66 | console.error('You must provide a PM2_SECRET_KEY environment variable'); 67 | process.exit(cst.ERROR_EXIT); 68 | } 69 | 70 | if (process.send) 71 | process.send({ 72 | online : true, 73 | pid : process.pid, 74 | machine_name : opts.MACHINE_NAME, 75 | public_key : opts.PUBLIC_KEY, 76 | secret_key : opts.SECRET_KEY, 77 | reverse_interaction : opts.REVERSE_INTERACT 78 | }); 79 | 80 | return opts; 81 | }, 82 | start : function() { 83 | var self = this; 84 | 85 | self.opts = self.validateData(); 86 | self.opts.ipm2 = null; 87 | 88 | // Expose Interactor methods 89 | self.activateRPC(); 90 | self.opts.ipm2 = self.connectToPM2(); 91 | 92 | // WatchDog.start({ 93 | // conf : self.opts 94 | // }); 95 | 96 | // Then connect to external services 97 | if (cst.DEBUG) { 98 | PushInteractor.start({ 99 | port : cst.REMOTE_PORT, 100 | host :'127.0.0.1', 101 | conf : self.opts 102 | }); 103 | 104 | if (self.opts.REVERSE_INTERACT == true) { 105 | ReverseInteractor.start({ 106 | port : cst.REMOTE_REVERSE_PORT, 107 | host : '127.0.0.1', 108 | conf : self.opts 109 | }); 110 | } 111 | } 112 | else { 113 | PushInteractor.start({ 114 | port : cst.REMOTE_PORT, 115 | host : cst.REMOTE_HOST, 116 | conf : self.opts 117 | }); 118 | 119 | if (self.opts.REVERSE_INTERACT == true) { 120 | ReverseInteractor.start({ 121 | port : cst.REMOTE_REVERSE_PORT, 122 | host : cst.REMOTE_HOST, 123 | conf : self.opts 124 | }); 125 | } 126 | } 127 | } 128 | }; 129 | 130 | /** 131 | * MAIN 132 | */ 133 | if (require.main === module) { 134 | console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Launching agent'); 135 | process.title = 'PM2: Keymetrics.io Agent'; 136 | 137 | Daemon.start(); 138 | } 139 | -------------------------------------------------------------------------------- /test/bash/cli.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SRC=$(cd $(dirname "$0"); pwd) 4 | source "${SRC}/include.sh" 5 | 6 | echo -e "\033[1mRunning tests:\033[0m" 7 | 8 | cd $file_path 9 | 10 | # 11 | # Different way to stop process 12 | # 13 | $pm2 start echo.js 14 | $pm2 start echo.js -f 15 | $pm2 start echo.js -f 16 | 17 | OUT=`$pm2 prettylist | grep -o "restart_time" | wc -l` 18 | [ $OUT -eq 3 ] || fail "$1" 19 | success "$1" 20 | 21 | $pm2 stop 12412 22 | $pm2 stop 0 23 | 24 | 25 | OUT=`$pm2 prettylist | grep -o "stopped" | wc -l` 26 | [ $OUT -eq 1 ] || fail "$1" 27 | success "$1" 28 | 29 | $pm2 stop asdsdaecho.js 30 | 31 | $pm2 stop echo 32 | 33 | $pm2 list 34 | OUT=`$pm2 prettylist | grep -o "stopped" | wc -l` 35 | [ $OUT -eq 3 ] || fail "$1" 36 | success "$1" 37 | 38 | 39 | # 40 | # Describe process 41 | # 42 | $pm2 describe 0 43 | spec "should describe stopped process" 44 | 45 | $pm2 restart 1 46 | 47 | $pm2 describe 1 48 | spec "should describe online process" 49 | 50 | $pm2 describe asdsa 51 | ispec "should exit with right exit code when no process found" 52 | 53 | # 54 | # Update pm2 55 | # 56 | $pm2 updatePM2 57 | spec "should update pm2" 58 | 59 | # 60 | # Main tests 61 | # 62 | 63 | 64 | $pm2 kill 65 | spec "kill daemon" 66 | 67 | $pm2 start eyayimfake 68 | ispec "should fail if script doesnt exist" 69 | 70 | $pm2 71 | ispec "No argument" 72 | 73 | $pm2 list 74 | 75 | $pm2 start cluster-pm2.json 76 | spec "Should start well formated json with name for file prefix" 77 | 78 | $pm2 list 79 | spec "Should list processes succesfully" 80 | 81 | 82 | $pm2 start multi-echo.json 83 | spec "Should start multiple applications" 84 | 85 | $pm2 generate echo 86 | spec "Should generate echo sample json" 87 | 88 | $pm2 start echo-pm2.json -f 89 | spec "Should start echo service" 90 | 91 | $pm2 list 92 | 93 | 94 | # $pm2 logs & 95 | # spec "Should display logs" 96 | # TMPPID=$! 97 | 98 | # sleep 1 99 | 100 | # kill $! 101 | # spec "Should kill logs" 102 | 103 | # $pm2 logs echo & 104 | # spec "Should display logs" 105 | # TMPPID=$! 106 | 107 | # sleep 1 108 | 109 | # kill $! 110 | # spec "Should kill logs" 111 | 112 | 113 | $pm2 web 114 | spec "Should start web interface" 115 | 116 | sleep 0.3 117 | 118 | JSON_FILE='/tmp/web-json' 119 | 120 | $http_get -q http://localhost:9615/ -O $JSON_FILE 121 | cat $JSON_FILE | grep "HttpInterface.js" > /dev/null 122 | spec "Should get the right JSON with HttpInterface file launched" 123 | 124 | $pm2 flush 125 | spec "Should clean logs" 126 | 127 | # cat ~/.pm2/logs/echo-out.log | wc -l 128 | # spec "File Log should be cleaned" 129 | 130 | sleep 0.3 131 | $http_get -q http://localhost:9615/ -O $JSON_FILE 132 | cat $JSON_FILE | grep "restart_time\":0" > /dev/null 133 | spec "Should get the right JSON with HttpInterface file launched" 134 | 135 | # 136 | # Restart only one process 137 | # 138 | $pm2 restart 1 139 | should 'should has restarted process' 'restart_time: 1' 1 140 | 141 | # 142 | # Restart all processes 143 | # 144 | $pm2 restart all 145 | spec "Should restart all processes" 146 | 147 | sleep 0.3 148 | $http_get -q http://localhost:9615/ -O $JSON_FILE 149 | OUT=`cat $JSON_FILE | grep -o "restart_time\":1" | wc -l` 150 | 151 | [ $OUT -eq 7 ] || fail "Error while wgeting data via web interface" 152 | success "Got data from interface" 153 | 154 | 155 | $pm2 list 156 | 157 | $pm2 dump 158 | spec "Should dump current processes" 159 | 160 | $pm2 save 161 | spec "Should save (dump alias) current processes" 162 | 163 | 164 | ls ~/.pm2/dump.pm2 165 | spec "Dump file should be present" 166 | 167 | $pm2 stop all 168 | spec "Should stop all processes" 169 | 170 | sleep 0.5 171 | OUT=`$pm2 prettylist | grep -o "stopped" | wc -l` 172 | [ $OUT -eq 8 ] || fail "Process not stopped" 173 | success "Process succesfully stopped" 174 | 175 | 176 | $pm2 kill 177 | 178 | # 179 | # Issue #71 180 | # 181 | 182 | PROC_NAME='ECHONEST' 183 | # Launch a script with name option 184 | $pm2 start echo.js --name $PROC_NAME -f 185 | OUT=`$pm2 prettylist | grep -o "ECHONEST" | wc -l` 186 | [ $OUT -gt 0 ] || fail "Process not launched" 187 | success "Processes sucessfully launched with a specific name" 188 | 189 | # Restart a process by name 190 | $pm2 restart $PROC_NAME 191 | OUT=`$pm2 prettylist | grep -o "restart_time: 1" | wc -l` 192 | [ $OUT -gt 0 ] || fail "Process name not restarted" 193 | success "Processes sucessfully restarted with a specific name" 194 | 195 | 196 | 197 | 198 | 199 | $pm2 kill 200 | 201 | $pm2 resurrect 202 | spec "Should resurrect all apps" 203 | 204 | sleep 0.5 205 | OUT=`$pm2 prettylist | grep -o "restart_time" | wc -l` 206 | [ $OUT -eq 8 ] || fail "Not valid process number" 207 | success "Processes valid" 208 | 209 | 210 | 211 | $pm2 delete all 212 | spec "Should delete all processes" 213 | 214 | sleep 0.5 215 | OUT=`$pm2 prettylist | grep -o "restart_time" | wc -l` 216 | [ $OUT -eq 0 ] || fail "Process not stopped" 217 | success "Process succesfully stopped" 218 | 219 | # 220 | # Cron 221 | # 222 | $pm2 start cron.js -c "* * * asdasd" 223 | ispec "Cron should throw error when pattern invalid" 224 | 225 | $pm2 start cron.js -c "* * * * * *" 226 | spec "Should cron restart echo.js" 227 | 228 | 229 | $pm2 kill test 230 | ispec "Should not kill with extra args" 231 | 232 | $pm2 kill 233 | spec "Should kill daemon" 234 | -------------------------------------------------------------------------------- /lib/God/ForkMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @file Fork execution related functions 5 | * @author Alexandre Strzelewicz 6 | * @project PM2 7 | */ 8 | 9 | var log = require('debug')('pm2:god'); 10 | var fs = require('fs'); 11 | var cst = require('../../constants.js'); 12 | var uidNumber = require('uid-number'); 13 | var moment = require('moment'); 14 | var Common = require('../Common'); 15 | /** 16 | * Description 17 | * @method exports 18 | * @param {} God 19 | * @return 20 | */ 21 | module.exports = function ForkMode(God) { 22 | /** 23 | * For all apps - FORK MODE 24 | * fork the app 25 | * @method forkMode 26 | * @param {} pm2_env 27 | * @param {} cb 28 | * @return 29 | */ 30 | God.forkMode = function forkMode(pm2_env, cb) { 31 | var command = ''; 32 | var args = []; 33 | 34 | console.log('Entering in fork mode'); 35 | var spawn = require('child_process').spawn; 36 | 37 | var interpreter = pm2_env.exec_interpreter || 'node'; 38 | var pidFile = pm2_env.pm_pid_path; 39 | 40 | if (interpreter !== 'none') { 41 | command = interpreter; 42 | 43 | if (pm2_env.node_args && Array.isArray(pm2_env.node_args)) { 44 | args = args.concat(pm2_env.node_args); 45 | } 46 | 47 | if (process.env.PM2_NODE_OPTIONS) { 48 | args = args.concat(process.env.PM2_NODE_OPTIONS.split(' ')); 49 | } 50 | 51 | args.push(pm2_env.pm_exec_path); 52 | } 53 | else { 54 | command = pm2_env.pm_exec_path; 55 | args = [ ]; 56 | } 57 | 58 | if (pm2_env.args) { 59 | args = args.concat(eval((pm2_env.args))); 60 | } 61 | 62 | 63 | var stdout, stderr; 64 | var outFile = pm2_env.pm_out_log_path; 65 | var errFile = pm2_env.pm_err_log_path; 66 | 67 | /** 68 | * Get the uid and gid number if run_as_user or run_as_group 69 | * is present 70 | * @method getuid 71 | * @param {function} cb A callback that reveice error, uid and gid 72 | */ 73 | function getugid(cb) { 74 | var user = pm2_env.run_as_user || process.getuid(); 75 | var group = pm2_env.run_as_group || process.getgid(); 76 | 77 | uidNumber(user, group, cb); 78 | } 79 | 80 | /** 81 | * Description 82 | * @method startLogging 83 | * @param {} cb 84 | * @return 85 | */ 86 | function startLogging(cb) { 87 | stdout = fs.createWriteStream(outFile, { flags : 'a' }); 88 | 89 | stdout.on('error', function(e) { 90 | God.logAndGenerateError(e); 91 | return cb(e); 92 | }); 93 | 94 | stdout.on('open', function() { 95 | stderr = fs.createWriteStream(errFile, { flags : 'a' }); 96 | 97 | stderr.on('error', function(e) { 98 | God.logAndGenerateError(e); 99 | return cb(e); 100 | }); 101 | 102 | stderr.on('open', function() { 103 | return cb(null); 104 | }); 105 | }); 106 | } 107 | 108 | startLogging(function(err) { 109 | if (err) { 110 | God.logAndGenerateError(err); 111 | return cb(err); 112 | }; 113 | 114 | getugid(function(e, uid, gid){ 115 | if(e){ 116 | God.logAndGenerateError(e); 117 | if (cb) return cb(e); 118 | } 119 | 120 | try { 121 | var cspr = spawn(command, args, { 122 | env : pm2_env, 123 | detached : true, 124 | gid : gid, 125 | uid : uid, 126 | cwd : pm2_env.pm_cwd || process.cwd(), 127 | stdio : ['ipc', null, null] 128 | }); 129 | } catch(e) { 130 | God.logAndGenerateError(e); 131 | if (cb) return cb(e); 132 | } 133 | 134 | cspr.process = {}; 135 | cspr.process.pid = cspr.pid; 136 | cspr.pm2_env = pm2_env; 137 | cspr.pm2_env.status = cst.ONLINE_STATUS; 138 | 139 | cspr.stderr.on('data', function forkErrData(data) { 140 | 141 | var log_data = data.toString(); 142 | if (pm2_env.log_date_format) 143 | log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; 144 | 145 | stderr.write(log_data); 146 | 147 | God.bus.emit('log:err', { 148 | process : Common.serialize(cspr), 149 | data : data.toString() 150 | }); 151 | }); 152 | 153 | cspr.stdout.on('data', function forkOutData(data) { 154 | 155 | var log_data = data.toString(); 156 | if (pm2_env.log_date_format) 157 | log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; 158 | 159 | stdout.write(log_data); 160 | 161 | God.bus.emit('log:out', { 162 | process : Common.serialize(cspr), 163 | data : data.toString() 164 | }); 165 | }); 166 | 167 | cspr.on('message', function forkMessage(data) { 168 | God.bus.emit(data.type ? data.type : 'process:msg', { 169 | process : Common.serialize(cspr), 170 | data : data 171 | }); 172 | }); 173 | 174 | fs.writeFileSync(pidFile, cspr.pid); 175 | 176 | cspr.once('close', function forkClose(status) { 177 | try { 178 | stderr.close(); 179 | stdout.close(); 180 | } catch(e) { God.logAndGenerateError(e);} 181 | }); 182 | 183 | cspr._reloadLogs = startLogging; 184 | 185 | cspr.unref(); 186 | 187 | if (cb) return cb(null, cspr); 188 | return false; 189 | }); 190 | return false; 191 | }); 192 | 193 | }; 194 | }; 195 | -------------------------------------------------------------------------------- /lib/Monit.js: -------------------------------------------------------------------------------- 1 | // pm2-htop 2 | // Library who interacts with PM2 to display processes resources in htop way 3 | // by Strzelewicz Alexandre 4 | 5 | var multimeter = require('pm2-multimeter'); 6 | var os = require('os'); 7 | var p = require('path'); 8 | 9 | var CliUx = require('./CliUx'); 10 | 11 | var debug = require('debug')('pm2:monit'); 12 | 13 | // Cst for light programs 14 | const RATIO_T1 = Math.floor(os.totalmem() / 500); 15 | // Cst for medium programs 16 | const RATIO_T2 = Math.floor(os.totalmem() / 50); 17 | // Cst for heavy programs 18 | const RATIO_T3 = Math.floor(os.totalmem() / 5); 19 | // Cst for heavy programs 20 | const RATIO_T4 = Math.floor(os.totalmem()); 21 | 22 | var Monit = {}; 23 | 24 | //helper to get bars.length (num bars printed) 25 | Object.size = function(obj) { 26 | var size = 0, key; 27 | for (key in obj) { 28 | if (obj.hasOwnProperty(key)) size++; 29 | } 30 | return size; 31 | }; 32 | 33 | /** 34 | * Reset the monitor through charm, basically \033c 35 | * @param String msg optional message to show 36 | * @return Monit 37 | */ 38 | Monit.reset = function(msg) { 39 | 40 | this.multi.charm.reset(); 41 | 42 | this.multi.write('\x1B[32m⌬ PM2 \x1B[39mmonitoring :\n\n'); 43 | 44 | if(msg) { 45 | this.multi.write(msg); 46 | } 47 | 48 | this.bars = {}; 49 | 50 | return this; 51 | } 52 | 53 | /** 54 | * Synchronous Monitor init method 55 | * @method init 56 | * @return Monit 57 | */ 58 | Monit.init = function() { 59 | 60 | this.multi = multimeter(process); 61 | 62 | this.multi.on('^C', this.stop); 63 | 64 | this.reset(); 65 | 66 | return this; 67 | } 68 | 69 | /** 70 | * Stops monitor 71 | * @method stop 72 | */ 73 | Monit.stop = function() { 74 | this.multi.charm.destroy(); 75 | process.exit(0); 76 | } 77 | 78 | 79 | /** 80 | * Refresh monitor 81 | * @method refresh 82 | * @param {} processes 83 | * @return this 84 | */ 85 | Monit.refresh = function(processes) { 86 | debug('Monit refresh'); 87 | 88 | if(!processes) { 89 | processes = []; 90 | } 91 | 92 | var num = processes.length; 93 | this.num_bars = Object.size(this.bars); 94 | 95 | if(num !== this.num_bars) { 96 | debug('Monit addProcesses - actual: %s, new: %s', this.num_bars, num); 97 | return this.addProcesses(processes); 98 | } else { 99 | 100 | if(num === 0) { 101 | return; 102 | } 103 | 104 | debug('Monit refresh'); 105 | var proc; 106 | 107 | for(var i = 0; i < num; i++) { 108 | proc = processes[i]; 109 | 110 | //this is to avoid a print issue when the process is restarted for example 111 | //we might also check for the pid but restarted|restarting will be rendered bad 112 | if(this.bars[proc.pm_id] && proc.pm2_env.status !== this.bars[proc.pm_id].status) { 113 | debug('bars for %s does not exists', proc.pm_id); 114 | this.addProcesses(processes); 115 | break; 116 | } 117 | 118 | this.updateBars(proc); 119 | 120 | } 121 | } 122 | 123 | return this; 124 | } 125 | 126 | Monit.addProcess = function(proc, i) { 127 | 128 | require('colors'); 129 | 130 | if(proc.pm_id in this.bars) { 131 | return ; 132 | } 133 | 134 | if (proc.monit.error) 135 | throw new Error(JSON.stringify(proc.monit.error)); 136 | 137 | var process_name = proc.pm2_env.name || p.basename(proc.pm2_env.pm_exec_path); 138 | var status = proc.pm2_env.status == 'online' ? '●'.green.bold : '●'.red.bold; 139 | 140 | this.multi.write(' ' + status + ' ' + process_name.green.bold); 141 | this.multi.write('\n'); 142 | this.multi.write('[' + proc.pm2_env.pm_id + '] [' + proc.pm2_env.exec_mode + ']\n'); 143 | 144 | var bar_cpu = this.multi(40, (i * 2) + 3 + i, { 145 | width: 30, 146 | solid: { 147 | text: '|', 148 | foreground: 'white', 149 | background: 'blue' 150 | }, 151 | empty: { 152 | text: ' ' 153 | } 154 | }); 155 | 156 | var bar_memory = this.multi(40, (i * 2) + 4 + i, { 157 | width: 30, 158 | solid: { 159 | text: '|', 160 | foreground: 'white', 161 | background: 'red' 162 | }, 163 | empty: { 164 | text: ' ' 165 | } 166 | }); 167 | 168 | this.bars[proc.pm_id] = { 169 | memory: bar_memory, 170 | cpu: bar_cpu, 171 | status: proc.pm2_env.status 172 | }; 173 | 174 | this.updateBars(proc); 175 | 176 | this.multi.write('\n'); 177 | 178 | return this; 179 | } 180 | 181 | Monit.addProcesses = function(processes) { 182 | 183 | if(!processes) { 184 | processes = []; 185 | } 186 | 187 | this.reset(); 188 | 189 | var num = processes.length; 190 | 191 | if(num > 0) { 192 | for(var i = 0; i < num; i++) { 193 | this.addProcess(processes[i], i); 194 | } 195 | } else { 196 | this.reset('No processes to monit'); 197 | } 198 | 199 | } 200 | 201 | // Draw memory bars 202 | /** 203 | * Description 204 | * @method drawRatio 205 | * @param {} bar_memory 206 | * @param {} memory 207 | * @return 208 | */ 209 | Monit.drawRatio = function(bar_memory, memory) { 210 | var scale = 0; 211 | 212 | if (memory < RATIO_T1) scale = RATIO_T1; 213 | else if (memory < RATIO_T2) scale = RATIO_T2; 214 | else if (memory < RATIO_T3) scale = RATIO_T3; 215 | else scale = RATIO_T4; 216 | 217 | bar_memory.ratio(memory, 218 | scale, 219 | CliUx.bytesToSize(memory, 3)); 220 | }; 221 | 222 | /** 223 | * Updates bars informations 224 | * @param {} proc proc object 225 | * @return this 226 | */ 227 | Monit.updateBars = function(proc) { 228 | require('colors'); 229 | 230 | if (proc.pm2_env.status !== 'online' || proc.pm2_env.status !== this.bars[proc.pm_id].status) { 231 | this.bars[proc.pm_id].cpu.percent(0, proc.pm2_env.status.red); 232 | this.drawRatio(this.bars[proc.pm_id].memory, 0, proc.pm2_env.status.red); 233 | } else if (!proc.monit) { 234 | this.bars[proc.pm_id].cpu.percent(0, 'No data'.red); 235 | this.drawRatio(this.bars[proc.pm_id].memory, 0, 'No data'.red); 236 | } else { 237 | this.bars[proc.pm_id].cpu.percent(proc.monit.cpu); 238 | this.drawRatio(this.bars[proc.pm_id].memory, proc.monit.memory); 239 | } 240 | 241 | return this; 242 | } 243 | 244 | module.exports = Monit; 245 | -------------------------------------------------------------------------------- /lib/Interactor/PushInteractor.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('axon'); 3 | var os = require('os'); 4 | var debug = require('debug')('interface:push-interactor'); 5 | var util = require('util'); 6 | var punt = require('punt'); 7 | 8 | var pkg = require('../../package.json'); 9 | var cst = require('../../constants.js'); 10 | var Filter = require('./Filter.js'); 11 | var Cipher = require('./Cipher.js'); 12 | 13 | var PushInteractor = module.exports = { 14 | start : function(p) { 15 | if (!p.port || !p.host) throw new Error('port or host not declared in PullInteractor'); 16 | if (!p.conf || !p.conf.ipm2) throw new Error('ipm2 is not initialized'); 17 | 18 | console.log(p.host + ':' + p.port); 19 | 20 | this.udpSocket = punt.connect(p.host + ':' + p.port); 21 | 22 | this.udpSocket.sock.on('error', function(e) { 23 | console.log(e); 24 | }); 25 | 26 | this.conf = p.conf; 27 | this.ipm2 = p.conf.ipm2; 28 | this.pm2_connected = false; 29 | this.send_buffer = []; 30 | this.http_transactions = []; 31 | 32 | /** 33 | * Handle PM2 connection state changes 34 | */ 35 | this.ipm2.on('ready', function() { 36 | console.log('[PUSH] Connected to PM2'); 37 | PushInteractor.pm2_connected = true; 38 | PushInteractor.startWorker(); 39 | }); 40 | 41 | this.ipm2.on('reconnecting', function() { 42 | console.log('[PUSH] Reconnecting to PM2'); 43 | if (PushInteractor.timer_worker) 44 | clearInterval(PushInteractor.timer_worker); 45 | PushInteractor.pm2_connected = false; 46 | }); 47 | 48 | PushInteractor.pm2_connected = true; 49 | 50 | /** 51 | * Connect to AXM 52 | */ 53 | console.log('[PUSH] Broadcasting UDP data %s:%s', p.host, p.port); 54 | 55 | /** 56 | * Start the chmilblik 57 | */ 58 | this.processEvents(); 59 | }, 60 | /** 61 | * Send bufferized data at regular interval 62 | */ 63 | startWorker : function() { 64 | this.timer_worker = setInterval(function() { 65 | PushInteractor.sendData(); 66 | }, cst.SEND_INTERVAL); 67 | }, 68 | processEvents : function() { 69 | this.ipm2.bus.on('*', function(event, packet) { 70 | //if (PushInteractor.pm2_connected == false) return false; 71 | 72 | if (packet.process && packet.process.pm2_env) { 73 | /** 74 | * Process specific messages 75 | */ 76 | if (event == 'axm:action' || event.match(/^log:/)) return false; 77 | 78 | packet.process = Filter.pruneProcessObj(packet.process, PushInteractor.conf.MACHINE_NAME); 79 | PushInteractor.bufferData(event, packet); 80 | } 81 | else { 82 | /** 83 | * PM2 specific messages 84 | */ 85 | if (packet.pm2_env) delete packet.pm2_env.env; 86 | console.log('PM2 internals', packet); 87 | } 88 | 89 | return false; 90 | }); 91 | }, 92 | bufferizeServerStatus : function(cb) { 93 | this.ipm2.rpc.getMonitorData({}, function(err, processes) { 94 | if (!processes) return console.error('Cant access to getMonitorData RPC PM2 method'); 95 | 96 | var ret; 97 | 98 | if ((ret = Filter.monitoring(processes, PushInteractor.conf))) { 99 | PushInteractor.bufferData('monitoring', ret); 100 | } 101 | 102 | if ((ret = Filter.status(processes, PushInteractor.conf))) { 103 | PushInteractor.bufferData('status', ret); 104 | } 105 | 106 | if (PushInteractor.http_transactions && PushInteractor.http_transactions.length > 0) { 107 | PushInteractor.send_buffer.push({ 108 | event : 'http:transaction', 109 | transactions : PushInteractor.http_transactions, 110 | server_name : PushInteractor.conf.MACHINE_NAME 111 | }); 112 | PushInteractor.http_transactions = []; 113 | } 114 | 115 | if (cb) return cb(); 116 | return false; 117 | }); 118 | }, 119 | /** 120 | * Description 121 | * @method send_data 122 | * @return 123 | */ 124 | sendData : function() { 125 | this.bufferizeServerStatus(function() { 126 | var data = {}; 127 | 128 | if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') { 129 | data = { 130 | public_key : PushInteractor.conf.PUBLIC_KEY, 131 | sent_at : new Date(), 132 | data : { 133 | buffer : PushInteractor.send_buffer, 134 | server_name : PushInteractor.conf.MACHINE_NAME 135 | } 136 | }; 137 | } 138 | else { 139 | 140 | /** 141 | * Cipher data with AES256 142 | */ 143 | 144 | var cipheredData = Cipher.cipherMessage(JSON.stringify({ 145 | buffer : PushInteractor.send_buffer, 146 | server_name : PushInteractor.conf.MACHINE_NAME 147 | }), PushInteractor.conf.SECRET_KEY); 148 | 149 | data = { 150 | public_key : PushInteractor.conf.PUBLIC_KEY, 151 | sent_at : Date.now(), 152 | data : cipheredData 153 | }; 154 | } 155 | 156 | PushInteractor.udpSocket.send(JSON.stringify(data)); 157 | 158 | debug('Buffer with length %d sent', PushInteractor.send_buffer.length); 159 | data = null; 160 | 161 | PushInteractor.send_buffer = []; 162 | }); 163 | }, 164 | bufferData : function(event, packet) { 165 | if (packet.process && !packet.server) { 166 | 167 | if (event == 'http:transaction') { 168 | packet.data.data.process_id = Filter.getProcessID(PushInteractor.conf.MACHINE_NAME, packet.process.name, packet.process.pm_id); 169 | packet.data.data.process_name = packet.process.name; 170 | return PushInteractor.http_transactions.push(packet.data.data); 171 | } 172 | 173 | PushInteractor.send_buffer.push({ 174 | at : Date.now(), 175 | event : event, 176 | data : packet.data || null, 177 | process : packet.process, 178 | process_id : Filter.getProcessID(PushInteractor.conf.MACHINE_NAME, packet.process.name, packet.process.pm_id), 179 | process_name : packet.process.name 180 | }); 181 | } 182 | else { 183 | PushInteractor.send_buffer.push({ 184 | at : Date.now(), 185 | event : event, 186 | data : packet, 187 | server_name : PushInteractor.conf.MACHINE_NAME 188 | }); 189 | } 190 | 191 | debug('Event %s bufferized', event); 192 | return false; 193 | } 194 | }; 195 | -------------------------------------------------------------------------------- /lib/ProcessContainer.js: -------------------------------------------------------------------------------- 1 | // ProcessContainer.js 2 | // Child wrapper. Redirect output to files, assign pid & co. 3 | // by Strzelewicz Alexandre 4 | 5 | // Rename process 6 | if (process.env.name != null) 7 | process.title = 'pm2: ' + process.env.name; 8 | 9 | var fs = require('fs'); 10 | var p = require('path'); 11 | var cst = require('../constants'); 12 | 13 | /** 14 | * Main entrance to wrap the desired code 15 | */ 16 | 17 | (function ProcessContainer() { 18 | var fs = require('fs'); 19 | var worker = require('cluster').worker; 20 | 21 | var outFile = process.env.pm_out_log_path; 22 | var errFile = process.env.pm_err_log_path; 23 | var pmId = process.env.pm_id; 24 | var pidFile = process.env.pm_pid_path; 25 | var script = process.env.pm_exec_path; 26 | var cronRestart = process.env.cron_restart; 27 | 28 | if (cst.MODIFY_REQUIRE) 29 | require.main.filename = process.env.pm_exec_path; 30 | 31 | fs.writeFileSync(pidFile, process.pid); 32 | 33 | // Add args to process if args specified on start 34 | if (process.env.args != null) 35 | process.argv = process.argv.concat(eval(process.env.args)); 36 | 37 | 38 | exec(script, outFile, errFile); 39 | 40 | if (cronRestart) 41 | cronize(cronRestart); 42 | })(); 43 | 44 | // 45 | // Cron pattern like to force app to restart 46 | // 47 | /** 48 | * Description 49 | * @method cronize 50 | * @param {} cron_pattern 51 | * @return 52 | */ 53 | function cronize(cron_pattern) { 54 | var cronJob = require('cron').CronJob; 55 | var job = new cronJob({ 56 | cronTime: cron_pattern, 57 | /** 58 | * Description 59 | * @method onTick 60 | * @return 61 | */ 62 | onTick: function() { 63 | process.exit(0); 64 | }, 65 | start: false 66 | }); 67 | job.start(); 68 | } 69 | 70 | /** 71 | * Description 72 | * @method exec 73 | * @param {} script 74 | * @param {} outFile 75 | * @param {} errFile 76 | * @return 77 | */ 78 | function exec(script, outFile, errFile) { 79 | var stderr, stdout; 80 | 81 | if (p.extname(script) == '.coffee') { 82 | require('coffee-script/register'); 83 | } 84 | 85 | process.on('message', function (msg) { 86 | if (msg.type === 'log:reload') { 87 | stdout.end(); 88 | stderr.end(); 89 | startLogging(function () { 90 | console.log('Reloading log...'); 91 | }); 92 | } 93 | }); 94 | 95 | var moment = null; 96 | 97 | if (process.env.log_date_format) 98 | moment = require('moment'); 99 | 100 | 101 | /** 102 | * Description 103 | * @method startLogging 104 | * @param {} callback 105 | * @return 106 | */ 107 | function startLogging(callback) { 108 | stdout = fs.createWriteStream(outFile, { flags : 'a' }); 109 | 110 | stdout.on('open', function() { 111 | stderr = fs.createWriteStream(errFile, { flags : 'a' }); 112 | stderr.on('open', function() { 113 | 114 | process.stderr.write = (function(write) { 115 | return function(string, encoding, fd) { 116 | var log_data = string.toString(); 117 | if (process.env.log_date_format && moment) 118 | log_data = moment().format(process.env.log_date_format) + ': ' + log_data; 119 | stderr.write(log_data); 120 | process.send({ 121 | type : 'log:err', 122 | data : string 123 | }); 124 | }; 125 | } 126 | )(process.stderr.write); 127 | 128 | process.stdout.write = (function(write) { 129 | return function(string, encoding, fd) { 130 | var log_data = string.toString(); 131 | if (process.env.log_date_format && moment) 132 | log_data = moment().format(process.env.log_date_format) + ': ' + log_data; 133 | stdout.write(log_data); 134 | process.send({ 135 | type : 'log:out', 136 | data : string 137 | }); 138 | }; 139 | })(process.stdout.write); 140 | callback(); 141 | }); 142 | }); 143 | } 144 | 145 | startLogging(function () { 146 | 147 | process.on('uncaughtException', function uncaughtListener(err) { 148 | try { 149 | stderr.write(err.stack); 150 | } catch(e) { 151 | try { 152 | stderr.write(err.toString()); 153 | } catch(e) {} 154 | } 155 | 156 | // Notify master that an uncaughtException has been catched 157 | try { 158 | 159 | var errObj = {}; 160 | Object.getOwnPropertyNames(err).forEach(function(key) { 161 | errObj[key] = err[key]; 162 | }); 163 | 164 | process.send({ 165 | type : 'process:exception', 166 | stack : err.stack, 167 | err : errObj, 168 | message : err.message || '' 169 | }); 170 | } catch(e) { 171 | try { 172 | stderr.write('Channel is already closed can\'t broadcast error', err); 173 | } catch(e) {} 174 | } 175 | 176 | if (!process.listeners('uncaughtException').filter(function (listener) { 177 | return listener !== uncaughtListener; 178 | }).length) { 179 | setTimeout(function() { 180 | process.exit(cst.CODE_UNCAUGHTEXCEPTION); 181 | }, 100); 182 | } 183 | 184 | }); 185 | 186 | // if we've been told to run as a different user or group (e.g. because they have fewer 187 | // privileges), switch to that user before importing any third party application code. 188 | if (process.env.run_as_group) { 189 | process.setgid(process.env.run_as_group); 190 | } 191 | 192 | if (process.env.run_as_user) { 193 | process.setuid(process.env.run_as_user); 194 | } 195 | 196 | // Change dir to fix process.cwd 197 | process.chdir(process.env.pm_cwd || process.env.PWD || p.dirname(script)); 198 | 199 | 200 | // Get the script & exec 201 | require(script); 202 | 203 | }); 204 | 205 | } 206 | -------------------------------------------------------------------------------- /lib/God/Methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @file Utilities for PM2 5 | * @author Alexandre Strzelewicz 6 | * @project PM2 7 | */ 8 | var p = require('path'); 9 | var Common = require('../Common'); 10 | /** 11 | * Description 12 | * @method exports 13 | * @param {} God 14 | * @return 15 | */ 16 | module.exports = function(God) { 17 | 18 | /** 19 | * Description 20 | * @method logAndGenerateError 21 | * @param {} err 22 | * @return NewExpression 23 | */ 24 | God.logAndGenerateError = function(err) { 25 | // Is an Error object 26 | if (err instanceof Error) { 27 | console.trace(err); 28 | return err; 29 | } 30 | // Is a JSON or simple string 31 | console.error(err); 32 | return new Error(err); 33 | }; 34 | 35 | /** 36 | * Utility functions 37 | * @method getProcesses 38 | * @return MemberExpression 39 | */ 40 | God.getProcesses = function() { 41 | return God.clusters_db; 42 | }; 43 | 44 | /** 45 | * Description 46 | * @method getFormatedProcesses 47 | * @return arr 48 | */ 49 | God.getFormatedProcesses = function getFormatedProcesses() { 50 | var db = Common.serialize(God.clusters_db); 51 | var arr = []; 52 | 53 | for (var key in db) { 54 | if (db[key]) { 55 | arr.push({ 56 | pid : db[key].process.pid, 57 | name : db[key].pm2_env.name, 58 | pm2_env : db[key].pm2_env, 59 | pm_id : db[key].pm2_env.pm_id 60 | }); 61 | } 62 | } 63 | db = null; 64 | return arr; 65 | }; 66 | 67 | /** 68 | * Description 69 | * @method findProcessById 70 | * @param {} id 71 | * @return ConditionalExpression 72 | */ 73 | God.findProcessById = function findProcessById(id) { 74 | return God.clusters_db[id] ? God.clusters_db[id] : null; 75 | }; 76 | 77 | /** 78 | * Description 79 | * @method findByName 80 | * @param {} name 81 | * @return arr 82 | */ 83 | God.findByName = function(name) { 84 | var db = God.clusters_db; 85 | var arr = []; 86 | 87 | for (var key in db) { 88 | if (God.clusters_db[key].pm2_env.name == name || God.clusters_db[key].pm2_env.pm_exec_path == p.resolve(name)) { 89 | arr.push(db[key]); 90 | } 91 | } 92 | return arr; 93 | }; 94 | 95 | /** 96 | * Description 97 | * @method findByScript 98 | * @param {} script 99 | * @param {} cb 100 | * @return 101 | */ 102 | God.findByScript = function(script, cb) { 103 | var db = Common.serialize(God.clusters_db); 104 | var arr = []; 105 | 106 | for (var key in db) { 107 | if (p.basename(db[key].pm2_env.pm_exec_path) == script) { 108 | arr.push(db[key].pm2_env); 109 | } 110 | } 111 | cb(null, arr.length == 0 ? null : arr); 112 | }; 113 | 114 | /** 115 | * Description 116 | * @method findByPort 117 | * @param {} port 118 | * @param {} cb 119 | * @return 120 | */ 121 | God.findByPort = function(port, cb) { 122 | var db = God.clusters_db; 123 | var arr = []; 124 | 125 | for (var key in db) { 126 | if (db[key].pm2_env.port && db[key].pm2_env.port == port) { 127 | arr.push(db[key].pm2_env); 128 | } 129 | } 130 | cb(null, arr.length == 0 ? null : arr); 131 | }; 132 | 133 | /** 134 | * Description 135 | * @method findByFullPath 136 | * @param {} path 137 | * @param {} cb 138 | * @return 139 | */ 140 | God.findByFullPath = function(path, cb) { 141 | var db = God.clusters_db; 142 | var procs = []; 143 | 144 | for (var key in db) { 145 | if (db[key].pm2_env.pm_exec_path == path) { 146 | procs.push(db[key]); 147 | } 148 | } 149 | cb(null, procs.length == 0 ? null : procs); 150 | }; 151 | 152 | /** 153 | * Check if a process is alive in system processes 154 | * Return TRUE if process online 155 | * @method checkProcess 156 | * @param {} pid 157 | * @return 158 | */ 159 | God.checkProcess = function(pid) { 160 | if (!pid) return false; 161 | 162 | try { 163 | // Sending 0 signal do not kill the process 164 | process.kill(pid, 0); 165 | return true; 166 | } 167 | catch (err) { 168 | return false; 169 | } 170 | }; 171 | 172 | /** 173 | * Description 174 | * @method processIsDead 175 | * @param {} pid 176 | * @param {} cb 177 | * @return Literal 178 | */ 179 | God.processIsDead = function(pid, cb) { 180 | if (!pid) return cb({type : 'param:missing', msg : 'no pid passed'}); 181 | 182 | var timeout; 183 | 184 | var timer = setInterval(function() { 185 | if (God.checkProcess(pid) === false) { 186 | console.log('process with pid %d successfully killed', pid); 187 | clearTimeout(timeout); 188 | clearInterval(timer); 189 | return cb(null, true); 190 | } 191 | console.log('process with pid %d still not killed, retrying...', pid); 192 | return false; 193 | }, 50); 194 | 195 | timeout = setTimeout(function() { 196 | clearInterval(timer); 197 | return cb({type : 'timeout', msg : 'timeout'}); 198 | }, 800); 199 | return false; 200 | }; 201 | 202 | /** 203 | * Description 204 | * @method killProcess 205 | * @param {} pid 206 | * @param {} cb 207 | * @return CallExpression 208 | */ 209 | God.killProcess = function(pid, cb) { 210 | if (!pid) return cb({msg : 'no pid passed or null'}); 211 | 212 | try { 213 | process.kill(pid); 214 | } catch(e) { 215 | console.error('%s pid can not be killed', pid, e); 216 | return cb({type : 'kill', msg : pid + ' can not be killed'}); 217 | } 218 | return God.processIsDead(pid, cb); 219 | }; 220 | 221 | /** 222 | * Description 223 | * @method getNewId 224 | * @return UpdateExpression 225 | */ 226 | God.getNewId = function() { 227 | return God.next_id++; 228 | }; 229 | 230 | /** 231 | * When a process is restarted or reloaded reset fields 232 | * to monitor unstable starts 233 | * @method resetState 234 | * @param {} pm2_env 235 | * @return 236 | */ 237 | God.resetState = function(pm2_env) { 238 | pm2_env.created_at = Date.now(); 239 | pm2_env.unstable_restarts = 0; 240 | }; 241 | 242 | /** 243 | * Description 244 | * @method deepReset 245 | * @param {} pm2_env 246 | * @return 247 | */ 248 | God.deepReset = function(pm2_env) { 249 | pm2_env.created_at = Date.now(); 250 | pm2_env.unstable_restarts = 0; 251 | }; 252 | 253 | }; 254 | -------------------------------------------------------------------------------- /test/programmatic/deprecated/interactor.mocha.js: -------------------------------------------------------------------------------- 1 | 2 | var should = require('should'); 3 | var ipm2 = require('pm2-interface'); 4 | var util = require('util'); 5 | var axon = require('axon'); 6 | var sock = axon.socket('sub'); 7 | var cst = require('../../constants.js'); 8 | var Plan = require('../helpers/plan.js'); 9 | 10 | 11 | var nssocket = require('nssocket'); 12 | 13 | var Ipm2 = require('pm2-interface'); 14 | 15 | var APPS = require('../helpers/apps.js'); 16 | 17 | /** 18 | * Description 19 | * @method forkPM2 20 | * @return pm2 21 | */ 22 | function forkPM2() { 23 | var pm2 = require('child_process').fork('lib/Satan.js', [], { 24 | detached : true 25 | }); 26 | pm2.unref(); 27 | return pm2; 28 | } 29 | 30 | /** 31 | * Description 32 | * @method forkInteractor 33 | * @return CallExpression 34 | */ 35 | function forkInteractor() { 36 | return require('child_process').fork('lib/Interactor.js', [], { 37 | env : util._extend({ 38 | PM2_MACHINE_NAME : 'test', 39 | PM2_SECRET_KEY : 'toto', 40 | PM2_PUBLIC_KEY : 'tg', 41 | PM2_DEBUG : true, 42 | NODE_ENV : 'test' // Permit to disable encryption 43 | }, process.env) 44 | }); 45 | } 46 | 47 | /** 48 | * Description 49 | * @method bufferContain 50 | * @param {} buffer 51 | * @param {} event 52 | * @return contain 53 | */ 54 | function bufferContain(buffer, event) { 55 | var contain = false; 56 | buffer.data.buffer.forEach(function(dt) { 57 | if (dt.event == event) 58 | contain = dt; 59 | }); 60 | return contain; 61 | } 62 | 63 | describe.skip('Interactor', function() { 64 | var pm2; 65 | var interactor; 66 | var ipm2; 67 | var socket; 68 | var server; 69 | 70 | after(function() { 71 | server.close(); 72 | }); 73 | 74 | it('should fork PM2', function(done) { 75 | try { 76 | pm2 = APPS.forkPM2(); 77 | } catch(e) { 78 | done(); 79 | } 80 | done(); 81 | }); 82 | 83 | it('should start mock NSSOCKER interface', function(done) { 84 | server = nssocket.createServer(function (_socket) { 85 | console.log('new connection'); 86 | socket = _socket; 87 | }); 88 | server.listen(cst.REMOTE_REVERSE_PORT); 89 | done(); 90 | }); 91 | 92 | describe('External interaction', function() { 93 | 94 | beforeEach(function(done) { 95 | ipm2 = Ipm2(); 96 | 97 | ipm2.once('ready', function() { 98 | done(); 99 | }); 100 | }); 101 | 102 | afterEach(function() { 103 | ipm2.disconnect(); 104 | }); 105 | 106 | 107 | it('should fork Interactor', function(done) { 108 | sock.bind(3900); 109 | interactor = forkInteractor(); 110 | 111 | done(); 112 | }); 113 | 114 | it('should receive an intervaled message (sent every sec)', function(done) { 115 | sock.once('message', function(raw_data) { 116 | var data = JSON.parse(raw_data); 117 | 118 | data.should.have.properties(['public_key', 'sent_at', 'data']); 119 | data.data.buffer.length.should.eql(2); // Include monitoring and server data 120 | done(); 121 | }); 122 | }); 123 | 124 | var cur_id = 0; 125 | 126 | it('should on application start, buffer contain a process:online event', function(done) { 127 | sock.once('message', function(raw_data) { 128 | var data = JSON.parse(raw_data); 129 | 130 | if (bufferContain(data, 'process:online')) { 131 | done(); 132 | } 133 | }); 134 | 135 | APPS.launchApp(ipm2, 'echo.js', 'echo', function(err, proc) { 136 | should(err).be.null; 137 | proc.length.should.eql(1); 138 | proc[0].pm2_env.status.should.eql('online'); 139 | }); 140 | 141 | }); 142 | 143 | it('should on launch custom action', function(done) { 144 | APPS.launchApp(ipm2, 'events/custom_action.js', 'custom_action', function(err, proc) { 145 | cur_id = proc[1].pm2_env.pm_id; 146 | should(err).be.null; 147 | 148 | setTimeout(function() { 149 | ipm2.rpc.getMonitorData({}, function(err, procs) { 150 | should(err).be.null; 151 | console.log(procs); 152 | procs.length.should.eql(2); 153 | procs[1].pm2_env.restart_time.should.eql(0); 154 | done(); 155 | }); 156 | }, 1000); 157 | }); 158 | }); 159 | 160 | 161 | it('should get information about instance', function(done) { 162 | 163 | socket.send('ask'); 164 | 165 | socket.data('ask:rep', function (data) { 166 | data.success.should.eql.true; 167 | data.machine_name.should.eql('test'); 168 | data.public_key.should.eql('tg'); 169 | done(); 170 | }); 171 | 172 | 173 | }); 174 | 175 | it('should trigger action like remote AXM', function(done) { 176 | var plan = new Plan(2, done); 177 | 178 | /** 179 | * Description 180 | * @method rcv 181 | * @param {} raw_data 182 | * @return 183 | */ 184 | function rcv(raw_data) { 185 | var data = JSON.parse(raw_data); 186 | var ret; 187 | //console.log(data.data.buffer); 188 | if ((ret = bufferContain(data, 'axm:reply'))) { 189 | ret.should.have.properties([ 190 | 'event', 'process_id', 'process_name', 'data', 'at' 191 | ]); 192 | ret.data.data.success.should.be.true; 193 | sock.removeListener('message', rcv); 194 | plan.ok(true); 195 | } 196 | } 197 | // 2 - He should then receive an axm:reply on completion 198 | sock.on('message', rcv); 199 | 200 | socket.send('trigger:action', { 201 | process_id : cur_id, 202 | action_name : 'refresh:db', 203 | type : 'remote_action' 204 | }); 205 | 206 | socket.data('trigger:action:success', function() { 207 | console.log('Action has been sent'); 208 | plan.ok(true); 209 | }); 210 | 211 | socket.data('trigger:action:failure', function(e) { 212 | console.log(e); 213 | throw new Error(e); 214 | }); 215 | }); 216 | 217 | 218 | // it('should remove all socket data and stuff if server disconnect', function(done) { 219 | 220 | // server.close(); 221 | // server = nssocket.createServer(function (_socket) { 222 | // console.log('new connection'); 223 | // socket = _socket; 224 | // done(); 225 | // }); 226 | 227 | // server.listen(cst.REMOTE_REVERSE_PORT); 228 | 229 | // }); 230 | 231 | 232 | it('should kill alive processes', function(done) { 233 | process.kill(pm2.pid); 234 | process.kill(interactor.pid); 235 | done(); 236 | }); 237 | 238 | }); 239 | 240 | 241 | }); 242 | -------------------------------------------------------------------------------- /lib/Common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var util = require('util'); 8 | var cronJob = require('cron').CronJob; 9 | 10 | var cst = require('../constants.js'); 11 | var extItps = require('./interpreter.json'); 12 | var p = path; 13 | 14 | var Stringify = require('json-stringify-safe'); 15 | 16 | var Satan = require('./Satan.js'); 17 | var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js'); 18 | /** 19 | * Common methods (used by CLI and God) 20 | */ 21 | 22 | var Common = module.exports; 23 | 24 | /** 25 | * Resolve app paths and replace missing values with defaults. 26 | * @method resolveAppPaths 27 | * @param app {Object} 28 | * @param {} cwd 29 | * @param {} outputter 30 | * @return app 31 | */ 32 | Common.resolveAppPaths = function(app, cwd, outputter) { 33 | 34 | var err = Common.validateApp(app, outputter); 35 | 36 | if (err) 37 | return err; 38 | 39 | if (cwd && cwd[0] == '/') 40 | cwd = cwd; 41 | else if (cwd) 42 | cwd = p.resolve(process.cwd(), cwd); 43 | else 44 | cwd = process.cwd(); 45 | 46 | // Set current env by first adding the process environment and then extending/replacing it 47 | // with env specified on command-line or JSON file. 48 | var env = app.env || { }; 49 | app.env = { }; 50 | util._extend(app.env, process.env); 51 | util._extend(app.env, env); 52 | 53 | app.env.pm_cwd = cwd; 54 | app.pm_cwd = cwd; 55 | 56 | if (!app.exec_interpreter) { 57 | if (extItps[path.extname(app.script)]) { 58 | app.exec_interpreter = extItps[path.extname(app.script)]; 59 | if (extItps[path.extname(app.script)] != 'node') 60 | app.exec_mode = 'fork_mode'; 61 | } else { 62 | app.exec_interpreter = 'node'; 63 | } 64 | } 65 | 66 | if (!('exec_mode' in app)) app['exec_mode'] = 'cluster_mode'; 67 | 68 | 69 | app["pm_exec_path"] = path.resolve(cwd, app.script); 70 | delete app.script; 71 | 72 | if (app.node_args && !Array.isArray(app.node_args)) 73 | app.node_args = app.node_args.split(' '); 74 | 75 | if (!app.node_args) 76 | app.node_args = []; 77 | 78 | if (app.max_memory_restart && 79 | !isNaN(parseInt(app.max_memory_restart)) && 80 | Array.isArray(app.node_args)) { 81 | app.node_args.push('--max-old-space-size=' + app.max_memory_restart); 82 | } 83 | 84 | if (!app.name) { 85 | app.name = p.basename(app["pm_exec_path"]); 86 | } 87 | 88 | var formated_app_name = app.name.replace(/[^a-zA-Z0-9\\.\\-]/g, '-'); 89 | 90 | if (fs.existsSync(app.pm_exec_path) == false) { 91 | return new Error('script not found : ' + app.pm_exec_path); 92 | } 93 | 94 | if (app.out_file) 95 | app["pm_out_log_path"] = path.resolve(cwd, app.out_file); 96 | else { 97 | app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '-out.log'].join('')); 98 | app.out_file = app["pm_out_log_path"]; 99 | } 100 | delete app.out_file; 101 | 102 | if (app.error_file) 103 | app["pm_err_log_path"] = path.resolve(cwd, app.error_file); 104 | else { 105 | app["pm_err_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '-err.log'].join('')); 106 | app.error_file = app["pm_err_log_path"]; 107 | } 108 | delete app.error_file; 109 | 110 | if (app.pid_file) 111 | app["pm_pid_path"] = path.resolve(cwd, app.pid_file); 112 | else { 113 | app["pm_pid_path"] = path.resolve(cst.DEFAULT_PID_PATH, [formated_app_name, '.pid'].join('')); 114 | app.pid_file = app["pm_pid_path"]; 115 | } 116 | delete app.pid_file; 117 | 118 | //set port env variable 119 | if (app.port) { 120 | app.env.PORT = app.port; 121 | } 122 | 123 | return app; 124 | }; 125 | 126 | Common.deepCopy = Common.serialize = function serialize(data) { 127 | return JSON.parse(Stringify(data)); 128 | }; 129 | 130 | /** 131 | * Description 132 | * @method validateApp 133 | * @param {} appConf 134 | * @param {} outputter 135 | * @return Literal 136 | */ 137 | Common.validateApp = function(appConf, outputter) { 138 | var instances = appConf['instances']; 139 | var cron_pattern = appConf['cron_restart']; 140 | 141 | if (instances && isNaN(parseInt(instances)) && instances != 'max') { 142 | return new Error('Instance option must be an integer or the "max" string'); 143 | } 144 | 145 | if (cron_pattern) { 146 | try { 147 | if (outputter) 148 | outputter(cron_pattern); 149 | var cron_test = new cronJob(cron_pattern, function() { 150 | if (outputter) 151 | outputter(cst.PREFIX_MSG + 'cron pattern for auto restart detected and valid'); 152 | cron_test = undefined; 153 | }); 154 | } catch(ex) { 155 | return new Error('Cron pattern is not valid !'); 156 | } 157 | } 158 | 159 | return null; 160 | }; 161 | 162 | /** 163 | * Description 164 | * @method exitCli 165 | * @param {} code 166 | * @return CallExpression 167 | */ 168 | Common.exitCli = function(code) { 169 | InteractorDaemonizer.disconnectRPC(function() { 170 | }); 171 | Satan.disconnectRPC(function() { 172 | }); 173 | setTimeout(function() { 174 | process.exit(code || 0); 175 | }, 100); 176 | }; 177 | 178 | /** 179 | * Description 180 | * @method printError 181 | * @param {} msg 182 | * @return CallExpression 183 | */ 184 | Common.printError = function(msg) { 185 | if (process.env.PM2_SILENT) return false; 186 | if (msg instanceof Error) 187 | return console.error(msg.message); 188 | return console.error.apply(console, arguments); 189 | }; 190 | 191 | /** 192 | * Description 193 | * @method printOut 194 | * @return 195 | */ 196 | Common.printOut = function() { 197 | if (process.env.PM2_SILENT) return false; 198 | return console.log.apply(console, arguments); 199 | }; 200 | 201 | 202 | Common.getAllProcessId = function(cb) { 203 | var found_proc = []; 204 | 205 | Satan.executeRemote('getMonitorData', {}, function(err, list) { 206 | if (err) { 207 | Common.printError('Error retrieving process list: ' + err); 208 | return cb(err); 209 | } 210 | 211 | list.forEach(function(proc) { 212 | found_proc.push(proc.pm_id); 213 | }); 214 | 215 | return cb(null, found_proc); 216 | }); 217 | }; 218 | 219 | Common.getProcessIdByName = function(name, cb) { 220 | var found_proc = []; 221 | 222 | Satan.executeRemote('getMonitorData', {}, function(err, list) { 223 | if (err) { 224 | Common.printError('Error retrieving process list: ' + err); 225 | return cb(err); 226 | } 227 | 228 | list.forEach(function(proc) { 229 | if (proc.pm2_env.name == name || 230 | proc.pm2_env.pm_exec_path == p.resolve(name)) { 231 | found_proc.push(proc.pm_id); 232 | } 233 | }); 234 | 235 | return cb(null, found_proc); 236 | }); 237 | }; 238 | -------------------------------------------------------------------------------- /lib/CliUx.js: -------------------------------------------------------------------------------- 1 | var Table = require('cli-table'); 2 | var p = require('path'); 3 | var UX = module.exports = {}; 4 | 5 | var chalk = require('chalk'); 6 | 7 | /** 8 | * Description 9 | * @method miniDisplay 10 | * @param {} list 11 | * @return 12 | */ 13 | UX.miniDisplay = function(list) { 14 | list.forEach(function(l) { 15 | 16 | var mode = l.pm2_env.exec_mode.split('_mode')[0]; 17 | var status = l.pm2_env.status; 18 | var port = l.pm2_env.port; 19 | var key = l.pm2_env.name || p.basename(l.pm2_env.pm_exec_path.script); 20 | 21 | console.log('+--- %s', key); 22 | console.log('pid : %s', l.pid); 23 | console.log('pm2 id : %s', l.pm2_env.pm_id); 24 | console.log('status : %s', status); 25 | console.log('mode : %s', mode); 26 | console.log('port : %s', port); 27 | console.log('restarted : %d', l.pm2_env.restart_time ? l.pm2_env.restart_time : 0); 28 | console.log('uptime : %s', (l.pm2_env.pm_uptime && status == 'online') ? timeSince(l.pm2_env.pm_uptime) : 0); 29 | console.log('memory usage : %s', l.monit ? UX.bytesToSize(l.monit.memory, 3) : ''); 30 | console.log('out log : %s', l.pm2_env.pm_out_log_path); 31 | console.log('error log : %s', l.pm2_env.pm_err_log_path); 32 | console.log('watching : %s', l.pm2_env.watch ? 'yes' : 'no'); 33 | console.log('PID file : %s\n', [l.pm2_env.pm_pid_path, l.pm_id, '.pid'].join('')); 34 | }); 35 | 36 | }; 37 | 38 | /** 39 | * Description 40 | * @method describeTable 41 | * @param {} process 42 | * @return 43 | */ 44 | UX.describeTable = function(process) { 45 | var table = new Table({ 46 | style : {'padding-left' : 1, head : ['cyan', 'bold'], border : ['white'], compact : true} 47 | }); 48 | var pm2_env = process.pm2_env; 49 | 50 | console.log('Describing process with pid %d - name %s', pm2_env.pm_id, pm2_env.name); 51 | table.push( 52 | { 'status' : colorStatus(pm2_env.status) }, 53 | { 'name': pm2_env.name }, 54 | { 'id' : pm2_env.pm_id }, 55 | { 'path' : pm2_env.pm_exec_path }, 56 | { 'args' : pm2_env.args ? JSON.parse(pm2_env.args.replace(/'/g, '"')).join(' ') : '' }, 57 | { 'exec cwd' : pm2_env.pm_cwd }, 58 | { 'error log path' : pm2_env.pm_err_log_path }, 59 | { 'out log path' : pm2_env.pm_out_log_path }, 60 | { 'pid path' : pm2_env.pm_pid_path }, 61 | { 'mode' : pm2_env.exec_mode }, 62 | { 'node v8 arguments' : pm2_env.node_args }, 63 | { 'watch & reload' : pm2_env.watch ? chalk.green.bold('✔') : '✘' }, 64 | { 'interpreter' : pm2_env.exec_interpreter }, 65 | { 'restarts' : pm2_env.restart_time }, 66 | { 'unstable restarts' : pm2_env.unstable_restarts }, 67 | { 'uptime' : (pm2_env.pm_uptime && pm2_env.status == 'online') ? timeSince(pm2_env.pm_uptime) : 0 }, 68 | { 'created at' : pm2_env.created_at != null ? new Date(pm2_env.created_at).toISOString() : 'N/A' } 69 | ); 70 | 71 | console.log(table.toString()); 72 | }; 73 | 74 | /** 75 | * Description 76 | * @method dispAsTable 77 | * @param {} list 78 | * @param {} interact_infos 79 | * @return 80 | */ 81 | UX.dispAsTable = function(list, interact_infos) { 82 | var table = new Table({ 83 | head: ['App name', 'id', 'mode', 'PID', 'status', 'restarted', 'uptime', 'memory', 'watching'], 84 | colAligns : ['left', 'left', 'left', 'left', 'left' , 'right', 'left', 'right', 'right'], 85 | style : {'padding-left' : 1, head : ['cyan', 'bold'], border : ['white'], compact : true} 86 | }); 87 | 88 | if (!list) 89 | return console.log('list empty'); 90 | list.forEach(function(l) { 91 | var obj = {}; 92 | 93 | var mode = l.pm2_env.exec_mode.split('_mode')[0]; 94 | var status = l.pm2_env.status; 95 | var port = l.pm2_env.port; 96 | var key = l.pm2_env.name.bold || p.basename(l.pm2_env.pm_exec_path.script).bold; 97 | 98 | obj[key] = [ 99 | l.pm2_env.pm_id, 100 | mode == 'fork' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster'), 101 | l.pid, 102 | colorStatus(status), 103 | l.pm2_env.restart_time ? l.pm2_env.restart_time : 0, 104 | (l.pm2_env.pm_uptime && status == 'online') ? timeSince(l.pm2_env.pm_uptime) : 0, 105 | l.monit ? UX.bytesToSize(l.monit.memory, 3) : '', 106 | l.pm2_env.watch ? chalk.green.bold('activated') : chalk.grey('unactivated') 107 | ]; 108 | 109 | table.push(obj); 110 | }); 111 | 112 | console.log(table.toString()); 113 | }; 114 | 115 | UX.processing = { 116 | /** 117 | * Description 118 | * @method start 119 | * @return 120 | */ 121 | start : function() { 122 | console.log('Processing......'); 123 | }, 124 | /** 125 | * Description 126 | * @method stop 127 | * @return 128 | */ 129 | stop : function() { 130 | } 131 | }; 132 | 133 | /** 134 | * Description 135 | * @method bytesToSize 136 | * @param {} bytes 137 | * @param {} precision 138 | * @return 139 | */ 140 | UX.bytesToSize = function(bytes, precision) { 141 | var kilobyte = 1024; 142 | var megabyte = kilobyte * 1024; 143 | var gigabyte = megabyte * 1024; 144 | var terabyte = gigabyte * 1024; 145 | 146 | if ((bytes >= 0) && (bytes < kilobyte)) { 147 | return bytes + ' B '; 148 | } else if ((bytes >= kilobyte) && (bytes < megabyte)) { 149 | return (bytes / kilobyte).toFixed(precision) + ' KB '; 150 | } else if ((bytes >= megabyte) && (bytes < gigabyte)) { 151 | return (bytes / megabyte).toFixed(precision) + ' MB '; 152 | } else if ((bytes >= gigabyte) && (bytes < terabyte)) { 153 | return (bytes / gigabyte).toFixed(precision) + ' GB '; 154 | } else if (bytes >= terabyte) { 155 | return (bytes / terabyte).toFixed(precision) + ' TB '; 156 | } else { 157 | return bytes + ' B '; 158 | } 159 | }; 160 | 161 | /** 162 | * Description 163 | * @method colorStatus 164 | * @param {} status 165 | * @return 166 | */ 167 | function colorStatus(status) { 168 | switch (status) { 169 | case 'online': 170 | return chalk.green.bold('online'); 171 | break; 172 | case 'launching': 173 | return chalk.blue.bold('launching'); 174 | break; 175 | default: 176 | return chalk.red.bold(status); 177 | } 178 | } 179 | 180 | /** 181 | * Description 182 | * @method timeSince 183 | * @param {} date 184 | * @return BinaryExpression 185 | */ 186 | function timeSince(date) { 187 | 188 | var seconds = Math.floor((new Date() - date) / 1000); 189 | 190 | var interval = Math.floor(seconds / 31536000); 191 | 192 | if (interval > 1) { 193 | return interval + 'Y'; 194 | } 195 | interval = Math.floor(seconds / 2592000); 196 | if (interval > 1) { 197 | return interval + 'M'; 198 | } 199 | interval = Math.floor(seconds / 86400); 200 | if (interval > 1) { 201 | return interval + 'D'; 202 | } 203 | interval = Math.floor(seconds / 3600); 204 | if (interval > 1) { 205 | return interval + 'h'; 206 | } 207 | interval = Math.floor(seconds / 60); 208 | if (interval > 1) { 209 | return interval + 'm'; 210 | } 211 | return Math.floor(seconds) + 's'; 212 | } 213 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.10.1-2-3-4 3 | 4 | - env option via programmatic interface 5 | - fix watch system 6 | - correct pm2 describe command 7 | - close file used via pm2 flush 8 | - add startOrReload 9 | - better closing events 10 | 11 | # 0.10.0 - PM2 Hellfire release 12 | 13 | - PM2 hearth code has been refactored and now it handles extreme scenario without any leak or bug 14 | - PM2 restart refresh current environment variables #528 15 | - PM2 delete all more verbose 16 | - PM2 reset reset restart numbers 17 | - Auto update script at PM2 installation 18 | - --watch enhanced to avoid zombie processes 19 | - Restart app when reaching a limit of memory by using --max-memory-restart (and max_memory_restart via JSON)(https://github.com/Unitech/pm2#max-memory-restart) 20 | - PM2 respects strong unix standard process management 21 | - Remove timestamps by default with pm2 logs 22 | - Coffeescript not enabled by default anymore (enhance memory usage) 23 | - PM2 Programmatic interface enhanced 24 | - PM2 hearth refactor 25 | - PM2 describe show node-args 26 | - node_args for V8 options is now available via JSON declaration 27 | - Watch system avoid ghost processes 28 | - Memory leak fixes 29 | - Better performance on interface 30 | - Fix tests 31 | - Enable PM2_NODE_OPTIONS and node-args for fork mode 32 | - Dependencies updated 33 | - Faster monitoring system 34 | - AXM actions unification 35 | - Socket errors handled 36 | - Watchdog via Agent - restart automatically PM2 with previous processes in case of crash 37 | - PM2_NODE_OPTIONS deprecation (use --node-args instead) 38 | 39 | # 0.9.6 - 0.9.5 - 0.9.4 40 | 41 | - Bash test auto exit when failure 42 | - Bump fix log streaming 43 | - Bump fix to display old logs streaming by default 44 | - Bump fix 45 | 46 | # 0.9.3 47 | 48 | - Critical bug on fork mode fixed (stream close) 49 | - Advanced log display interface pm2-logs #589 50 | - Simple log timestamp via --log-date-format (with momentJS formating) #183 51 | - Possible to pass arguments via scriptArg with programmatic PM2 #591 52 | - Gentoo startup script generation #592 53 | - Fix run-as-user and run-as-group in fork mode #582 54 | - Documentation update 55 | 56 | # 0.9.2 57 | 58 | - max_restart enabled 59 | - sudo fix for init scripts 60 | - some startup refactoring 61 | - Possibility to specify the configuration folder for PM2 via process.env.PM2_HOME 62 | - Fix date format 63 | - N/A for undefined date 64 | - Evented interactions with PM2, available via pm2-interface 65 | - Deep Interactor refactoring 66 | - Force reload for upstart script 67 | 68 | # 0.9.0-0.9.1 69 | 70 | - CLI flattening 71 | - require('pm2') possible to interact with 72 | - deployment system 73 | - Remove builtin monitoring feature 74 | - Fix watch on delete #514 75 | - Gracefull reload now rightly handled #502 76 | - Allow path in watch option #501 77 | - Allow management of non-interpreted binaries #499 78 | - Documentation fixes 79 | 80 | # 0.8.12-0.8.15 81 | 82 | - Version bumping 83 | 84 | # 0.8.12 85 | 86 | - Fix CWD option #295 87 | 88 | # 0.8.10-0.8.11 89 | 90 | - Builtin monitoring feature with email (with pm2 subscribe) 91 | - Reload Logs for Fork 92 | - Deletion of possible circular dependencies error 93 | - pm2 updatePM2 command to update in-memory pm2 94 | - notification message if the in-memory pm2 is outdated 95 | - cwd option in json #405 #417 #295 96 | - README updates 97 | - ipc channel for fork mode 98 | - re enable process event loggin for interactor 99 | - avoid possible stream error 100 | - watch ignore option in JSON 101 | 102 | # 0.8.5-6 103 | 104 | - Update monitoring module 105 | 106 | # 0.8.4 107 | 108 | - Remove C++ binding for monitoring 109 | - Update axon and axon-rpc 110 | 111 | # 0.8.2 112 | 113 | - Adds option to switch to a different user/group before starting a managed process #329 114 | - watch doesnt watch node_module folder 115 | - default log files and pid files location can be overidded by PM2_LOG_DIR / PM2_PID_DIR 116 | 117 | 118 | # 0.8.1 119 | 120 | - Readme changes #400 #398 121 | - Fix describe command #403 122 | - reload/gracefulReload throw error if no process has been reloaded #340 123 | 124 | # 0.8.0 125 | 126 | - More verbosity to pm2.log 127 | - Fast Watch & Reload 128 | - New README.md 129 | - --merge-logs option to merge logs for a group of process 130 | - logs reload with SIGUSR2 or `pm2 reloadLogs` 131 | - return failure code when no process has been reloaded 132 | - Upgrade of outdated packages 133 | - Silent (-s) flag remove all possible pm2 output to CLI 134 | - New display for list, more compact 135 | - `pm2 describe ` to get more details about a process 136 | - Fixed 0.10.x issue when stop/kill 137 | - Helper shown when -h 138 | - Linter errors 139 | - Systemd support for Fedora / ArchLinux 140 | - #381 Add support for Amazon Linux startup script 141 | - Fixed rendering 142 | - Interaction possible with VitalSigns.io 143 | - Avoid exception when dump file is not present 144 | 145 | # 0.7.8 146 | 147 | - List processes with user right `service pm2-init.sh status` 148 | 149 | # 0.7.7 150 | 151 | - Bug fixes, stability fixes 152 | 153 | # 0.7.2 154 | 155 | - harmony can be enabled [Enabling harmony](#a66) 156 | - can pass any options to node via PM2_NODE_OPTIONS, configurable via ~/.pm2/custom_options.sh 157 | - pid file written in ~/.pm2/pm2.pid 158 | - startup script support for CentOS 159 | - --no-daemon option (Alex Kocharin) 160 | - json file now can be : started/stoped/restarted/deleted 161 | - coffeescript support for new versions (Hao-kang Den) 162 | - accept JSON via pipe from standard input (Ville Walveranta) 163 | - adjusting logical when process got an uncaughtException (Ethanz) 164 | 165 | ## Update from 0.x -> 0.7.2 166 | 167 | - CentOS crontab option should not be used anymore and use the new init script with `pm2 startup centos` 168 | - If you use the configuration file or the harmonoy option, you should regenerate the init script 169 | 170 | # 0.7.1 171 | 172 | - Integrates hardened reload, graceful reload and strengthened process management 173 | 174 | # 0.7.0 175 | 176 | - Reload works at 100% 177 | - Logs are now separated by process id 178 | - Minimal listing with -m option 179 | - pid files are deleted once process exit 180 | - ping method to launch or knwo if pm2 is alive 181 | - more tests 182 | - coffeescript is supported in cluster mode 183 | - clean exit 184 | - clean process stopping 185 | - speed process management enhanced 186 | - async used instead of recuresive loops 187 | - broad test for node 0.11.10 0.11.9 0.11.8 0.11.7 0.11.5 0.10.24 0.10.23 0.10.22 0.10.21 0.10.20 0.10.19 0.10.18 0.10.17 0.10.16 0.10.15 0.10.14 0.10.13 0.10.12 0.10.11 0.8 188 | 189 | # 0.6.8 190 | 191 | - Homogeneize JSON #186 192 | - Auto intepreter selection (you can do pm2 start app.php) 193 | 194 | # 0.5.6 195 | 196 | - Coffeescript support 197 | - Updating dependencies - axon - commander 198 | - Log feature enhanced - duplicates removed - name or id can be passed to pm2 logs xxx 199 | 200 | # 0.5.5 201 | 202 | - Ability to set a name to a launched script + tests 203 | - with the --name option when launching file 204 | - with the "name" parameter for JSON files 205 | - Ability to restart a script by name + tests 206 | - Upgrade node-usage to 0.3.8 - fix monitoring feedback for MacOSx 207 | - require.main now require the right file (activate it by modifying MODIFY_REQUIRE in constants.js) 208 | - CentOS startup script with pm2 startup centos 209 | - 0 downtime reload 210 | 211 | # 0.5.4 212 | 213 | - Remove unused variable in startup script 214 | - Add options min_uptime max_restarts when configuring an app with JSON 215 | - Remove pid file on process exit 216 | - Command stopAll -> stop all | restartAll -> restart all (backward compatible with older versions) 217 | 218 | # 0.5.0 219 | 220 | - Hardening tests 221 | - Cron mode to restart a script 222 | - Arguments fully supported 223 | - MacOSx monitoring possible 224 | -------------------------------------------------------------------------------- /lib/God/Reload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @file Reload functions related 5 | * @author Alexandre Strzelewicz 6 | * @project PM2 7 | */ 8 | 9 | var async = require('async'); 10 | var cst = require('../../constants.js'); 11 | var Common = require('../Common'); 12 | /** 13 | * softReload will wait permission from process to exit 14 | * @method softReload 15 | * @param {} God 16 | * @param {} id 17 | * @param {} cb 18 | * @return Literal 19 | */ 20 | function softReload(God, id, cb) { 21 | var t_key = '_old_' + id; 22 | var timeout_1; 23 | var timeout_2; 24 | 25 | // Move old worker to tmp id 26 | God.clusters_db[t_key] = God.clusters_db[id]; 27 | delete God.clusters_db[id]; 28 | 29 | var old_worker = God.clusters_db[t_key]; 30 | // Deep copy 31 | var new_env = JSON.parse(JSON.stringify(old_worker.pm2_env)); 32 | new_env.restart_time += 1; 33 | 34 | God.bus.emit('process:start_soft_reload', { process : Common.serialize(old_worker) }); 35 | 36 | // Reset created_at and unstable_restarts 37 | God.resetState(new_env); 38 | 39 | old_worker.pm2_env.pm_id = t_key; 40 | 41 | God.executeApp(new_env, function(err, new_worker) { 42 | if (err) return cb(err); 43 | 44 | timeout_1 = setTimeout(function() { 45 | // We've waited long enough; disconnect the old worker. 46 | new_worker.emit('listening'); 47 | }, cst.GRACEFUL_TIMEOUT); 48 | 49 | /** 50 | * Description 51 | * @method onmessage 52 | * @param {} message 53 | * @return 54 | */ 55 | var onmessage = function(message) { 56 | if (message === 'online') { 57 | // The new worker is up but does not need to listen. 58 | new_worker.emit('listening'); 59 | } 60 | } 61 | 62 | new_worker.on('message', onmessage); 63 | 64 | // Bind to know when the new process is up 65 | new_worker.once('listening', function() { 66 | new_worker.removeListener('message', onmessage); 67 | clearTimeout(timeout_1); 68 | console.log('%s - id%d worker listening', 69 | new_worker.pm2_env.pm_exec_path, 70 | new_worker.pm2_env.pm_id); 71 | 72 | 73 | old_worker.once('disconnect', function() { 74 | clearTimeout(timeout_2); 75 | console.log('%s - id%s worker disconnect', 76 | old_worker.pm2_env.pm_exec_path, 77 | old_worker.pm2_env.pm_id); 78 | return God.deleteProcessId(t_key, cb); 79 | }); 80 | timeout_2 = setTimeout(function() { 81 | // Don't throw if the channel has already been closed. 82 | try { 83 | old_worker.disconnect(); 84 | } catch (e) { 85 | console.error('Worker %d is already disconnected', old_worker.pm2_env.pm_id); 86 | God.deleteProcessId(t_key, cb); 87 | } 88 | }, cst.GRACEFUL_TIMEOUT); 89 | /** 90 | * Message sent to the process to alert to shutdown 91 | * Then after cst.GRACEFUL_TIMEOUT ms it disconnect the process 92 | */ 93 | // Don't throw if the channel has already been closed. 94 | try { 95 | old_worker.send('shutdown'); 96 | } catch(e) { 97 | console.error('Worker %d is already disconnected', old_worker.pm2_env.pm_id); 98 | God.deleteProcessId(t_key, cb); 99 | } 100 | }); 101 | return false; 102 | }); 103 | return false; 104 | }; 105 | 106 | /** 107 | * hardReload will reload without waiting permission from process 108 | * @method hardReload 109 | * @param {} God 110 | * @param {} id 111 | * @param {} cb 112 | * @return Literal 113 | */ 114 | function hardReload(God, id, cb) { 115 | var t_key = '_old_' + id; 116 | var timer; 117 | // Move old worker to tmp id 118 | God.clusters_db[t_key] = God.clusters_db[id]; 119 | delete God.clusters_db[id]; 120 | 121 | var old_worker = God.clusters_db[t_key]; 122 | // Deep copy 123 | var new_env = JSON.parse(JSON.stringify(old_worker.pm2_env)); 124 | new_env.restart_time += 1; 125 | 126 | God.bus.emit('process:start_reload', { process : Common.serialize(old_worker) }); 127 | 128 | // Reset created_at and unstable_restarts 129 | God.resetState(new_env); 130 | 131 | old_worker.pm2_env.pm_id = t_key; 132 | 133 | 134 | God.executeApp(new_env, function(err, new_worker) { 135 | if (err) return cb(err); 136 | 137 | timer = setTimeout(function() { 138 | return God.deleteProcessId(t_key, cb); 139 | }, 4000); 140 | 141 | // Bind to know when the new process is up 142 | new_worker.once('listening', function() { 143 | clearTimeout(timer); 144 | console.log('%s - id%d worker listening', 145 | new_worker.pm2_env.pm_exec_path, 146 | new_worker.pm2_env.pm_id); 147 | //old_worker.once('message', function(type) { 148 | old_worker.once('disconnect', function() { 149 | console.log('%s - id%s worker disconnect', 150 | old_worker.pm2_env.pm_exec_path, 151 | old_worker.pm2_env.pm_id); 152 | 153 | God.deleteProcessId(t_key, cb); 154 | }); 155 | try { 156 | old_worker.disconnect(); 157 | } catch(e) { 158 | console.error('Worker %d is already disconnected', old_worker.pm2_env.pm_id); 159 | God.deleteProcessId(t_key, cb); 160 | } 161 | }); 162 | return false; 163 | }); 164 | return false; 165 | }; 166 | 167 | /** 168 | * Description 169 | * @method exports 170 | * @param {} God 171 | * @return 172 | */ 173 | module.exports = function(God) { 174 | 175 | /** 176 | * Description 177 | * @method softReloadProcessId 178 | * @param {} id 179 | * @param {} cb 180 | * @return CallExpression 181 | */ 182 | God.softReloadProcessId = function(id, cb) { 183 | if (!(id in God.clusters_db)) 184 | return cb(new Error({msg : 'PM ID unknown'}), {}); 185 | if (God.clusters_db[id].pm2_env.status == cst.STOPPED_STATUS && 186 | God.clusters_db[id].pm2_env.status == cst.STOPPING_STATUS) 187 | return cb(null, God.getFormatedProcesses()); 188 | 189 | return softReload(God, id, cb); 190 | }; 191 | 192 | /** 193 | * Description 194 | * @method reloadProcessId 195 | * @param {} id 196 | * @param {} cb 197 | * @return CallExpression 198 | */ 199 | God.reloadProcessId = function(id, cb) { 200 | if (!(id in God.clusters_db)) 201 | return cb(new Error({msg : 'PM ID unknown'}), {}); 202 | if (God.clusters_db[id].pm2_env.status == cst.STOPPED_STATUS && 203 | God.clusters_db[id].pm2_env.status == cst.STOPPING_STATUS) 204 | return cb(null, God.getFormatedProcesses()); 205 | 206 | return hardReload(God, id, cb); 207 | }; 208 | 209 | /** 210 | * Description 211 | * @method reload 212 | * @param {} env 213 | * @param {} cb 214 | * @return 215 | */ 216 | God.reload = function(env, cb) { 217 | var processes = God.getFormatedProcesses(); 218 | var l = processes.length; 219 | 220 | async.eachLimit(processes, 1, function(proc, next) { 221 | if (proc.state == cst.STOPPED_STATUS || 222 | proc.state == cst.STOPPING_STATUS || 223 | proc.pm2_env.exec_mode != 'cluster_mode') 224 | return next(); 225 | God.reloadProcessId(proc.pm2_env.pm_id, function() { 226 | return next(); 227 | }); 228 | return false; 229 | }, function(err) { 230 | if (err) return cb(new Error(err)); 231 | return cb(null, {process_restarted : l}); 232 | }); 233 | }; 234 | 235 | /** 236 | * Description 237 | * @method reloadProcessName 238 | * @param {} name 239 | * @param {} cb 240 | * @return 241 | */ 242 | God.reloadProcessName = function(name, cb) { 243 | var processes = God.findByName(name); 244 | var l = processes.length; 245 | 246 | async.eachLimit(processes, 1, function(proc, next) { 247 | if (proc.state == cst.STOPPED_STATUS || 248 | proc.state == cst.STOPPING_STATUS || 249 | proc.pm2_env.exec_mode != 'cluster_mode') 250 | return next(); 251 | God.reloadProcessId(proc.pm2_env.pm_id, function() { 252 | return next(); 253 | }); 254 | return false; 255 | }, function(err) { 256 | if (err) return cb(new Error(err)); 257 | return cb(null, {process_restarted : l}); 258 | }); 259 | }; 260 | 261 | }; 262 | -------------------------------------------------------------------------------- /test/programmatic/god.mocha.js: -------------------------------------------------------------------------------- 1 | 2 | var God = require('../../lib/God'); 3 | var numCPUs = require('os').cpus().length; 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var should = require('should'); 7 | var Common = require('../../lib/Common'); 8 | /** 9 | * Description 10 | * @method getConf 11 | * @return AssignmentExpression 12 | */ 13 | function getConf() { 14 | var a = Common.resolveAppPaths({ 15 | script : path.resolve(process.cwd(), 'test/fixtures/echo.js'), 16 | name : 'echo', 17 | instances : 2 18 | }); 19 | return a; 20 | } 21 | 22 | function getConf2() { 23 | return Common.resolveAppPaths({ 24 | script : path.resolve(process.cwd(), 'test/fixtures/child.js'), 25 | instances : 4, 26 | exec_mode : 'cluster_mode', 27 | name : 'child' 28 | }); 29 | } 30 | 31 | function getConf3() { 32 | return Common.resolveAppPaths({ 33 | script : path.resolve(process.cwd(), 'test/fixtures/child.js'), 34 | instances : 10, 35 | exec_mode : 'cluster_mode', 36 | name : 'child' 37 | }); 38 | } 39 | 40 | function getConf4() { 41 | return Common.resolveAppPaths({ 42 | script : path.resolve(process.cwd(), 'test/fixtures/args.js'), 43 | args : "['-d', '-a']", 44 | instances : '1', 45 | name : 'child' 46 | }); 47 | } 48 | 49 | 50 | describe('God', function() { 51 | before(function(done) { 52 | God.deleteAll({}, function(err, dt) { 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should have right properties', function() { 58 | God.should.have.property('prepare'); 59 | God.should.have.property('ping'); 60 | God.should.have.property('getProcesses'); 61 | God.should.have.property('getMonitorData'); 62 | God.should.have.property('getSystemData'); 63 | God.should.have.property('getFormatedProcesses'); 64 | God.should.have.property('checkProcess'); 65 | God.should.have.property('stopAll'); 66 | God.should.have.property('reloadLogs'); 67 | God.should.have.property('stopProcessId'); 68 | God.should.have.property('reload'); 69 | God.should.have.property('reloadProcessName'); 70 | God.should.have.property('sendSignalToProcessId'); 71 | God.should.have.property('sendSignalToProcessName'); 72 | }); 73 | 74 | describe('Special functions for God', function() { 75 | before(function(done) { 76 | God.deleteAll({}, function(err, dt) { 77 | done(); 78 | }); 79 | }); 80 | 81 | it('should kill a process by name', function(done) { 82 | God.prepare(getConf(), function(err, procs) { 83 | God.getFormatedProcesses().length.should.equal(2); 84 | 85 | God.stopProcessName('echo', function() { 86 | God.getFormatedProcesses().length.should.equal(2); 87 | God.deleteAll({}, function() { 88 | done(); 89 | }); 90 | }); 91 | }); 92 | }); 93 | }); 94 | 95 | describe('One process', function() { 96 | var proc, pid; 97 | 98 | before(function(done) { 99 | God.deleteAll({}, function(err, dt) { 100 | done(); 101 | }); 102 | }); 103 | 104 | it('should fork one process', function(done) { 105 | God.prepare(getConf(), function(err, procs) { 106 | should(err).be.null; 107 | pid = procs[0].pid; 108 | procs[0].pm2_env.status.should.be.equal('online'); 109 | God.getFormatedProcesses().length.should.equal(2); 110 | done(); 111 | }); 112 | }); 113 | }); 114 | 115 | describe('Process State Machine', function() { 116 | var clu, pid; 117 | 118 | before(function(done) { 119 | God.deleteAll({}, function(err, dt) { 120 | done(); 121 | }); 122 | }); 123 | it('should start a process', function(done) { 124 | God.prepare(getConf(), function(err, procs) { 125 | clu = procs[0]; 126 | 127 | pid = clu.pid; 128 | procs[0].pm2_env.status.should.be.equal('online'); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('should stop a process and keep in database on state stopped', function(done) { 134 | God.stopProcessId(clu.pm2_env.pm_id, function(err, dt) { 135 | var proc = God.findProcessById(clu.pm2_env.pm_id); 136 | proc.pm2_env.status.should.be.equal('stopped'); 137 | God.checkProcess(proc.process.pid).should.be.equal(false); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('should restart the same process and set it as state online and be up', function(done) { 143 | God.restartProcessId({id:clu.pm2_env.pm_id}, function(err, dt) { 144 | var proc = God.findProcessById(clu.pm2_env.pm_id); 145 | proc.pm2_env.status.should.be.equal('online'); 146 | God.checkProcess(proc.process.pid).should.be.equal(true); 147 | done(); 148 | }); 149 | }); 150 | 151 | it('should stop this process by name and keep in db on state stopped', function(done) { 152 | God.stopProcessName(clu.pm2_env.name, function(err, dt) { 153 | var proc = God.findProcessById(clu.pm2_env.pm_id); 154 | proc.pm2_env.status.should.be.equal('stopped'); 155 | God.checkProcess(proc.process.pid).should.be.equal(false); 156 | done(); 157 | }); 158 | }); 159 | 160 | it('should restart the same process by NAME and set it as state online and be up', function(done) { 161 | God.restartProcessName(clu.pm2_env.name, function(err, dt) { 162 | var proc = God.findProcessById(clu.pm2_env.pm_id); 163 | proc.pm2_env.status.should.be.equal('online'); 164 | God.checkProcess(proc.process.pid).should.be.equal(true); 165 | done(); 166 | }); 167 | }); 168 | 169 | it('should stop and delete a process id', function(done) { 170 | var old_pid = clu.pid; 171 | God.deleteProcessId(clu.pm2_env.pm_id, function(err, dt) { 172 | var proc = God.findProcessById(clu.pm2_env.pm_id); 173 | God.checkProcess(old_pid).should.be.equal(false); 174 | dt.length.should.be.equal(1); 175 | done(); 176 | }); 177 | }); 178 | 179 | it('should start stop and delete the process name from database', function(done) { 180 | God.prepare(getConf(), function(err, _clu) { 181 | pid = _clu[0].pid; 182 | _clu[0].pm2_env.status.should.be.equal('online'); 183 | var old_pid = _clu[0].pid; 184 | God.deleteProcessName(_clu[0].pm2_env.name, function(err, dt) { 185 | setTimeout(function() { 186 | var proc = God.findProcessById(clu.pm2_env.pm_id); 187 | should(proc == null); 188 | God.checkProcess(old_pid).should.be.equal(false); 189 | done(); 190 | }, 100); 191 | }); 192 | }); 193 | }); 194 | 195 | }); 196 | 197 | 198 | describe('Reload - cluster', function() { 199 | 200 | before(function(done) { 201 | God.deleteAll({}, function(err, dt) { 202 | done(); 203 | }); 204 | }); 205 | 206 | it('should launch app', function(done) { 207 | God.prepare(getConf2(), function(err, procs) { 208 | var processes = God.getFormatedProcesses(); 209 | 210 | setTimeout(function() { 211 | processes.length.should.equal(4); 212 | processes.forEach(function(proc) { 213 | proc.pm2_env.restart_time.should.eql(0); 214 | }); 215 | done(); 216 | }, 100); 217 | }); 218 | }); 219 | 220 | it('should restart the same process and set it as state online and be up', function(done) { 221 | var processes = God.getFormatedProcesses(); 222 | 223 | God.reload({}, function(err, dt) { 224 | var processes = God.getFormatedProcesses(); 225 | 226 | processes.length.should.equal(4); 227 | processes.forEach(function(proc) { 228 | proc.pm2_env.restart_time.should.eql(1); 229 | }); 230 | done(); 231 | }); 232 | }); 233 | 234 | }); 235 | 236 | describe('Multi launching', function() { 237 | 238 | before(function(done) { 239 | God.deleteAll({}, function(err, dt) { 240 | setTimeout(done, 50); 241 | }); 242 | }); 243 | 244 | 245 | afterEach(function(done) { 246 | God.deleteAll({}, function(err, dt) { 247 | setTimeout(done, 50); 248 | }); 249 | }); 250 | 251 | it('should launch multiple processes depending on CPUs available', function(done) { 252 | God.prepare(Common.resolveAppPaths({ 253 | script : path.resolve(process.cwd(), 'test/fixtures/echo.js'), 254 | name : 'child', 255 | instances:3 256 | }), function(err, procs) { 257 | God.getFormatedProcesses().length.should.equal(3); 258 | procs.length.should.equal(3); 259 | done(); 260 | }); 261 | }); 262 | 263 | it('should start maximum processes depending on CPU numbers', function(done) { 264 | God.prepare(getConf3(), function(err, procs) { 265 | God.getFormatedProcesses().length.should.equal(10); 266 | procs.length.should.equal(10); 267 | done(); 268 | }); 269 | }); 270 | 271 | it('should handle arguments', function(done) { 272 | God.prepare(getConf4(), function(err, procs) { 273 | setTimeout(function() { 274 | God.getFormatedProcesses()[0].pm2_env.restart_time.should.eql(0); 275 | done(); 276 | }, 500); 277 | }); 278 | }); 279 | }); 280 | 281 | it('should report pm2 version', function(done) { 282 | God.getVersion({}, function(err, version) { 283 | version.should.not.be.null; 284 | done(); 285 | }); 286 | }); 287 | }); 288 | -------------------------------------------------------------------------------- /test/programmatic/programmatic.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Test Satan in a programmatic way 4 | */ 5 | 6 | var pm2 = require('../..'); 7 | var should = require('should'); 8 | var assert = require('better-assert'); 9 | var path = require('path'); 10 | 11 | //process.env.PM2_SILENT = true; 12 | 13 | describe('PM2 programmatic calls', function() { 14 | 15 | var proc1 = null; 16 | var procs = []; 17 | 18 | after(function(done) { 19 | pm2.delete('all', function(err, ret) { 20 | pm2.disconnect(done); 21 | }); 22 | }); 23 | 24 | before(function(done) { 25 | pm2.connect(function() { 26 | setTimeout(function() { 27 | pm2.delete('all', function(err, ret) { 28 | done(); 29 | }); 30 | }, 1000); 31 | }); 32 | }); 33 | 34 | it('should start a script', function(done) { 35 | pm2.start(process.cwd() + '/test/programmatic/child.js', 36 | {}, 37 | function(err, data) { 38 | proc1 = data[0]; 39 | 40 | should(err).be.null; 41 | done(); 42 | }); 43 | }); 44 | 45 | it('should start a script and force to launch it', function(done) { 46 | pm2.start(process.cwd() + '/test/programmatic/child.js', { 47 | force : true, 48 | name : 'toto' 49 | }, function(err, data) { 50 | should(err).be.null; 51 | data.length.should.eql(1); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('should start a script in a specified cwd', function(done) { 57 | pm2.start(process.cwd() + '/test/programmatic/cwd.js', 58 | {cwd:process.cwd() + '/test/programmatic/'}, 59 | function(err, data) { 60 | proc1 = data[0]; 61 | proc1.pm2_env.cwd.should.eql(process.cwd() + '/test/programmatic/'); 62 | should(err).be.null; 63 | done(); 64 | }); 65 | }); 66 | 67 | it('should notice error if wrong file passed', function(done) { 68 | pm2.start(process.cwd() + '/child.js', { 69 | force : true, 70 | name : 'tota', 71 | instances : 3 72 | }, function(err, data) { 73 | should(err).exists; 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should start a script and force to launch it', function(done) { 79 | pm2.start(process.cwd() + '/test/programmatic/child.js', { 80 | force : true, 81 | name : 'tota', 82 | instances : 3 83 | }, function(err, data) { 84 | should(err).be.null; 85 | data.length.should.eql(3); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('should get pm2 version', function(done) { 91 | pm2.getVersion(function(err, data) { 92 | should(err).be.null; 93 | data.should.exists; 94 | done(); 95 | }); 96 | }); 97 | 98 | it('should list processes', function(done) { 99 | pm2.list(function(err, ret) { 100 | should(err).be.null; 101 | ret.length.should.eql(6); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should delete one process', function(done) { 107 | pm2.delete(proc1.pm2_env.pm_id, function(err, ret) { 108 | should(err).be.null; 109 | pm2.list(function(err, ret) { 110 | should(err).be.null; 111 | ret.length.should.eql(5); 112 | done(); 113 | }); 114 | }); 115 | }); 116 | 117 | it('should save all processes', function(done) { 118 | pm2.dump(function(err, ret) { 119 | should(err).be.null; 120 | done(); 121 | }); 122 | }); 123 | 124 | it('should delete processes', function(done) { 125 | pm2.delete('all', function(err, ret) { 126 | should(err).be.null; 127 | pm2.list(function(err, ret) { 128 | should(err).be.null; 129 | ret.length.should.eql(0); 130 | done(); 131 | }); 132 | }); 133 | }); 134 | 135 | it('should resurrect processes', function(done) { 136 | pm2.resurrect(function(err, ret) { 137 | should(err).be.null; 138 | pm2.list(function(err, ret) { 139 | should(err).be.null; 140 | ret.length.should.eql(5); 141 | done(); 142 | }); 143 | }); 144 | }); 145 | 146 | it('should ping pm2', function(done) { 147 | pm2.ping(function(err, ret) { 148 | should(err).be.null; 149 | done(); 150 | }); 151 | }); 152 | 153 | it('should launch pm2 web API', function(done) { 154 | pm2.web(function(err, ret) { 155 | should(err).be.null; 156 | pm2.list(function(err, ret) { 157 | should(err).be.null; 158 | ret.length.should.eql(6); 159 | done(); 160 | }); 161 | }); 162 | }); 163 | 164 | it('should reload all', function(done) { 165 | pm2.reload('all', function(err, ret) { 166 | should(err).be.null; 167 | done(); 168 | }); 169 | }); 170 | 171 | it('should reload only on2', function(done) { 172 | pm2.reload('tota', function(err, ret) { 173 | should(err).be.null; 174 | pm2.describe('tota', function(err, proc) { 175 | should(err).be.null; 176 | procs = proc; 177 | proc.length.should.eql(3); 178 | proc[0].pm2_env.restart_time.should.eql(2); 179 | done(); 180 | }); 181 | }); 182 | }); 183 | 184 | it('should describe all process with name', function(done) { 185 | pm2.describe('tota', function(err, proc) { 186 | should(err).be.null; 187 | proc.length.should.eql(3); 188 | done(); 189 | }); 190 | }); 191 | 192 | 193 | describe('Restart methods', function() { 194 | it('should restart all', function(done) { 195 | pm2.restart('all', function(err, ret) { 196 | should(err).be.null; 197 | pm2.describe('tota', function(err, proc) { 198 | should(err).be.null; 199 | proc.length.should.eql(3); 200 | proc[0].pm2_env.restart_time.should.eql(3); 201 | done(); 202 | }); 203 | }); 204 | }); 205 | 206 | it('should restart process by name', function(done) { 207 | pm2.restart('tota', function(err, ret) { 208 | should(err).be.null; 209 | pm2.describe('tota', function(err, proc) { 210 | should(err).be.null; 211 | proc.length.should.eql(3); 212 | proc[0].pm2_env.restart_time.should.eql(4); 213 | done(); 214 | }); 215 | }); 216 | }); 217 | 218 | it('should restart process by id', function(done) { 219 | pm2.restart(procs[0].pm2_env.pm_id, function(err, ret) { 220 | should(err).be.null; 221 | pm2.describe(procs[0].pm2_env.pm_id, function(err, proc) { 222 | should(err).be.null; 223 | proc.length.should.eql(1); 224 | proc[0].pm2_env.restart_time.should.eql(5); 225 | done(); 226 | }); 227 | }); 228 | }); 229 | }); 230 | 231 | describe('Stop methods', function() { 232 | it('should stop process name', function(done) { 233 | pm2.stop(procs[0].pm2_env.name, function(err, ret) { 234 | should(err).be.null; 235 | pm2.describe(procs[0].pm2_env.name, function(err, procs) { 236 | should(err).be.null; 237 | procs[0].pm2_env.status.should.eql('stopped'); 238 | done(); 239 | }); 240 | }); 241 | }); 242 | 243 | it('should stop process id', function(done) { 244 | pm2.stop(procs[1].pm2_env.pm_id, function(err, ret) { 245 | should(err).be.null; 246 | pm2.describe(procs[1].pm2_env.pm_id, function(err, procs) { 247 | should(err).be.null; 248 | procs[0].pm2_env.status.should.eql('stopped'); 249 | done(); 250 | }); 251 | }); 252 | }); 253 | 254 | it('should stop process all', function(done) { 255 | pm2.stop('all', function(err, ret) { 256 | should(err).be.null; 257 | pm2.describe(procs[0].pm2_env.pm_id, function(err, procs) { 258 | should(err).be.null; 259 | procs[0].pm2_env.status.should.eql('stopped'); 260 | done(); 261 | }); 262 | }); 263 | }); 264 | }); 265 | 266 | 267 | 268 | describe('start OR restart', function() { 269 | before(function(done) { 270 | pm2.delete('all', function(err, ret) { 271 | pm2.list(function(err, ret) { 272 | should(err).be.null; 273 | ret.length.should.eql(0); 274 | done(); 275 | }); 276 | }); 277 | }); 278 | 279 | it('should start', function(done) { 280 | pm2._jsonStartOrAction('restart', process.cwd() + '/test/fixtures/all2.json', {}, function(err, data) { 281 | should(err).be.null; 282 | pm2.list(function(err, ret) { 283 | should(err).be.null; 284 | ret.length.should.eql(4); 285 | done(); 286 | }); 287 | }); 288 | }); 289 | 290 | it('should NOW restart action', function(done) { 291 | pm2._jsonStartOrAction('restart', process.cwd() + '/test/fixtures/all2.json', {}, function(err, data) { 292 | should(err).be.null; 293 | pm2.list(function(err, ret) { 294 | should(err).be.null; 295 | should(ret[0].pm2_env['NODE_ENV']).not.exist; 296 | ret.forEach(function(app) { 297 | app.pm2_env.restart_time.should.eql(1); 298 | }); 299 | setTimeout(function() { done(); }, 500); 300 | }); 301 | }); 302 | }); 303 | 304 | it('should reset status', function(done) { 305 | pm2.delete('all', function(err, ret) { 306 | done(); 307 | }); 308 | }); 309 | 310 | it('should start with specific environment variables depending on the env type', function(done) { 311 | pm2._jsonStartOrAction('restart', process.cwd() + '/test/fixtures/all2.json', { 312 | env : 'production' 313 | }, function(err, data) { 314 | should(err).be.null; 315 | pm2.list(function(err, ret) { 316 | should(err).be.null; 317 | ret[0].pm2_env['NODE_ENV'].should.eql('production'); 318 | ret[0].pm2_env['TOTO'].should.eql('heymoto'); 319 | done(); 320 | }); 321 | }); 322 | }); 323 | 324 | }); 325 | 326 | }); 327 | --------------------------------------------------------------------------------