diff --git a/CHANGELOG.md b/CHANGELOG.md index 3812e88d..2570cf2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,32 @@ +# 0.10.0 - PM2 Hellfire release + +- PM2 hearth code has been refactored and now it handles extreme scenario without any leak or bug +- PM2 restart refresh current environment variables #528 +- PM2 delete all more verbose +- PM2 reset reset restart numbers +- Auto update script at PM2 installation +- --watch enhanced to avoid zombie processes +- 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) +- PM2 respects strong unix standard process management +- Remove timestamps by default with pm2 logs +- Coffeescript not enabled by default anymore (enhance memory usage) +- PM2 Programmatic interface enhanced +- PM2 hearth refactor +- PM2 describe show node-args +- node_args for V8 options is now available via JSON declaration +- Watch system avoid ghost processes +- Memory leak fixes +- Better performance on interface +- Fix tests +- Enable PM2_NODE_OPTIONS and node-args for fork mode +- Dependencies updated +- Faster monitoring system +- AXM actions unification +- Socket errors handled +- Watchdog via Agent - restart automatically PM2 with previous processes in case of crash +- PM2_NODE_OPTIONS deprecation (use --node-args instead) + # 0.9.6 - 0.9.5 - 0.9.4 - Bash test auto exit when failure diff --git a/README.md b/README.md index 3a72211c..0e61c8c6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -![pm2](https://github.com/unitech/pm2/raw/master/pres/top-logo-wo.png) +![PM2](https://github.com/unitech/pm2/raw/master/pres/pm2-v2.png) -pm2 is a process manager for Node apps with a built-in load balancer. +PM2 is a process manager for Node apps with a built-in load balancer. ### Tech notes -pm2 is perfect for spreading your stateless Node.js code across all CPUs available on a server, for keeping all processes alive forever and 0s reload them. +PM2 is perfect for spreading your stateless Node.js code across all CPUs available on a server, for keeping all processes alive forever and 0s reload them. ### Main features @@ -12,11 +12,12 @@ pm2 is perfect for spreading your stateless Node.js code across all CPUs availab - Script daemonization - 0s downtime reload for Node apps - Generate SystemV/SystemD startup scripts (Ubuntu, Centos...) +- Set memory limit for process to restart - Pause unstable process (avoid infinite loop) - Restart on file change with `--watch` - Monitoring in console -Tested with Node v0.11, v0.10 (https://travis-ci.org/Unitech/pm2). +Tested with Node v0.11, v0.10 (https://travis-ci.org/Unitech/PM2). **The recommended Node.js version is v0.11.13** @@ -30,20 +31,21 @@ Works on Linux & MacOS. -Master: [![Build Status](https://api.travis-ci.org/Unitech/PM2.png?branch=master)](https://travis-ci.org/Unitech/pm2) +Master: [![Build Status](https://api.travis-ci.org/Unitech/PM2.png?branch=master)](https://travis-ci.org/Unitech/PM2) + +Development: [![Build Status](https://api.travis-ci.org/Unitech/PM2.png?branch=development)](https://travis-ci.org/Unitech/PM2) -Development: [![Build Status](https://api.travis-ci.org/Unitech/PM2.png?branch=development)](https://travis-ci.org/Unitech/pm2) ## Monitoring dashboard ![Dashboard](http://leapfrogui.com/controlfrog/img/cf-layout-1.png) -We're going to release a very nice product, a dashboard to monitor every part of your Node.js applications. Here are some links: +We are developing a top-notch product: a dashboard to monitor each part of your Node.js applications. Here are some links: - [Pitch + Survey](https://docs.google.com/forms/d/1FuCjIhrGg-ItxInq2nLreoe9GS-gZWJNkNWE0JJajw8/viewform) People who fill the survey will be eligible for free license - [Newsletter](http://signup.pm2.io/) Subscribe to be kept informed -Thanks in advance and we hope that you like pm2! +Thanks in advance and we hope that you like PM2! ------ @@ -56,18 +58,19 @@ Thanks in advance and we hope that you like pm2! - [Examples](#a3) - [Different ways to launch a process](#a667) - [Options](#a987) -- [How to update pm2 ?](#update-pm2) +- [How to update PM2 ?](#update-pm2) ### Features - [Transitional state of apps](#a4) - [Process listing](#a6) +- [Automatic restart process based on memory](#max-memory-restart) - [Monitoring CPU/Memory usage](#a7) - [Logs management](#a9) - [Clustering](#a5) - [Watch & Restart](#a890) - [Reloading without downtime](#a690) -- [Make pm2 restart on server reboot](#a8) +- [Make PM2 restart on server reboot](#a8) - [JSON app declaration](#a10) ### Deployment - ecosystem.json @@ -88,14 +91,14 @@ Thanks in advance and we hope that you like pm2! - [Configuration file](#a989) - [Enabling Harmony ES6](#a66) - [CoffeeScript](#a19) -- [Testing pm2 on your prod environment](#a149) +- [Testing PM2 on your prod environment](#a149) - [JSON app via pipe](#a96) ### Knowledge - [Stateless apps ?](#stateless-apps) - [Transitional state of apps](#a4) -- [Setup pm2 on server: tutorial](#a89) +- [Setup PM2 on server: tutorial](#a89) - [Logs and PID files](#a34) - [Execute any script: What is fork mode ?](#a23) @@ -113,15 +116,15 @@ Thanks in advance and we hope that you like pm2! ## Installation -The preferred Node version to run pm2, is the **0.11.10** +The preferred Node version to run PM2 is **0.11.10** -The latest pm2 stable version is installable via NPM: +The latest PM2 stable version is installable via NPM: ```bash $ npm install pm2@latest -g ``` -If the above fails: +If the above fails use: ```bash $ npm install git://github.com/Unitech/pm2#master -g @@ -179,6 +182,7 @@ $ pm2 delete all # Will remove all processes from pm2 list # Misc +$ pm2 reset # Reset meta data (restarted time...) $ pm2 updatePM2 # Update in memory pm2 $ pm2 ping # Ensure pm2 daemon has been launched $ pm2 sendSignal SIGUSR2 my-app # Send system signal to script @@ -226,7 +230,7 @@ $ pm2 start -x echo.rb $ pm2 start -x echo.pl ``` -The not javascript languages will have to be run in [fork mode](#a23). +Languages other than javascript have to be run in [fork mode](#a23). ## Options @@ -259,7 +263,7 @@ Options: ``` -## How to update pm2 +## How to update PM2 Install the latest pm2 version : @@ -267,7 +271,7 @@ Install the latest pm2 version : $ npm install pm2@latest -g ``` -Then update the in-memory pm2 : +Then update the in-memory PM2 : ```bash $ pm2 updatePM2 @@ -278,7 +282,7 @@ $ pm2 updatePM2 ## Transitional state of apps (important) -pm2 is a process manager, as said, pm2 can start, stop, restart and *delete* processes. +PM2 is a process manager. PM2 can start, stop, restart and *delete* processes. Start a process: @@ -293,7 +297,8 @@ Now let's say I need to stop the web-interface: $ pm2 stop web-interface ``` -As you can see **the process hasn't disappeared**. It is still there but now in `stopped` status. +As you can see **the process hasn't disappeared**. It's still there but in `stopped` status. + To restart it just do: @@ -301,8 +306,8 @@ To restart it just do: $ pm2 restart web-interface ``` -Now I want to **delete** the app from the pm2 process list. -To do that: +Now I want to **delete** the app from the PM2 process list. +To do so: ```bash $ pm2 delete web-interface @@ -327,6 +332,25 @@ To get more details about a specific process: $ pm2 describe 0 ``` + +## Automatic restart process based on memory + +Value passed is in megaoctets. Internally it uses the V8 flag `--max-old-space-size=MEM` to make a process exit when memory exceed a certain amount of RAM used. + +CLI: +```bash +$ pm2 start big-array.js --max-memory-restart 20 +``` + +JSON: +```json +{ + "name" : "max_mem", + "script" : "big-array.js", + "max_memory_restart" : "20" +} +``` + ## Monitoring CPU/Memory usage @@ -363,7 +387,7 @@ $ pm2 ilogs ### Reloading all logs (SIGUSR2/Logrotate) -To reload all logs, you can send `SIGUSR2` to the pm2 process. +To reload all logs, you can send `SIGUSR2` to the PM2 process. You can also reload all logs via the command line with: @@ -389,6 +413,7 @@ $ pm2 start app.js --name "API" -i max If your app is well-designed (**stateless**) you'll be able to **process many more queries**. + Important concepts to make a Node.js app stateless: - Sessions must not be stored in memory but shared via a database (Redis, Mongo, whatever) @@ -448,13 +473,13 @@ Then use the command: $ pm2 gracefulReload [all|name] ``` -When pm2 starts a new process to replace an old one, it will wait for the new process to begin listening to a connection before sending the shutdown message to the old one. If a script does not need to listen to a connection, it can manually tell pm2 that the process has started up by calling `process.send('online')`. +When PM2 starts a new process to replace an old one, it will wait for the new process to begin listening to a connection before sending the shutdown message to the old one. If a script does not need to listen to a connection, it can manually tell PM2 that the process has started up by calling `process.send('online')`. ## Startup script -pm2 has the amazing ability to **generate startup scripts and configure them**. -pm2 is also smart enough to **save all your process list** and to **bring back all your processes on restart**. +PM2 has the amazing ability to **generate startup scripts and configure them**. +PM2 is also smart enough to **save all your process list** and to **bring back all your processes on restart**. ```bash $ pm2 startup [ubuntu|centos|gentoo|systemd] @@ -466,7 +491,7 @@ Once you have started the apps and want to keep them on server reboot do: $ pm2 save ``` -**Warning** It's tricky to make this feature work generically, so once pm2 has setup your startup script, reboot your server to make sure that pm2 has launched your apps! +**Warning** It's tricky to make this feature work generically, so once PM2 has setup your startup script, reboot your server to make sure that PM2 has launched your apps! ### More information @@ -495,7 +520,7 @@ $ pm2 startup ubuntu -u www ### Related commands -Dump all processes status and environment managed by pm2: +Dump all processes status and environment managed by PM2: ```bash $ pm2 dump ``` @@ -509,7 +534,7 @@ $ pm2 resurrect ## Watch & Restart -pm2 can automatically restart your app when a file changes in the current directory or its subdirectories: +PM2 can automatically restart your app when a file changes in the current directory or its subdirectories: ```bash $ pm2 start app.js --watch @@ -528,7 +553,6 @@ To watch specifics paths, please use a JSON app declaration, `watch` can take a "watch": ["server", "client"], "ignoreWatch" : ["node_modules", "client/img"] } - ``` @@ -545,6 +569,7 @@ You can define parameters for your apps in `processes.json`: "log_date_format" : "YYYY-MM-DD HH:mm Z", "ignoreWatch" : ["[\\/\\\\]\\./", "node_modules"], "watch" : "true", + "node_args" : "--harmony", "cwd" : "/this/is/a/path/to/start/script", "env": { "NODE_ENV": "production", @@ -613,6 +638,7 @@ Note that if you execute `pm2 start node-app-2` again, it will spawn an addition "cwd" : "/srv/node-app/current", "args" : "['--toto=heya coco', '-d', '1']", "script" : "bin/app.js", + "node_args" : "--harmony", "log_date_format" : "YYYY-MM-DD HH:mm Z", "error_file" : "/var/log/node-app/node-app.stderr.log", "out_file" : "log/node-app.stdout.log", @@ -891,7 +917,7 @@ pm2.connect(function(err) { # Special features -Launching pm2 without daemonizing itself: +Launching PM2 without daemonizing itself: ```bash $ pm2 start app.js --no-daemon @@ -915,7 +941,6 @@ PM2_BIND_ADDR PM2_API_PORT PM2_GRACEFUL_TIMEOUT PM2_MODIFY_REQUIRE -PM2_NODE_OPTIONS ``` @@ -928,36 +953,21 @@ $ pm2 web ## Enabling Harmony ES6 -### Enable by default for all processes - -You can enable Harmony ES6 by setting `PM2_NODE_OPTIONS='--harmony'` environment variable option when you start pm2 (pm2 should not be already daemonized). - -To pass this option by default, you can edit `~/.pm2/custom_options.sh` and add: - -```bash -export PM2_NODE_OPTIONS='--harmony' -``` - -Then: - -```bash -$ pm2 dump -$ pm2 exit -$ pm2 resurrect -``` - -If ES6 has been enabled you should see this message at the beginning of each pm2 command: - -``` -● ES6 mode -``` - -### Enable for specific processes - +The `--node-args` option permit to launch script with V8 flags, so to enable harmony for a process just do this: ```bash $ pm2 start my_app.js --node-args="--harmony" ``` +And with JSON declaration: + +```bash +[{ + "name" : "ES6", + "script" : "es6.js", + "node_args" : "--harmony" +}] +``` + ## CoffeeScript @@ -991,7 +1001,7 @@ We recommend following the 12 factor convention : [http://12factor.net/](http:// ## Log and PID files -By default, logs (error and output), pid files, dumps, and pm2 logs are located in `~/.pm2/`: +By default, logs (error and output), pid files, dumps, and PM2 logs are located in `~/.pm2/`: ``` .pm2/ @@ -1006,7 +1016,7 @@ By default, logs (error and output), pid files, dumps, and pm2 logs are located ## Execute any script: What is fork mode? -The default mode of pm2 consists of wrapping the code of your node application into the Node Cluster module. It's called the **cluster mode**. +The default mode of PM2 consists of wrapping the code of your node application into the Node Cluster module. It's called the **cluster mode**. There is also a more classical way to execute your app, like node-forever does, called the **fork mode**. @@ -1060,9 +1070,9 @@ echo $my_json | pm2 start - ``` -## Is my production server ready for pm2? +## Is my production server ready for PM2? -Just try the tests before using pm2 on your production server +Just try the tests before using PM2 on your production server ```bash $ git clone https://github.com/Unitech/pm2.git @@ -1086,7 +1096,7 @@ $ nvm alias default v0.11.10 ## Contributing/Development mode -To hack pm2, it's pretty simple: +To hack PM2, it's very simple: ```bash $ pm2 kill # kill the current pm2 @@ -1095,9 +1105,9 @@ $ cd pm2/ $ DEBUG=* PM2_DEBUG=true ./bin/pm2 --no-daemon ``` -Each time you edit the code, be sure to kill and restart pm2 to make changes taking effect. +Each time you edit the code, be sure to kill and restart PM2 to make changes taking effect. -## Install pm2 development +## Install PM2 development ```bash $ npm install git://github.com/Unitech/pm2#development -g @@ -1106,7 +1116,7 @@ $ npm install git://github.com/Unitech/pm2#development -g ## Known bugs and workarounds -First, install the lastest pm2 version: +First, install the lastest PM2 version: ```bash $ npm install -g pm2@latest @@ -1114,7 +1124,7 @@ $ npm install -g pm2@latest ### Node 0.10.x doesn't free the script port when stopped. It's due to the Node.js cluster module. So if you feel that this problem is important for your use case, use the [fork mode](#execute-any-script-what-is-fork-mode-) instead. -By using the fork mode you will lose core features of pm2 like the automatic clusterization of your code over all CPUs available and the 0s reload. +By using the fork mode you will lose core features of PM2 like the automatic clusterization of your code over all CPUs available and the 0s reload. ``` $ pm2 start index.js -x # start my app in fork mode diff --git a/bin/pm2 b/bin/pm2 index c25682a3..98c50f62 100755 --- a/bin/pm2 +++ b/bin/pm2 @@ -10,11 +10,41 @@ var util = require('util'); var cronJob = require('cron').CronJob; var chalk = require('chalk'); +var debug = require('debug')('pm2:cli'); var Satan = require('../lib/Satan'); var CLI = require('../lib/CLI'); var cst = require('../constants.js'); var pkg = require('../package.json'); +// +// Init +// +(function init() { + var exist = fs.existsSync(cst.DEFAULT_FILE_PATH); + + if (!exist) { + console.log('Initializing folder for pm2 on %s', cst.DEFAULT_FILE_PATH); + fs.mkdirSync(cst.DEFAULT_FILE_PATH); + fs.mkdirSync(cst.DEFAULT_LOG_PATH); + fs.mkdirSync(cst.DEFAULT_PID_PATH); + + /** + * Create configuration file if not present + */ + fs.exists(cst.PM2_CONF_FILE, function(exist) { + if (!exist) { + console.log('Creating PM2 configuration file in %s', cst.PM2_CONF_FILE); + fs + .createReadStream(path.join(__dirname, cst.SAMPLE_CONF_FILE)) + .pipe(fs.createWriteStream(cst.PM2_CONF_FILE)); + } + + }); + } + +})(); + + commander.version(pkg.version) .option('-v --version', 'get version') .option('-s --silent', 'hide all messages', false) @@ -25,6 +55,7 @@ commander.version(pkg.version) .option('-o --output ', 'specify out log file') .option('-e --error ', 'specify error log file') .option('-p --pid ', 'specify pid file') + .option('--max-memory-restart ', 'specify max memory amount used to autorestart (in megaoctets)') .option('--env ', 'specify environment to get specific env variables (for JSON declaration)') .option('-x --execute-command', 'execute a program using fork system') .option('-u --user ', 'define user when generating startup script') @@ -36,8 +67,8 @@ commander.version(pkg.version) .option('--merge-logs', 'merge logs from different instances but keep error and out separated') .option('--watch', 'watch application folder for changes') .option('--node-args ', "space delimited arguments to pass to node in cluster mode - e.g. --node-args=\"--debug=7001 --trace-deprecation\"", - function(val) { - return val.split(' '); + function(val) { + return val.split(' '); }) .option('--run-as-user ', 'The user or uid to run a managed process as') .option('--run-as-group ', 'The group or gid to run a managed process as') @@ -121,7 +152,7 @@ function failOnUnknown(fn) { // commander.command('start ') .option('--watch', 'Watch folder for changes') - .description(' start and daemonize an app') + .description('start and daemonize an app') .action(function(cmd) { if (cmd == "-") { process.stdin.resume(); @@ -137,13 +168,13 @@ commander.command('start ') }); commander.command('deploy ') - .description(' deploy your json') + .description('deploy your json') .action(function(cmd) { CLI.deploy(cmd, commander); }); commander.command('startOrRestart ') - .description(' start or restart JSON file') + .description('start or restart JSON file') .action(function(file) { CLI._jsonStartOrAction('restart', file, commander); }); @@ -153,7 +184,7 @@ commander.command('startOrRestart ') // commander.command('stop ') .option('--watch', 'Stop watching folder for changes') - .description(' stop a process (to start it again, do pm2 restart )') + .description('stop a process (to start it again, do pm2 restart )') .action(function(param) { CLI.stop(param); }); @@ -163,7 +194,7 @@ commander.command('stop ') // commander.command('restart ') .option('--watch', 'Toggle watching folder for changes') - .description(' restart a process') + .description('restart a process') .action(function(param) { CLI.restart(param); }); @@ -172,7 +203,7 @@ commander.command('restart ') // Reload process(es) // commander.command('reload ') - .description(' reload processes (note that its for app using HTTP/HTTPS)') + .description('reload processes (note that its for app using HTTP/HTTPS)') .action(function(pm2_id) { CLI.reload(pm2_id); }); @@ -181,13 +212,13 @@ commander.command('reload ') // Reload process(es) // commander.command('gracefulReload ') -.description(' gracefully reload a process. Send a "shutdown" message to close all connections.') + .description('gracefully reload a process. Send a "shutdown" message to close all connections.') .action(function(pm2_id) { CLI.gracefulReload(pm2_id); }); // commander.command('gracefulStop ') -// .description(' gracefully reload a process. Send a "shutdown" message to close all connections.') +// .description('gracefully reload a process. Send a "shutdown" message to close all connections.') // .action(function(pm2_id) { // CLI.gracefulStop(pm2_id); // }); @@ -196,7 +227,7 @@ commander.command('gracefulReload ') // Stop and delete a process by name from database // commander.command('delete ') - .description(' stop and delete a process from pm2 process list') + .description('stop and delete a process from pm2 process list') .action(function(name) { if (name == "-") { process.stdin.resume(); @@ -213,7 +244,7 @@ commander.command('delete ') // Send system signal to process // commander.command('sendSignal ') - .description(' send a system signal to the target process') + .description('send a system signal to the target process') .action(function(signal, pm2_id) { if (isNaN(parseInt(pm2_id))) { console.log(cst.PREFIX_MSG + 'Sending signal to process name ' + pm2_id); @@ -228,16 +259,16 @@ commander.command('sendSignal ') // Stop and delete a process by name from database // commander.command('ping') - .description(' ping pm2 daemon - if not up it will launch it') + .description('ping pm2 daemon - if not up it will launch it') .action(function() { - CLI.ping(); - }); + CLI.ping(); + }); commander.command('updatePM2') - .description(' updatePM2 after a npm update') + .description('updatePM2 after a npm update') .action(function() { - CLI.updatePM2() - }); + CLI.updatePM2() + }); // // Interact @@ -245,20 +276,20 @@ commander.command('updatePM2') commander.command('interact [secret_key] [public_key] [machine_name]') .description('launch agent to interact with Keymetrics') .action(function(secret, public, machine) { - CLI.interact(secret, public, machine); - }); + CLI.interact(secret, public, machine); + }); commander.command('killInteract') .description('stop agent') .action(function() { - CLI.killInteract() - }); + CLI.killInteract() + }); commander.command('infoInteract') .description('get information about agent') .action(function() { - CLI.infoInteract(); - }); + CLI.infoInteract(); + }); // @@ -323,17 +354,23 @@ commander.command('ecosystem') CLI.generateSample(name); }); +commander.command('reset ') + .description('reset counters for process') + .action(function(proc_id) { + CLI.resetMetaProcess(proc_id); + }); + commander.command('describe ') .description('describe all parameters of a process id') .action(function(proc_id) { - CLI.describe(proc_id); - }); + CLI.describe(proc_id); + }); commander.command('desc ') .description('describe all parameters of a process id') .action(function(proc_id) { - CLI.describe(proc_id); - }); + CLI.describe(proc_id); + }); // // List command @@ -341,34 +378,34 @@ commander.command('desc ') commander.command('list') .description('list all processes') .action(function() { - CLI.list() - }); + CLI.list() + }); commander.command('ls') .description('(alias) list all processes') .action(function() { - CLI.list() - }); + CLI.list() + }); commander.command('l') .description('(alias) list all processes') .action(function() { - CLI.list() - }); + CLI.list() + }); commander.command('status') .description('(alias) list all processes') .action(function() { - CLI.list() - }); + CLI.list() + }); // List in raw json commander.command('jlist') .description('list all processes in JSON format') .action(function() { - CLI.jlist() - }); + CLI.jlist() + }); // List in prettified Json commander.command('prettylist') @@ -383,14 +420,14 @@ commander.command('prettylist') commander.command('monit') .description('launch termcaps monitoring') .action(function() { - CLI.monit() - }); + CLI.monit() + }); commander.command('m') .description('(alias) launch termcaps monitoring') .action(function() { - CLI.monit() - }); + CLI.monit() + }); // @@ -464,53 +501,15 @@ if (process.argv.length == 2) { // in file Satan.js, method Satan.launchRPC // process.once('satan:client:ready', function() { - CLI.getVersion(function(err, remote_version) { - if (!err && (pkg.version != remote_version)) { - console.log(''); - console.log(chalk.red.bold('>>>> In-memory PM2 is out-of-date, do:\n>>>> $ pm2 updatePM2')); - console.log('In memory PM2 version:', chalk.blue.bold(remote_version)); - console.log('Local PM2 version:', chalk.blue.bold(pkg.version)); - console.log(''); - } - commander.parse(process.argv); - }); + debug('Got message from Satan as succesfully connected to PM2, now parsing arguments'); + CLI.getVersion(function(err, remote_version) { + if (!err && (pkg.version != remote_version)) { + console.log(''); + console.log(chalk.red.bold('>>>> In-memory PM2 is out-of-date, do:\n>>>> $ pm2 updatePM2')); + console.log('In memory PM2 version:', chalk.blue.bold(remote_version)); + console.log('Local PM2 version:', chalk.blue.bold(pkg.version)); + console.log(''); + } + commander.parse(process.argv); + }); }); - -// -// Init -// -(function init() { - fs.exists(cst.DEFAULT_FILE_PATH, function(exist) { - if (!exist) { - console.log('Initializing folder for pm2 on %s', cst.DEFAULT_FILE_PATH); - fs.mkdirSync(cst.DEFAULT_FILE_PATH); - fs.mkdirSync(cst.DEFAULT_LOG_PATH); - fs.mkdirSync(cst.DEFAULT_PID_PATH); - } - }); - - /** - * Create configuration file if not present - */ - fs.exists(cst.PM2_CONF_FILE, function(exist) { - if (!exist) { - console.log('Creating PM2 configuration file in %s', cst.PM2_CONF_FILE); - fs - .createReadStream(path.join(__dirname, cst.SAMPLE_CONF_FILE)) - .pipe(fs.createWriteStream(cst.PM2_CONF_FILE)); - } - }); -})(); - -(function testHarmony() { - // - // Harmony test - // - try { - var assert = require('assert') - , s = new Set(); - s.add('a'); - assert.ok(s.has('a')); - console.log('● ES6 mode'.green); - } catch(e) {} -})(); diff --git a/constants.js b/constants.js index 847f6bab..64600c69 100644 --- a/constants.js +++ b/constants.js @@ -15,13 +15,13 @@ var DEFAULT_FILE_PATH = p.resolve(HOME, '.pm2'); var default_conf = { DEFAULT_FILE_PATH : DEFAULT_FILE_PATH, - PM2_LOG_FILE_PATH : p.join(process.env.PM2_LOG_DIR || p.resolve(process.env.HOME, '.pm2'), 'pm2.log'), - PM2_PID_FILE_PATH : p.join(process.env.PM2_PID_DIR || p.resolve(process.env.HOME, '.pm2'), 'pm2.pid'), + PM2_LOG_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'pm2.log'), + PM2_PID_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'pm2.pid'), DEFAULT_PID_PATH : p.join(DEFAULT_FILE_PATH, 'pids'), DEFAULT_LOG_PATH : p.join(DEFAULT_FILE_PATH, 'logs'), DUMP_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'dump.pm2'), - SAMPLE_CONF_FILE : '../lib/custom_options.sh', + SAMPLE_CONF_FILE : p.join('..', 'lib', 'custom_options.sh'), PM2_CONF_FILE : p.join(DEFAULT_FILE_PATH, 'custom_options.sh'), DAEMON_BIND_HOST : process.env.PM2_BIND_ADDR || 'localhost', @@ -36,8 +36,8 @@ var default_conf = { DEBUG : process.env.PM2_DEBUG || false, WEB_INTERFACE : parseInt(process.env.PM2_API_PORT) || 9615, MODIFY_REQUIRE : process.env.PM2_MODIFY_REQUIRE || false, - PREFIX_MSG : '\x1B[32mPM2 \x1B[39m', - PREFIX_MSG_ERR : '\x1B[31mPM2 [ERROR] \x1B[39m', + PREFIX_MSG : '\x1B[32m[PM2] \x1B[39m', + PREFIX_MSG_ERR : '\x1B[31m[PM2] [ERROR] \x1B[39m', SAMPLE_FILE_PATH : '../lib/sample.json', CENTOS_STARTUP_SCRIPT : '../lib/scripts/pm2-init-centos.sh', @@ -62,8 +62,8 @@ var default_conf = { INTERACTION_CONF : p.join(DEFAULT_FILE_PATH, 'agent.json'), SEND_INTERVAL : 1000, - INTERACTOR_LOG_FILE_PATH : p.join(p.resolve(process.env.HOME, '.pm2'), 'agent.log'), - INTERACTOR_PID_PATH : p.join(p.resolve(process.env.HOME, '.pm2'), 'agent.pid'), + INTERACTOR_LOG_FILE_PATH : p.join(DEFAULT_FILE_PATH, 'agent.log'), + INTERACTOR_PID_PATH : p.join(DEFAULT_FILE_PATH, 'agent.pid'), INTERACTOR_RPC_PORT : parseInt(process.env.PM2_INTERACTOR_PORT) || 6668 }; diff --git a/examples/auto-save.js b/examples/auto-save.js index 79de09f5..e69de29b 100644 --- a/examples/auto-save.js +++ b/examples/auto-save.js @@ -1,30 +0,0 @@ - - -// Expose action -// And "touch" file every 1.4s to restart the file - -var axm = require('axm'); - -function makeid() { - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for( var i=0; i < 5; i++ ) - text += possible.charAt(Math.floor(Math.random() * possible.length)); - - return text; -} - - -axm.action('cmd:' + makeid(), {comment : 'Refresh main database'}, function(reply) { - console.log('Refreshing'); - reply({success : true}); - }); - -setTimeout(function() { - var fs = require('fs'); - - - var a = fs.readFileSync(__filename); - fs.writeFileSync(__filename, a); -}, 1400); diff --git a/examples/custom_action.js b/examples/custom_action.js index 5daf95c1..104c6001 100644 --- a/examples/custom_action.js +++ b/examples/custom_action.js @@ -2,7 +2,22 @@ var axm = require('axm'); + + + axm.action('refresh:db2', {comment : 'Refresh main database'}, function(reply) { + + axm.emit('user:register', { + user : 'Alex registered', + email : 'thorustor@gmail.com' + }); + + reply({success : true}); +}); + + + +axm.action('hello', {comment : 'Refresh main database'}, function(reply) { console.log('Refreshing'); reply({success : true}); }); diff --git a/examples/echo.js b/examples/echo.js old mode 100644 new mode 100755 diff --git a/examples/keymetrics-load.js b/examples/keymetrics-load.js new file mode 100644 index 00000000..83bae41a --- /dev/null +++ b/examples/keymetrics-load.js @@ -0,0 +1,28 @@ + + +var pm2 = require('..'); + +pm2.connect(function() { + pm2.delete('all', function() { + pm2.start('examples/human_event.js', function() { + pm2.start('examples/child.js', {instances:2},function() { + pm2.start('examples/kill-not-so-fast.js', { + instances:10, + minUptime: 0, + maxRestarts : 0 + }, function() { + pm2.start('examples/auto-save.js', {execMode : 'fork', watch:true, force : true}, function() { + pm2.start('examples/custom_action_with_params.js', function() { + //pm2.start('examples/auto-bench.js', {instances : 'max'}, function() { + pm2.start('examples/throw.js', {name:'auto-throw'}, function() { + pm2.disconnect(function() { process.exit(1); }); + }); + }); + + }); + }); + }); + + }); + }); +}); diff --git a/examples/keymetrics-test.js b/examples/keymetrics-test.js index d5e623cf..c5244dcd 100644 --- a/examples/keymetrics-test.js +++ b/examples/keymetrics-test.js @@ -4,20 +4,24 @@ var pm2 = require('..'); pm2.connect(function() { pm2.delete('all', function() { pm2.start('examples/human_event.js', function() { - pm2.start('examples/child.js', {instances:2},function() { - pm2.start('examples/custom_action.js', function() { - pm2.start('examples/custom_action_with_params.js', function() { - pm2.start('examples/auto-save.js', {watch : true, name :'auto-save-modify'}, function() { - pm2.start('examples/http-trace.js', {name:'trace'}, function() { - //pm2.start('examples/auto-bench.js', {instances : 'max'}, function() { - pm2.start('examples/throw.js', {name:'auto-throw'}, function() { - pm2.disconnect(function() { process.exit(1); }); + pm2.start('examples/child.js', {instances:2},function() { + pm2.start('examples/custom_action.js', function() { + pm2.start('examples/custom_action.js', {execMode : 'fork', force : true}, function() { + pm2.start('examples/auto-save.js', {execMode : 'fork', watch:true, force : true}, function() { + pm2.start('examples/custom_action_with_params.js', function() { + pm2.start('examples/auto-save.js', {watch : true,force:true, name :'auto-save-modify'}, function() { + pm2.start('examples/http-trace.js', {name:'trace'}, function() { + //pm2.start('examples/auto-bench.js', {instances : 'max'}, function() { + pm2.start('examples/throw.js', {name:'auto-throw'}, function() { + pm2.disconnect(function() { process.exit(1); }); + }); + //}); }); - //}); + }); + }); }); }); }); - }); }); }); }); diff --git a/examples/kill-not-so-fast.js b/examples/kill-not-so-fast.js new file mode 100644 index 00000000..921b0517 --- /dev/null +++ b/examples/kill-not-so-fast.js @@ -0,0 +1,7 @@ + +console.log('start'); + +setTimeout(function() { + console.log('exit'); + throw new Error('Exitasdsadasdsda unacepted !!'); +}, 300); diff --git a/lib/CLI.js b/lib/CLI.js index 15ef31cc..0f849bc9 100644 --- a/lib/CLI.js +++ b/lib/CLI.js @@ -24,6 +24,8 @@ var exitCli = Common.exitCli; var printError = Common.printError; var printOut = Common.printOut; +var pm2 = require('..'); + /** * Method to start a script * @method startFile @@ -41,17 +43,22 @@ CLI.start = function(script, opts, cb) { opts = {}; } - if (opts.nodeArgs) + if (opts.nodeArgs) { //maintain backwards compat for space delimited string args if (Array.isArray(opts.nodeArgs)){ - appConf['nodeArgs'] = opts.nodeArgs; + appConf['node_args'] = opts.nodeArgs; } else { - appConf['nodeArgs'] = opts.nodeArgs.split(' '); + appConf['node_args'] = opts.nodeArgs.split(' '); } + } else { + appConf.node_args = []; + } if (opts.scriptArgs) appConf['args'] = JSON.stringify(opts.scriptArgs); if (opts.name) appConf['name'] = opts.name; + if (opts.maxMemoryRestart) + appConf.max_memory_restart = opts.maxMemoryRestart; if (opts.instances) appConf['instances'] = opts.instances; if (opts.error) @@ -72,6 +79,10 @@ CLI.start = function(script, opts, cb) { appConf['run_as_group'] = opts.runAsGroup; if (opts.logDateFormat) appConf['log_date_format'] = opts.logDateFormat; + if (typeof(opts.minUptime) !== 'undefined') + appConf['min_uptime'] = opts.minUptime; + if (typeof(opts.maxRestarts) !== 'undefined') + appConf['max_restarts'] = opts.maxRestarts; if (opts.executeCommand) { appConf['exec_mode'] = 'fork_mode'; @@ -89,9 +100,16 @@ CLI.start = function(script, opts, cb) { appConf['exec_interpreter'] = 'node'; } - // if (appConf['exec_mode'] == 'cluster_mode' && process.version.match(/0.10/)) { - // printOut(cst.PREFIX_MSG_ERR + ' [Warning], you\'re using the 0.10.x node version, it\'s prefered that you switch to fork mode by adding the -x parameter.'); - // } + if (opts.execMode) { + appConf['exec_mode'] = opts.execMode; + } + + if (appConf['exec_mode'] == 'cluster_mode' && + process.version.match(/0.10/) && + (parseInt(appConf.instances) == 1 || !appConf.instances)) { + //appConf['exec_mode'] = 'fork_mode'; + printOut('\x1B[31m[PM2] [WARNING] \x1B[39m you\'re using the 0.10.x node version, it\'s prefered that you switch to fork mode by adding the -x parameter. 0.11.x can use cluster_mode (default mode) efficiently.'); + } // Script arguments @@ -195,7 +213,7 @@ CLI.deploy = function(file, commands, cb) { Deploy.deployForEnv(json_conf.deploy, env, args, function(err, data) { if (err) { - printError(err); + printError('Deploy failed'); return cb ? cb(err) : exitCli(cst.ERROR_EXIT); } printOut('--> Success'); @@ -235,30 +253,45 @@ CLI.actionFromJson = function(action, file, jsonVia, cb) { if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array - async.eachLimit(appConf, cst.CONCURRENT_ACTIONS, function(proc, next) { - var name; + async.eachLimit(appConf, cst.CONCURRENT_ACTIONS, function(proc, next1) { + var name = ''; + var new_env = proc.env ? proc.env : {}; if (!proc.name) name = p.basename(proc.script); else name = proc.name; - Satan.executeRemote(action, name, function(err, list) { - if (err) - console.error(err); - printOut(cst.PREFIX_MSG + 'Stopping process by name ' + name); - next(); + Common.getProcessIdByName(name, function(err, ids) { + if (err) { + printError(err); + return next1(); + } + if (!ids) return next1(); + + async.eachLimit(ids, 1, function(id, next2) { + var opts; + + if (action == 'restartProcessId') + opts = { id : id, env : new_env }; + else + opts = id; + + Satan.executeRemote(action, opts, function(err, res) { + if (err) { + printError(err); + return next2(); + } + printOut(cst.PREFIX_MSG + 'Process ' + id + ' restarted'); + return next2(); + }); + }, function(err) { + return next1(null, {success:true}); + }); }); - }, function(err) { - if (err) { - printOut(err); - if (cb) cb(err); - else return exitCli(cst.ERROR_EXIT); - } - if (cb) return cb(null, {success:true}); - else return setTimeout(speedList, 800); + else return setTimeout(speedList, 100); }); }; @@ -458,6 +491,7 @@ CLI.startup = function(platform, opts, cb) { * @param {string} machine_name */ CLI.interact = function(secret_key, public_key, machine_name, cb) { + debug('Launching interact'); InteractorDaemonizer.launchAndInteract({ secret_key : secret_key || null, public_key : public_key || null, @@ -476,22 +510,8 @@ CLI.interact = function(secret_key, public_key, machine_name, cb) { * @method killInteract */ CLI.killInteract = function(cb) { - InteractorDaemonizer.ping(function(online) { - if (!online) { - if (!cb) printError('Interactor not launched'); - return cb ? cb({msg:'Interactor not launched'}) : exitCli(cst.SUCCESS_EXIT); - } - InteractorDaemonizer.launchRPC(function() { - InteractorDaemonizer.rpc.kill(function(err) { - if (err) { - if (!cb) printError(err); - return cb ? cb({msg : err}) : exitCli(cst.ERROR_EXIT); - } - if (!cb) printOut('Interactor successfully killed'); - return cb ? cb(null, {msg : 'killed'}) : exitCli(cst.SUCCESS_EXIT); - }); - }); - return false; + InteractorDaemonizer.killDaemon(function(err) { + return cb ? cb({msg:'Interactor not launched'}) : exitCli(cst.SUCCESS_EXIT); }); }; @@ -525,6 +545,46 @@ CLI.ping = function(cb) { }); }; +/** + * Reset meta data + * @method resetMetaProcess + */ +CLI.resetMetaProcess = function(process_name, cb) { + function processIds(ids, cb) { + async.eachLimit(ids, 4, function(id, next) { + Satan.executeRemote('resetMetaProcessId', id, function(err, res) { + if (err) console.error(err); + printOut(cst.PREFIX_MSG + 'Reseting meta for process id %d', id); + return next(); + }); + }, function(err) { + if (err) return cb(new Error(err)); + return cb ? cb(null, {success:true}) : speedList(); + }); + }; + + if (process_name == 'all') { + Common.getAllProcessId(function(err, ids) { + if (err) { + printError(err); + return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); + } + return processIds(ids, cb); + }); + } + else if (isNaN(parseInt(process_name))) { + Common.getProcessIdByName(process_name, function(err, ids) { + if (err) { + printError(err); + return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); + } + return processIds(ids, cb); + }); + } else { + processIds([process_name], cb); + } +}; + /** * Resurrect processes * @method resurrect @@ -564,29 +624,33 @@ CLI.resurrect = function(cb) { CLI.updatePM2 = function(cb) { printOut('Be sure to haave the latest version by doing `npm install pm2@latest -g` before doing this procedure.'); - InteractorDaemonizer.update(function() { + // Kill Daemon and disconnect RPC + InteractorDaemonizer.killDaemon(function() { + + // Dump PM2 processes CLI.dump(function(err) { - printOut(cst.PREFIX_MSG + '--- dumped'); + debug('Dumping successfull', err); + CLI.killDaemon(function() { + debug('Daemon killed'); - CLI.killDaemon(function(err) { - printOut(cst.PREFIX_MSG + '--- killed'); - Satan.launchDaemon(function(err, child) { + Satan.launchDaemon(function(err, child) { + Satan.launchRPC(function() { - printOut(cst.PREFIX_MSG + '--- resurrected'); - if (err) { - printError(err); - return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); - } - CLI.resurrect(function() { - printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated')); - return cb ? cb(null, {success:true}) : speedList(); + CLI.resurrect(function() { + printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated')); + return cb ? cb(null, {success:true}) : speedList(); + }); }); - return false; + + }); }); }); + }); + + return false; }; /** @@ -632,11 +696,12 @@ CLI.dump = function(cb) { * @return */ CLI.web = function(cb) { - Satan.executeRemote('prepare', resolvePaths({ - script : p.resolve(p.dirname(module.filename), './HttpInterface.js'), - name : 'Pm2Http' + cst.WEB_INTERFACE, - exec_mode : 'fork_mode' - }), function(err, proc) { + var filepath = p.resolve(p.dirname(module.filename), 'HttpInterface.js'); + + CLI.start(filepath, { + name : 'pm2-http-interface', + execMode : 'fork_mode' + }, function(err, proc) { if (err) { printError(cst.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err); return cb ? cb({msg:err}) : speedList(); @@ -854,14 +919,14 @@ CLI.restart = function(process_name, cb) { process.stdin.setEncoding('utf8'); process.stdin.on('data', function (param) { process.stdin.pause(); - CLI.actionFromJson('restartProcessName', param, 'pipe', cb); + CLI.actionFromJson('restartProcessId', param, 'pipe', cb); }); } else if (process_name.indexOf('.json') > 0) - CLI.actionFromJson('restartProcessName', process_name, 'file', cb); + CLI.actionFromJson('restartProcessId', process_name, 'file', cb); else if (process_name == 'all') CLI._restartAll(cb); else if (isNaN(parseInt(process_name))) { - printError('Restarting process by name ' + process_name); + printOut('Restarting process by name ' + process_name); CLI._restartProcessByName(process_name, cb); } else { printOut('Restarting process by id ' + process_name); @@ -875,14 +940,31 @@ CLI.restart = function(process_name, cb) { * @param {} pm2_name * @return */ -CLI._restartProcessByName = function(pm2_name, cb) { - Satan.executeRemote('restartProcessName', pm2_name, function(err, list) { +CLI._restartProcessByName = function(process_name, cb) { + Common.getProcessIdByName(process_name, function(err, ids) { if (err) { printError(err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } - printOut(cst.PREFIX_MSG + 'Process ' + pm2_name + ' restarted'); - return cb ? cb(null, list) : speedList(); + if (!ids || ids.length === 0) { + return cb ? cb({msg:'Unknown process name'}) : exitCli(cst.ERROR_EXIT); + } + async.eachLimit(ids, 1, function(id, next) { + Satan.executeRemote('restartProcessId', { + id: id, + env : process.env + }, function(err, res) { + if (err) { + printError(err); + return next(); + } + printOut(cst.PREFIX_MSG + 'Process ' + id + ' restarted'); + return next(); + }); + }, function(err) { + if (err) return cb(new Error(err)); + return cb ? cb(null, ids) : speedList(); + }); }); }; @@ -893,12 +975,15 @@ CLI._restartProcessByName = function(pm2_name, cb) { * @return */ CLI._restartProcessById = function(pm2_id, cb) { - Satan.executeRemote('restartProcessId', pm2_id, function(err, res) { + Satan.executeRemote('restartProcessId', { + id: pm2_id, + env : process.env + }, function(err, res) { if (err) { printError(err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } - printOut(cst.PREFIX_MSG + 'Process ' + pm2_id + ' restarted'); + printOut(cst.PREFIX_MSG + 'Process id ' + pm2_id + ' restarted'); return cb ? cb(null, res) : speedList(); }); }; @@ -915,27 +1000,29 @@ CLI._restartAll = function(cb) { return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } if (list && list.length === 0) { - printError('No process launched'); + printError(cst.PREFIX_MSG + 'No process launched'); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } - (function rec(processes) { var proc = processes[0]; if (proc == null) { - printOut(cst.PREFIX_MSG + 'Process restarted...'); + printOut(cst.PREFIX_MSG + 'All processes has been restarted'); return cb ? cb(null, processes) : setTimeout(speedList, 1000); } - Satan.executeRemote('restartProcessId', proc.pm2_env.pm_id, function(err, res) { - if (err) { - printError(err); - return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); - } - printOut(cst.PREFIX_MSG + 'Process ' + proc.pm2_env.name + ' restarted'); - processes.shift(); - return rec(processes); - }); + Satan.executeRemote('restartProcessId', { + id : proc.pm2_env.pm_id, + env : process.env + }, function(err, res) { + if (err) { + printError(err); + return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); + } + printOut(cst.PREFIX_MSG + 'Process ' + proc.pm2_env.name + ' restarted'); + processes.shift(); + return rec(processes); + }); return false; })(list); }); @@ -957,35 +1044,42 @@ CLI.delete = function(process_name, jsonVia, cb) { process_name = process_name.toString(); } + printOut(cst.PREFIX_MSG + 'Deleting %s process', process_name); + if (jsonVia == 'pipe') - return CLI.actionFromJson('deleteProcessName', process_name, 'pipe'); + return CLI.actionFromJson('deleteProcessId', process_name, 'pipe'); if (process_name.indexOf('.json') > 0) - return CLI.actionFromJson('deleteProcessName', process_name, 'file'); + return CLI.actionFromJson('deleteProcessId', process_name, 'file'); else if (process_name == 'all') { - printOut(cst.PREFIX_MSG + 'Stopping and deleting all processes'); - Satan.executeRemote('deleteAll', {}, function(err, list) { + Common.getAllProcessId(function(err, ids) { if (err) { printError(err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } - return cb ? cb(null, list) : speedList(); + async.eachLimit(ids, 1, function(id, next) { + printOut(cst.PREFIX_MSG + 'Deleting process id %d', id); + Satan.executeRemote('deleteProcessId', id, function(err, list) { + return next(); + }); + }, function(err) { + return cb ? cb(null, ids) : speedList(); + }); + return false; }); } else if (!isNaN(parseInt(process_name))) { - printOut('Stopping and deleting process by id : %s', process_name); Satan.executeRemote('deleteProcessId', process_name, function(err, list) { if (err) { - printError(err); + printError(cst.PREFIX_MSG_ERR + err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } return cb ? cb(null, list) : speedList(); }); } else { - printOut(cst.PREFIX_MSG + 'Stopping and deleting process by name %s', process_name); Satan.executeRemote('deleteProcessName', process_name, function(err, list) { if (err) { - printError(err); + printError(cst.PREFIX_MSG_ERR + err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } return cb ? cb(null, list) : speedList(); @@ -998,21 +1092,22 @@ CLI.stop = function(process_name, cb) { if (typeof(process_name) === 'number') process_name = process_name.toString(); + printOut(cst.PREFIX_MSG + 'Stopping ' + process_name); + if (process_name == "-") { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (param) { process.stdin.pause(); - CLI.actionFromJson('stopProcessName', param, 'pipe', cb); + CLI.actionFromJson('stopProcessId', param, 'pipe', cb); }); } else if (process_name.indexOf('.json') > 0) - CLI.actionFromJson('stopProcessName', process_name, 'file', cb); + CLI.actionFromJson('stopProcessId', process_name, 'file', cb); else if (process_name == 'all') CLI._stopAll(cb); else if (isNaN(parseInt(process_name))) { CLI._stopProcessName(process_name, cb); } else { - printOut(cst.PREFIX_MSG + 'Stopping process by id ' + process_name); CLI._stopId(process_name, cb); } }; @@ -1025,9 +1120,10 @@ CLI.stop = function(process_name, cb) { CLI._stopAll = function(cb) { Satan.executeRemote('stopAll', {}, function(err, list) { if (err) { - printError(err); + printError(cst.PREFIX_MSG_ERR + err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } + printOut(cst.PREFIX_MSG + 'All processes stopped'); return cb ? cb(null, list) : speedList(); }); }; @@ -1041,7 +1137,7 @@ CLI._stopAll = function(cb) { CLI._stopProcessName = function(name, cb) { Satan.executeRemote('stopProcessName', name, function(err, list) { if (err) { - printError(err); + printError(cst.PREFIX_MSG_ERR + err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } printOut(cst.PREFIX_MSG + 'Stopping process by name ' + name); @@ -1058,10 +1154,10 @@ CLI._stopProcessName = function(name, cb) { CLI._stopId = function(pm2_id, cb) { Satan.executeRemote('stopProcessId', pm2_id, function(err, list) { if (err) { - printError(err); + printError(cst.PREFIX_MSG_ERR + err); return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT); } - printOut(cst.PREFIX_MSG + ' Process stopped'); + printOut(cst.PREFIX_MSG + 'Process %d stopped', pm2_id); return cb ? cb(null, list) : speedList(); }); }; @@ -1139,6 +1235,7 @@ CLI.flush = function(cb) { fs.openSync(l.pm2_env.pm_out_log_path, 'w'); fs.openSync(l.pm2_env.pm_err_log_path, 'w'); }); + printOut(cst.PREFIX_MSG + 'Logs flushed'); return cb ? cb(null, list) : exitCli(cst.SUCCESS_EXIT); }); }; @@ -1310,7 +1407,7 @@ CLI.ilogs = function() { }); } catch(e) { printOut('pm2-logs module is not installed'); - fallbackLogStream(id); + fallbackLogStream(); } }; @@ -1321,35 +1418,16 @@ CLI.ilogs = function() { * @return */ CLI.killDaemon = function(cb) { - printOut(cst.PREFIX_MSG + 'Killing pm2...'); + printOut(cst.PREFIX_MSG + 'Stopping PM2...'); - Satan.executeRemote('getMonitorData', {}, function(err, list) { - if (err) { - printError('Error retrieving process list: ' + err); - return cb ? cb({msg : err}) : exitCli(cst.ERROR_EXIT); - } + this.delete('all', function(err, list) { + printOut(cst.PREFIX_MSG + 'All processes has been stopped and deleted'); - async.eachLimit(list, 1, function(proc, next) { - Satan.executeRemote('deleteProcessId', proc.pm2_env.pm_id, function(err, res) { - if (err) { - printError('Error : ' + err); - } - printOut(cst.PREFIX_MSG + 'Process %s stopped', proc.pm2_env.name); - return next(); - }); - return false; - }, function(err) { - printOut(cst.PREFIX_MSG + 'All processes stopped'); - CLI.killInteract(function() { - Satan.killDaemon(function(err, res) { - if (err) { - printError(err); - if (cb) return cb({msg:err}); - else exitCli(cst.ERROR_EXIT); - } - console.info('PM2 stopped'); - return cb ? cb(null, res) : exitCli(cst.SUCCESS_EXIT); - }); + InteractorDaemonizer.killDaemon(function() { + Satan.killDaemon(function(err, res) { + if (err) printError(err); + printOut(cst.PREFIX_MSG + 'PM2 stopped'); + return cb ? cb(null) : exitCli(cst.SUCCESS_EXIT); }); }); return false; @@ -1408,6 +1486,7 @@ function speedList() { * @return */ function getInteractInfo(cb) { + debug('Getting interaction info'); InteractorDaemonizer.ping(function(online) { if (!online) { return cb({msg : 'offline'}); @@ -1417,7 +1496,10 @@ function getInteractInfo(cb) { if (err) { return cb(err); } - return cb(null, infos); + InteractorDaemonizer.disconnectRPC(function() { + return cb(null, infos); + }); + return false; }); }); return false; diff --git a/lib/CliUx.js b/lib/CliUx.js index 0ef3ee9a..544722ed 100644 --- a/lib/CliUx.js +++ b/lib/CliUx.js @@ -59,6 +59,7 @@ UX.describeTable = function(process) { { 'out log path' : pm2_env.pm_out_log_path }, { 'pid path' : pm2_env.pm_pid_path }, { 'mode' : pm2_env.exec_mode }, + { 'node v8 arguments' : pm2_env.node_args }, { 'watch & reload' : pm2_env.watch ? chalk.green.bold('✔') : '✘' }, { 'interpreter' : pm2_env.exec_interpreter }, { 'restarts' : pm2_env.restart_time }, @@ -84,6 +85,8 @@ UX.dispAsTable = function(list, interact_infos) { style : {'padding-left' : 1, head : ['cyan', 'bold'], border : ['white'], compact : true} }); + if (!list) + return console.log('list empty'); list.forEach(function(l) { var obj = {}; diff --git a/lib/Common.js b/lib/Common.js index 0a61bc20..d588770c 100644 --- a/lib/Common.js +++ b/lib/Common.js @@ -11,7 +11,10 @@ var cst = require('../constants.js'); var extItps = require('./interpreter.json'); var p = path; +var Stringify = require('json-stringify-safe'); + var Satan = require('./Satan.js'); +var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js'); /** * Common methods (used by CLI and God) */ @@ -33,7 +36,12 @@ Common.resolveAppPaths = function(app, cwd, outputter) { if (err) return err; - cwd = cwd || process.cwd(); + if (cwd && cwd[0] == '/') + cwd = cwd; + else if (cwd) + cwd = p.resolve(process.cwd(), cwd); + else + cwd = process.cwd(); // Set current env by first adding the process environment and then extending/replacing it // with env specified on command-line or JSON file. @@ -43,6 +51,7 @@ Common.resolveAppPaths = function(app, cwd, outputter) { util._extend(app.env, env); app.env.pm_cwd = cwd; + app.pm_cwd = cwd; if (!app.exec_interpreter) { if (extItps[path.extname(app.script)]) { @@ -60,10 +69,24 @@ Common.resolveAppPaths = function(app, cwd, outputter) { app["pm_exec_path"] = path.resolve(cwd, app.script); delete app.script; - if (!app["name"]) { - app["name"] = p.basename(app["pm_exec_path"]); + if (app.node_args && !Array.isArray(app.node_args)) + app.node_args = app.node_args.split(' '); + + if (!app.node_args) + app.node_args = []; + + if (app.max_memory_restart && + !isNaN(parseInt(app.max_memory_restart)) && + Array.isArray(app.node_args)) { + app.node_args.push('--max-old-space-size=' + app.max_memory_restart); } + if (!app.name) { + app.name = p.basename(app["pm_exec_path"]); + } + + var formated_app_name = app.name.replace(/[^a-zA-Z0-9\\.\\-]/g, '-'); + if (fs.existsSync(app.pm_exec_path) == false) { return new Error('script not found : ' + app.pm_exec_path); } @@ -71,10 +94,7 @@ Common.resolveAppPaths = function(app, cwd, outputter) { if (app.out_file) app["pm_out_log_path"] = path.resolve(cwd, app.out_file); else { - if (!app.name) { - return new Error('You havent specified log path, please specify at least a "name" field in the JSON'); - } - app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-out.log'].join('')); + app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '-out.log'].join('')); app.out_file = app["pm_out_log_path"]; } delete app.out_file; @@ -82,7 +102,7 @@ Common.resolveAppPaths = function(app, cwd, outputter) { if (app.error_file) app["pm_err_log_path"] = path.resolve(cwd, app.error_file); else { - app["pm_err_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-err.log'].join('')); + app["pm_err_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '-err.log'].join('')); app.error_file = app["pm_err_log_path"]; } delete app.error_file; @@ -90,7 +110,7 @@ Common.resolveAppPaths = function(app, cwd, outputter) { if (app.pid_file) app["pm_pid_path"] = path.resolve(cwd, app.pid_file); else { - app["pm_pid_path"] = path.resolve(cst.DEFAULT_PID_PATH, [app.name, '.pid'].join('')); + app["pm_pid_path"] = path.resolve(cst.DEFAULT_PID_PATH, [formated_app_name, '.pid'].join('')); app.pid_file = app["pm_pid_path"]; } delete app.pid_file; @@ -103,6 +123,10 @@ Common.resolveAppPaths = function(app, cwd, outputter) { return app; }; +Common.deepCopy = Common.serialize = function serialize(data) { + return JSON.parse(Stringify(data)); +}; + /** * Description * @method validateApp @@ -142,8 +166,10 @@ Common.validateApp = function(appConf, outputter) { * @return CallExpression */ Common.exitCli = function(code) { - Satan.disconnectRPC(function() { - return process.exit(code || 0); + InteractorDaemonizer.disconnectRPC(function() { + Satan.disconnectRPC(function() { + return process.exit(code || 0); + }); }); }; @@ -169,3 +195,41 @@ Common.printOut = function() { if (process.env.PM2_SILENT) return false; return console.log.apply(console, arguments); }; + + +Common.getAllProcessId = function(cb) { + var found_proc = []; + + Satan.executeRemote('getMonitorData', {}, function(err, list) { + if (err) { + Common.printError('Error retrieving process list: ' + err); + return cb(err); + } + + list.forEach(function(proc) { + found_proc.push(proc.pm_id); + }); + + return cb(null, found_proc); + }); +}; + +Common.getProcessIdByName = function(name, cb) { + var found_proc = []; + + Satan.executeRemote('getMonitorData', {}, function(err, list) { + if (err) { + Common.printError('Error retrieving process list: ' + err); + return cb(err); + } + + list.forEach(function(proc) { + if (proc.pm2_env.name == name || + proc.pm2_env.pm_exec_path == p.resolve(name)) { + found_proc.push(proc.pm_id); + } + }); + + return cb(null, found_proc); + }); +}; diff --git a/lib/God.js b/lib/God.js index c1ac5c41..80801605 100644 --- a/lib/God.js +++ b/lib/God.js @@ -1,8 +1,3 @@ -'use strict'; - -/** - * Module dependencies - */ var cluster = require('cluster'); var numCPUs = require('os').cpus() ? require('os').cpus().length : 1; @@ -13,6 +8,14 @@ var fs = require('fs'); var p = path; var Common = require('./Common'); var cst = require('../constants.js'); +var pidusage = require('pidusage'); + +// require('webkit-devtools-agent').start({ +// port: 9999, +// bind_to: '0.0.0.0', +// ipc_port: 3333, +// verbose: true +// }); /** * Override cluster module configuration @@ -28,10 +31,9 @@ var God = module.exports = { next_id : 0, clusters_db : {}, bus : new EventEmitter2({ - wildcard: true, + wildcard: false, delimiter: ':', - newListener: false, - maxListeners: 20 + maxListeners: 1000 }) }; @@ -44,22 +46,6 @@ require('./God/ClusterMode.js')(God); require('./God/Reload')(God); require('./God/ActionMethods')(God); -/** - * Forced entry to initialize cluster monitoring - */ - -(function initEngine() { - cluster.on('online', function(clu) { - console.log('%s - id%d worker online', clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id); - clu.pm2_env.status = cst.ONLINE_STATUS; - God.bus.emit('process:online', { process : clu }); - }); - - cluster.on('exit', function(clu, code, signal) { - handleExit(clu, code); - }); -})(); - /** * Handle logic when a process exit (Node or Fork) * @method handleExit @@ -67,24 +53,34 @@ require('./God/ActionMethods')(God); * @param {} exit_code * @return */ -function handleExit(clu, exit_code) { - console.log('Script %s %s exited code %d', clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id, exit_code); +God.handleExit = function handleExit(clu, exit_code) { + console.log('Script %s %s exit', clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id); - var stopping = (clu.pm2_env.status == cst.STOPPING_STATUS || clu.pm2_env.status == cst.ERRORED_STATUS) ? true : false; + var proc = this.clusters_db[clu.pm2_env.pm_id]; + + pidusage.unmonitor(proc.process.pid); + + if (!proc) { + console.error('Process undefined ? with process id ', clu.pm2_env.pm_id); + return false; + } + + var stopping = (proc.pm2_env.status == cst.STOPPING_STATUS || proc.pm2_env.status == cst.ERRORED_STATUS) ? true : false; var overlimit = false; - var pidFile = clu.pm2_env.pm_pid_path; - if (stopping) clu.process.pid = 0; + if (stopping) proc.process.pid = 0; - if (clu.pm2_env.axm_actions) clu.pm2_env.axm_actions = []; + if (proc.pm2_env.axm_actions) proc.pm2_env.axm_actions = []; - if (clu.pm2_env.status != cst.ERRORED_STATUS && - clu.pm2_env.status != cst.STOPPING_STATUS) - clu.pm2_env.status = cst.STOPPED_STATUS; + if (proc.pm2_env.status != cst.ERRORED_STATUS && + proc.pm2_env.status != cst.STOPPING_STATUS) + proc.pm2_env.status = cst.STOPPED_STATUS; try { - fs.unlinkSync(pidFile); - } catch(e) { console.error('Error when unlinking PID file', e); } + fs.unlinkSync(proc.pm2_env.pm_pid_path); + } catch (e) { + console.error('Error when unlinking PID file', e); + } /** * Avoid infinite reloop if an error is present @@ -92,37 +88,42 @@ function handleExit(clu, exit_code) { // If the process has been created less than 15seconds ago // And if the process has an uptime less than a second - var min_uptime = (clu.pm2_env.min_uptime || 1000); - var max_restarts = (clu.pm2_env.max_restarts || 15); + var min_uptime = typeof(proc.pm2_env.min_uptime) !== 'undefined' ? proc.pm2_env.min_uptime : 1000; + var max_restarts = typeof(proc.pm2_env.max_restarts) !== 'undefined' ? proc.pm2_env.max_restarts : 15; - if ((Date.now() - clu.pm2_env.created_at) < (min_uptime * max_restarts)) { - if ((Date.now() - clu.pm2_env.pm_uptime) < min_uptime) { + if ((Date.now() - proc.pm2_env.created_at) < (min_uptime * max_restarts)) { + if ((Date.now() - proc.pm2_env.pm_uptime) < min_uptime) { // Increment unstable restart - clu.pm2_env.unstable_restarts += 1; + proc.pm2_env.unstable_restarts += 1; } - if (clu.pm2_env.unstable_restarts >= max_restarts) { + if (proc.pm2_env.unstable_restarts >= max_restarts) { // Too many unstable restart in less than 15 seconds // Set the process as 'ERRORED' // And stop to restart it - clu.pm2_env.status = cst.ERRORED_STATUS; - console.log('Script %s had too many unstable restarts (%d). Stopped.', - clu.pm2_env.pm_exec_path, - clu.pm2_env.unstable_restarts); - God.bus.emit('process:exit:overlimit', { process : clu }); - clu.pm2_env.unstable_restarts = 0; - clu.pm2_env.created_at = null; + proc.pm2_env.status = cst.ERRORED_STATUS; + + console.log('Script %s had too many unstable restarts (%d). Stopped. %j', + proc.pm2_env.pm_exec_path, + proc.pm2_env.unstable_restarts, + proc.pm2_env.status); + this.bus.emit('process:exit:overlimit', { process : Common.serialize(proc) }); + proc.pm2_env.unstable_restarts = 0; + proc.pm2_env.created_at = null; overlimit = true; } } - God.bus.emit('process:exit', { process : clu }); + this.bus.emit('process:exit', { process : Common.serialize(proc) }); if (!stopping) - clu.pm2_env.restart_time = clu.pm2_env.restart_time + 1; + proc.pm2_env.restart_time = proc.pm2_env.restart_time + 1; - if (!stopping && !overlimit) God.executeApp(clu.pm2_env); -} + if (!stopping && !overlimit) + this.executeApp(proc.pm2_env); + + return false; +}; /** @@ -133,10 +134,10 @@ function handleExit(clu, exit_code) { * @param {Function} cb * @return Literal */ -God.executeApp = function(env, cb) { - var env_copy = JSON.parse(JSON.stringify(env)); +God.executeApp = function executeApp(env, cb) { + var env_copy = Common.serialize(env); - util._extend(env_copy, env.env); + util._extend(env_copy, env_copy.env); env_copy['axm_actions'] = []; @@ -162,7 +163,7 @@ God.executeApp = function(env, cb) { } if (env_copy['watch']) { - env_copy['watcher'] = require('./Watcher').watch(env_copy); + env_copy['watcher'] = require('./Watcher').watch(env_copy); } } @@ -176,22 +177,30 @@ God.executeApp = function(env, cb) { /** * Fork mode logic */ - God.forkMode(env_copy, function(err, clu) { + God.forkMode(env_copy, function forkMode(err, clu) { if (cb && err) return cb(err); + if (err) return false; - God.clusters_db[env_copy.pm_id] = clu; + var old_env = God.clusters_db[clu.pm2_env.pm_id]; + if (old_env) old_env = null; - clu.once('error', function(err) { - clu.pm2_env.status = cst.ERRORED_STATUS; + var proc = God.clusters_db[env_copy.pm_id] = clu; + + clu.once('error', function cluError(err) { + proc.pm2_env.status = cst.ERRORED_STATUS; + return false; }); - clu.once('close', function(code) { - handleExit(clu, code); + clu.once('close', function cluClose(code) { + proc.removeAllListeners(); + clu._reloadLogs = null; + God.handleExit(proc, code); + return false; }); - God.bus.emit('process:online', {process : clu }); + God.bus.emit('process:online', {process : Common.serialize(proc)}); + if (cb) cb(null, clu); - return false; }); } @@ -199,11 +208,44 @@ God.executeApp = function(env, cb) { /** * Cluster mode logic (for NodeJS apps) */ - God.nodeApp(env_copy, function(err, clu) { + God.nodeApp(env_copy, function nodeApp(err, clu) { if (cb && err) return cb(err); if (err) return false; - God.clusters_db[clu.pm2_env.pm_id] = clu; - if (cb) cb(null, clu); + + var old_env = God.clusters_db[clu.pm2_env.pm_id]; + + if (old_env) { + old_env = null; + + if (typeof(God.clusters_db[clu.pm2_env.pm_id].process._handle) !== 'undefined') { + if (God.clusters_db[clu.pm2_env.pm_id].process._handle) + God.clusters_db[clu.pm2_env.pm_id].process._handle.owner = null; + God.clusters_db[clu.pm2_env.pm_id].process._handle = null; + God.clusters_db[clu.pm2_env.pm_id].process = null; + } + God.clusters_db[clu.pm2_env.pm_id] = null; + } + + var proc = God.clusters_db[clu.pm2_env.pm_id] = clu; + + clu.once('online', function cluOnline() { + proc.pm2_env.status = cst.ONLINE_STATUS; + + console.log('%s - id%d worker online', proc.pm2_env.pm_exec_path, proc.pm2_env.pm_id); + + God.bus.emit('process:online', { process : Common.serialize(proc) }); + + if (cb) return cb(null, proc); + return false; + }); + + clu.once('exit', function cluExit(exited_clu, code) { + proc.removeAllListeners(); + proc.process.removeAllListeners(); + God.handleExit(proc, code); + return false; + }); + return false; }); } @@ -220,7 +262,7 @@ God.executeApp = function(env, cb) { * @param {} cb * @return Literal */ -God.prepare = function(env, cb) { +God.prepare = function prepare(env, cb) { // If instances option is set (-i [arg]) if (env.instances) { if (env.instances == 'max') env.instances = numCPUs; @@ -233,7 +275,8 @@ God.prepare = function(env, cb) { if (cb != null) return cb(null, arr); return false; } - return God.executeApp(JSON.parse(JSON.stringify(env)), function(err, clu) { // deep copy + + return God.executeApp(Common.serialize(env), function(err, clu) { if (err) return ex(i - 1); arr.push(clu); return ex(i - 1); @@ -242,7 +285,7 @@ God.prepare = function(env, cb) { } else { return God.executeApp(env, function(err, dt) { - cb(err, [dt]); + cb(err, [Common.serialize(dt)]); }); } return false; @@ -259,13 +302,14 @@ God.prepare = function(env, cb) { * @param cb {Function} * @return CallExpression */ -God.prepareJson = function (app, cwd, cb) { +God.prepareJson = function prepareJson(app, cwd, cb) { if (!cb) { cb = cwd; cwd = undefined; } app = Common.resolveAppPaths(app, cwd); + if (app instanceof Error) return cb(app); diff --git a/lib/God/ActionMethods.js b/lib/God/ActionMethods.js index 35946e6b..3772ad14 100644 --- a/lib/God/ActionMethods.js +++ b/lib/God/ActionMethods.js @@ -14,6 +14,10 @@ var p = path; var cst = require('../../constants.js'); var pkg = require('../../package.json'); var pidusage = require('pidusage'); +var Common = require('../Common'); +var util = require('util'); + +var debug = require('debug')('pm2:ActionMethod'); /** * Description @@ -30,42 +34,43 @@ module.exports = function(God) { * @param {} cb * @return */ - God.getMonitorData = function(env, cb) { + God.getMonitorData = function getMonitorData(env, cb) { var processes = God.getFormatedProcesses(); - var arr = []; - - async.mapLimit(processes, 6, function(pro, next) { + async.map(processes, function computeMonitor(pro, next) { if (pro.pm2_env.status != cst.STOPPED_STATUS && pro.pm2_env.status != cst.STOPPING_STATUS && pro.pm2_env.status != cst.ERRORED_STATUS) { - try { - pidusage(pro.pid, function(err, res) { - if (err) - return next(err); - + pidusage.stat(pro.pid, function retPidUsage(err, res) { + if (err) { pro['monit'] = { - memory : Math.floor(res.memory), - cpu : Math.floor(res.cpu) + memory : 0, + cpu : 0 }; return next(null, pro); - }); + } - } catch(e) { - God.logAndGenerateError(e); - pro['monit'] = {memory : 0, cpu : 0}; + pro['monit'] = { + memory : Math.floor(res.memory), + cpu : Math.floor(res.cpu) + }; + res = null; return next(null, pro); - } - + }); } else { - pro['monit'] = {memory : 0, cpu : 0}; + pro['monit'] = { + memory : 0, + cpu : 0 + }; return next(null, pro); } return false; - }, function(err, res) { + }, function retMonitor(err, res) { if (err) return cb(God.logAndGenerateError(err), null); + processes = null; + return cb(null, res); }); @@ -78,7 +83,7 @@ module.exports = function(God) { * @param {} cb * @return */ - God.getSystemData = function(env, cb) { + God.getSystemData = function getSystemData(env, cb) { God.getMonitorData(env, function(err, processes) { cb(err, { system: { @@ -142,7 +147,7 @@ module.exports = function(God) { God.startProcessId = function(id, cb) { if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); - God.bus.emit('process:start', { process : God.clusters_db[id] }); + God.bus.emit('process:start', { process : Common.serialize(God.clusters_db[id]) }); if (God.clusters_db[id].pm2_env.status == cst.ONLINE_STATUS) return cb(God.logAndGenerateError('process already online'), {}); return God.executeApp(God.clusters_db[id].pm2_env, cb); @@ -162,13 +167,18 @@ module.exports = function(God) { if (God.clusters_db[id].pm2_env.status == cst.STOPPED_STATUS) return cb(null, God.getFormatedProcesses()); - God.bus.emit('process:stop', { process : God.clusters_db[id] }); + God.bus.emit('process:stop', { process : Common.serialize(God.clusters_db[id]) }); var proc = God.clusters_db[id]; var timeout = null; proc.pm2_env.status = cst.STOPPING_STATUS; + if (!proc.process.pid) { + proc.pm2_env.status = cst.STOPPED_STATUS; + return cb(null); + } + /** * Process to stop on cluster mode */ @@ -177,13 +187,15 @@ module.exports = function(God) { proc.state != 'dead') { proc.once('disconnect', function(){ - delete cluster.workers[proc.id]; + cluster.workers[proc.id] = null; + clearTimeout(timeout); God.killProcess(proc.process.pid, function() { - return setTimeout(function() { + setTimeout(function() { proc.pm2_env.status = cst.STOPPED_STATUS; cb(null, God.getFormatedProcesses()); }, 100); + return false; }); return false; }); @@ -237,6 +249,20 @@ module.exports = function(God) { return false; }; + God.resetMetaProcessId = function(id, cb) { + if (!(id in God.clusters_db)) + return cb(God.logAndGenerateError(id + ' id unknown'), {}); + + if (!God.clusters_db[id] || !God.clusters_db[id].pm2_env) + return cb(God.logAndGenerateError('Error when getting proc || proc.pm2_env'), {}); + + God.clusters_db[id].pm2_env.created_at = Date.now(); + God.clusters_db[id].pm2_env.unstable_restarts = 0; + God.clusters_db[id].pm2_env.restart_time = 0; + + return cb(null, God.getFormatedProcesses()); + }; + /** * Delete a process by id * It will stop it and remove it from the database @@ -247,10 +273,11 @@ module.exports = function(God) { */ God.deleteProcessId = function(id, cb) { if (God.clusters_db[id]) - God.bus.emit('process:delete', { process : God.clusters_db[id] }); + God.bus.emit('process:delete', { process : Common.serialize(God.clusters_db[id]) }); God.stopProcessId(id, function(err, dt) { if (err) return cb(God.logAndGenerateError(err), {}); + // ! transform to slow object delete God.clusters_db[id]; return cb(null, God.getFormatedProcesses()); }); @@ -266,21 +293,27 @@ module.exports = function(God) { * @param {} cb * @return Literal */ - God.restartProcessId = function(id, cb) { + God.restartProcessId = function(opts, cb) { + var id = opts.id; + var env = opts.env; + if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); var proc = God.clusters_db[id]; - God.bus.emit('process:restart', { process : proc }); + God.bus.emit('process:restart', { process : Common.serialize(proc) }); God.resetState(proc.pm2_env); + util._extend(proc.pm2_env.env, opts.env); + if (proc.pm2_env.status == cst.ONLINE_STATUS) { God.killProcess(proc.process.pid, function() { - return setTimeout(function() { + setTimeout(function() { return cb(null, God.getFormatedProcesses()); }, 100); + return false; }); } else @@ -351,12 +384,11 @@ module.exports = function(God) { var proc = God.clusters_db[id]; - God.bus.emit('process:send_signal', { process : proc }); + God.bus.emit('process:send_signal', { process : Common.serialize(proc) }); try { process.kill(God.clusters_db[id].process.pid, signal); } catch(e) { - console.error(e); return cb(God.logAndGenerateError('Error when sending signal (signal unknown)'), {}); } return cb(null, God.getFormatedProcesses()); @@ -408,6 +440,7 @@ module.exports = function(God) { async.eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) { God.stopProcessId(proc.pm2_env.pm_id, function() { + // Slow object delete God.clusters_db[proc.pm2_env.pm_id]; return next(); }); @@ -432,7 +465,9 @@ module.exports = function(God) { if (processes && processes.length === 0) return cb(God.logAndGenerateError('No processes launched'), {}); + debug('Deleting all processes'); async.eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) { + debug('Deleting process %s', proc.pm2_env.pm_id); God.deleteProcessId(proc.pm2_env.pm_id, function() { return next(); }); @@ -454,17 +489,17 @@ module.exports = function(God) { * @return */ God.killMe = function(env, cb) { - God.deleteAll({}, function(err, processes) { - console.log('pm2 has been killed by command line'); - God.bus.emit('pm2:kill', { - status : 'killed', - msg : 'pm2 has been killed via CLI' - }); - setTimeout(function() { - cb(null, {msg : 'pm2 killed'}); - process.exit(cst.SUCCESS_EXIT); - }, 800); + console.log('PM2 has been killed by user command'); + God.bus.emit('pm2:kill', { + status : 'killed', + msg : 'pm2 has been killed via CLI' }); + + cb(null, {msg : 'pm2 killed'}); + + setTimeout(function() { + process.exit(cst.SUCCESS_EXIT); + }, 5); }; @@ -542,10 +577,16 @@ module.exports = function(God) { processIds.forEach(function (id) { var cluster = God.clusters_db[id]; + if (cluster.pm2_env.exec_mode == 'cluster_mode') cluster.send({type:'log:reload'}); - else - cluster._reloadLogs(function() { + else // Fork mode + cluster._reloadLogs(function(err) { + if (err) { + God.logAndGenerateError(err); + return cb(new Error(err)); + }; + return false; }); console.log('Reloading logs for process id %d', id); }); diff --git a/lib/God/ClusterMode.js b/lib/God/ClusterMode.js index ec133d5a..5044eaf2 100644 --- a/lib/God/ClusterMode.js +++ b/lib/God/ClusterMode.js @@ -10,6 +10,7 @@ var cluster = require('cluster'); var fs = require('fs'); var cst = require('../../constants.js'); var util = require('util'); +var Common = require('../Common'); /** * Description @@ -17,7 +18,7 @@ var util = require('util'); * @param {} God * @return */ -module.exports = function(God) { +module.exports = function ClusterMode(God) { /** * For Node apps - Cluster mode @@ -27,55 +28,50 @@ module.exports = function(God) { * @param {} cb * @return Literal */ - God.nodeApp = function(env_copy, cb){ - var clu; + God.nodeApp = function nodeApp(env_copy, cb){ + var clu = null; - if (fs.existsSync(env_copy.pm_exec_path) == false) { - console.error('Script ' + env_copy.pm_exec_path + ' missing'); - return cb(God.logAndGenerateError('Script ' + env_copy.pm_exec_path + ' missing'), {}); - } + // if (fs.existsSync(env_copy.pm_exec_path) == false) { + // return cb(God.logAndGenerateError('Script ' + env_copy.pm_exec_path + ' missing'), {}); + // } console.log('Entering in node wrap logic (cluster_mode) for script %s', env_copy.pm_exec_path); - if (env_copy.nodeArgs && Array.isArray(env_copy.nodeArgs)) { - cluster.settings.execArgv = env_copy.nodeArgs; + if (env_copy.node_args && Array.isArray(env_copy.node_args)) { + cluster.settings.execArgv = env_copy.node_args; } try { clu = cluster.fork(env_copy); - } catch(e) { return God.logAndGenerateError(e); } + } catch(e) { + God.logAndGenerateError(e); + return cb(e); + } clu.pm2_env = env_copy; - God.clusters_db[env_copy.pm_id] = clu; // Receive message from child - clu.on('message', function(msg) { + clu.on('message', function cluMessage(msg) { + var proc_data = Common.serialize(clu); + switch (msg.type) { case 'process:exception': - God.bus.emit('process:exception', {process : clu, data : msg, err : msg.err}); + God.bus.emit('process:exception', {process : proc_data, data : msg, err : msg.err}); break; case 'log:out': - God.bus.emit('log:out', {process : clu, data : msg.data}); + God.bus.emit('log:out', {process : proc_data, data : msg.data}); break; case 'log:err': - God.bus.emit('log:err', {process : clu, data : msg.data}); + God.bus.emit('log:err', {process : proc_data, data : msg.data}); break; case 'human_event': - God.bus.emit('human_event', {process : clu, data : util._extend(msg, {type:msg.name})}); + God.bus.emit('human_event', {process : proc_data, data : util._extend(msg, {type:msg.name})}); break; default: // Permits to send message to external from the app - God.bus.emit(msg.type ? msg.type : 'process:msg', {process : clu, data : msg }); + God.bus.emit(msg.type ? msg.type : 'process:msg', {process : proc_data, data : msg }); } }); - // Avoid circular dependency - delete clu.process._handle.owner; - - clu.once('online', function() { - clu.pm2_env.status = cst.ONLINE_STATUS; - if (cb) return cb(null, clu); - return false; - }); - return false; + return cb(null, clu); }; }; diff --git a/lib/God/ForkMode.js b/lib/God/ForkMode.js index 30188b49..5c7ec30c 100644 --- a/lib/God/ForkMode.js +++ b/lib/God/ForkMode.js @@ -11,14 +11,14 @@ var fs = require('fs'); var cst = require('../../constants.js'); var uidNumber = require('uid-number'); var moment = require('moment'); - +var Common = require('../Common'); /** * Description * @method exports * @param {} God * @return */ -module.exports = function(God) { +module.exports = function ForkMode(God) { /** * For all apps - FORK MODE * fork the app @@ -27,10 +27,11 @@ module.exports = function(God) { * @param {} cb * @return */ - God.forkMode = function(pm2_env, cb) { - var command, args; + God.forkMode = function forkMode(pm2_env, cb) { + var command = ''; + var args = []; - log('Entering in fork mode'); + console.log('Entering in fork mode'); var spawn = require('child_process').spawn; var interpreter = pm2_env.exec_interpreter || 'node'; @@ -38,16 +39,26 @@ module.exports = function(God) { if (interpreter !== 'none') { command = interpreter; - args = [pm2_env.pm_exec_path]; + + if (pm2_env.node_args && Array.isArray(pm2_env.node_args)) { + args = args.concat(pm2_env.node_args); + } + + if (process.env.PM2_NODE_OPTIONS) { + args = args.concat(process.env.PM2_NODE_OPTIONS.split(' ')); + } + + args.push(pm2_env.pm_exec_path); } else { command = pm2_env.pm_exec_path; args = [ ]; } - // Concat args if present - if (pm2_env.args) + if (pm2_env.args) { args = args.concat(eval((pm2_env.args))); + } + var stdout, stderr; var outFile = pm2_env.pm_out_log_path; @@ -75,93 +86,109 @@ module.exports = function(God) { function startLogging(cb) { stdout = fs.createWriteStream(outFile, { flags : 'a' }); + stdout.on('error', function(e) { + God.logAndGenerateError(e); + return cb(e); + }); + stdout.on('open', function() { stderr = fs.createWriteStream(errFile, { flags : 'a' }); + + stderr.on('error', function(e) { + God.logAndGenerateError(e); + return cb(e); + }); + stderr.on('open', function() { - return cb(); + return cb(null); }); }); } - startLogging(function() { + startLogging(function(err) { + if (err) { + God.logAndGenerateError(err); + return cb(err); + }; + getugid(function(e, uid, gid){ - if(e){ - console.error(e.stack || e); - if (cb) return cb(e); - } + if(e){ + God.logAndGenerateError(e); + if (cb) return cb(e); + } - try { - var cspr = spawn(command, args, { - env : pm2_env, - detached : true, - gid : gid, - uid : uid, - cwd : pm2_env.pm_cwd || process.cwd(), - stdio : ['ipc', null, null] - }); - } catch(e) { - console.error(e.stack || e); - if (cb) return cb(e); - } - - cspr.process = {}; - cspr.process.pid = cspr.pid; - cspr.pm2_env = pm2_env; - cspr.pm2_env.status = cst.ONLINE_STATUS; - - cspr.stderr.on('data', function(data) { - - var log_data = data.toString(); - if (pm2_env.log_date_format) - log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; - - stderr.write(log_data); - - God.bus.emit('log:err', { - process : cspr, - data : data.toString() - }); + try { + var cspr = spawn(command, args, { + env : pm2_env, + detached : true, + gid : gid, + uid : uid, + cwd : pm2_env.pm_cwd || process.cwd(), + stdio : ['ipc', null, null] }); + } catch(e) { + God.logAndGenerateError(e); + if (cb) return cb(e); + } - cspr.stdout.on('data', function(data) { - - var log_data = data.toString(); - if (pm2_env.log_date_format) - log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; + cspr.process = {}; + cspr.process.pid = cspr.pid; + cspr.pm2_env = pm2_env; + cspr.pm2_env.status = cst.ONLINE_STATUS; - stdout.write(log_data); + cspr.stderr.on('data', function forkErrData(data) { - God.bus.emit('log:out', { - process : cspr, - data : data.toString() - }); + var log_data = data.toString(); + if (pm2_env.log_date_format) + log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; + + stderr.write(log_data); + + God.bus.emit('log:err', { + process : Common.serialize(cspr), + data : data.toString() }); - - cspr.on('message', function(data) { - God.bus.emit(data.type ? data.type : 'process:msg', { - process : cspr, - data : data - }); - }); - - fs.writeFileSync(pidFile, cspr.pid); - - cspr.once('close', function(status) { - try { - stderr.close(); - stdout.close(); - } catch(e) { console.error(e.stack || e);} - }); - - cspr._reloadLogs = startLogging; - - - cspr.unref(); - - if (cb) return cb(null, cspr); - return false; }); + + cspr.stdout.on('data', function forkOutData(data) { + + var log_data = data.toString(); + if (pm2_env.log_date_format) + log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; + + stdout.write(log_data); + + God.bus.emit('log:out', { + process : Common.serialize(cspr), + data : data.toString() + }); + }); + + cspr.on('message', function forkMessage(data) { + God.bus.emit(data.type ? data.type : 'process:msg', { + process : Common.serialize(cspr), + data : data + }); + }); + + fs.writeFileSync(pidFile, cspr.pid); + + cspr.once('close', function forkClose(status) { + try { + stderr.close(); + stdout.close(); + } catch(e) { God.logAndGenerateError(e);} + }); + + cspr._reloadLogs = startLogging; + + cspr.unref(); + + if (cb) return cb(null, cspr); + return false; }); + return false; + }); }; }; diff --git a/lib/God/Methods.js b/lib/God/Methods.js index ca42d365..2c757e7e 100644 --- a/lib/God/Methods.js +++ b/lib/God/Methods.js @@ -6,7 +6,7 @@ * @project PM2 */ var p = require('path'); - +var Common = require('../Common'); /** * Description * @method exports @@ -46,8 +46,8 @@ module.exports = function(God) { * @method getFormatedProcesses * @return arr */ - God.getFormatedProcesses = function() { - var db = God.clusters_db; + God.getFormatedProcesses = function getFormatedProcesses() { + var db = Common.serialize(God.clusters_db); var arr = []; for (var key in db) { @@ -60,6 +60,7 @@ module.exports = function(God) { }); } } + db = null; return arr; }; @@ -69,7 +70,7 @@ module.exports = function(God) { * @param {} id * @return ConditionalExpression */ - God.findProcessById = function(id) { + God.findProcessById = function findProcessById(id) { return God.clusters_db[id] ? God.clusters_db[id] : null; }; @@ -99,7 +100,7 @@ module.exports = function(God) { * @return */ God.findByScript = function(script, cb) { - var db = God.clusters_db; + var db = Common.serialize(God.clusters_db); var arr = []; for (var key in db) { diff --git a/lib/God/Reload.js b/lib/God/Reload.js index e72a6940..f3153c00 100644 --- a/lib/God/Reload.js +++ b/lib/God/Reload.js @@ -8,7 +8,7 @@ var async = require('async'); var cst = require('../../constants.js'); - +var Common = require('../Common'); /** * softReload will wait permission from process to exit * @method softReload @@ -31,7 +31,7 @@ function softReload(God, id, cb) { var new_env = JSON.parse(JSON.stringify(old_worker.pm2_env)); new_env.restart_time += 1; - God.bus.emit('process:start_soft_reload', { process : old_worker }); + God.bus.emit('process:start_soft_reload', { process : Common.serialize(old_worker) }); // Reset created_at and unstable_restarts God.resetState(new_env); @@ -123,7 +123,7 @@ function hardReload(God, id, cb) { var new_env = JSON.parse(JSON.stringify(old_worker.pm2_env)); new_env.restart_time += 1; - God.bus.emit('process:start_reload', { process : old_worker }); + God.bus.emit('process:start_reload', { process : Common.serialize(old_worker) }); // Reset created_at and unstable_restarts God.resetState(new_env); diff --git a/lib/HttpInterface.js b/lib/HttpInterface.js index 77c65aaa..b147769b 100644 --- a/lib/HttpInterface.js +++ b/lib/HttpInterface.js @@ -59,17 +59,3 @@ http.createServer(function (req, res) { return res.end(); } }).listen(cst.WEB_INTERFACE); - - -// var MicroDB = require("nodejs-microdb"); - -// var fdb = new MicroDB({ -// "file" : p.join(cst.DEFAULT_FILE_PATH, "monit.db") -// }); - -// setInterval(function() { -// Satan.executeRemote("list", {}, function(err, data_proc) { -// console.log('adding'); -// fdb.add(data_proc); -// }); -// }, 1000); diff --git a/lib/Interactor/Daemon.js b/lib/Interactor/Daemon.js index 2a6584fe..a1f2cf01 100644 --- a/lib/Interactor/Daemon.js +++ b/lib/Interactor/Daemon.js @@ -6,12 +6,14 @@ var axon = require('axon'); var debug = require('debug')('interface:driver'); // Interface var chalk = require('chalk'); +var pkg = require('../../package.json'); + var cst = require('../../constants.js'); var Cipher = require('./Cipher.js'); var Filter = require('./Filter.js'); var ReverseInteractor = require('./ReverseInteractor.js'); var PushInteractor = require('./PushInteractor.js'); - +var WatchDog = require('./WatchDog.js'); var Daemon = { connectToPM2 : function() { @@ -20,15 +22,15 @@ var Daemon = { activateRPC : function() { console.log('Launching Interactor exposure'); - var self = this; - var rep = axon.socket('rep'); + var self = this; + var rep = axon.socket('rep'); var daemon_server = new rpc.Server(rep); var sock = rep.bind(cst.INTERACTOR_RPC_PORT); daemon_server.expose({ kill : function(cb) { console.log('Killing interactor'); - cb(); + cb(null); setTimeout(function() { process.exit(cst.SUCCESS_EXIT) }, 50); }, getInfos : function(cb) { @@ -51,7 +53,7 @@ var Daemon = { opts.PUBLIC_KEY = process.env.PM2_PUBLIC_KEY; opts.SECRET_KEY = process.env.PM2_SECRET_KEY; opts.REVERSE_INTERACT = JSON.parse(process.env.PM2_REVERSE_INTERACT); - + opts.PM2_VERSION = pkg.version; if (!opts.MACHINE_NAME) { console.error('You must provide a PM2_MACHINE_NAME environment variable'); process.exit(cst.ERROR_EXIT); @@ -77,28 +79,6 @@ var Daemon = { return opts; }, - wrapStd : function(cb) { - var stdout = fs.createWriteStream(cst.INTERACTOR_LOG_FILE_PATH, { - flags : 'a' - }); - - stdout.on('open', function() { - process.stderr.write = (function(write) { - return function(string, encoding, fd) { - stdout.write(new Date().toISOString() + ' : ' + string); - }; - })(process.stderr.write); - - process.stdout.write = (function(write) { - return function(string, encoding, fd) { - stdout.write(new Date().toISOString() + ' : ' + string); - }; - })(process.stdout.write); - - if (cb) return cb(); - return false; - }); - }, start : function() { var self = this; @@ -109,6 +89,10 @@ var Daemon = { self.activateRPC(); self.opts.ipm2 = self.connectToPM2(); + // WatchDog.start({ + // conf : self.opts + // }); + // Then connect to external services if (cst.DEBUG) { PushInteractor.start({ @@ -150,7 +134,5 @@ if (require.main === module) { console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Launching agent'); process.title = 'PM2: Keymetrics.io Agent'; - Daemon.wrapStd(function() { - Daemon.start(); - }); + Daemon.start(); } diff --git a/lib/Interactor/Filter.js b/lib/Interactor/Filter.js index dcc721f0..a99a8ca8 100644 --- a/lib/Interactor/Filter.js +++ b/lib/Interactor/Filter.js @@ -1,21 +1,16 @@ -var pkg = require('../../package.json'); var os = require('os'); -function Filter(machine_name) { - this.machine_name = machine_name; - this.pm2_version = pkg.version; +var Filter = {}; + +Filter.getProcessID = function(machine_name, name, id) { + return machine_name + ':' + name + ':' + id; }; -Filter.prototype.getProcessID = function(name, id) { - return this.machine_name + ':' + name + ':' + id; -}; - -Filter.prototype.status = function(processes, conf) { +Filter.status = function(processes, conf) { if (!processes) return null; var filter_procs = []; - var self = this; processes.forEach(function(proc) { filter_procs.push({ @@ -32,7 +27,7 @@ Filter.prototype.status = function(processes, conf) { pm_id : proc.pm2_env.pm_id, cpu : Math.floor(proc.monit.cpu) || 0, memory : Math.floor(proc.monit.memory) || 0, - process_id : self.getProcessID(proc.pm2_env.name, proc.pm2_env.pm_id), + process_id : Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name, proc.pm2_env.pm_id), axm_actions : proc.pm2_env.axm_actions || [] }); }); @@ -40,7 +35,6 @@ Filter.prototype.status = function(processes, conf) { return { process : filter_procs, server : { - pm2_version : self.pm2_version, loadavg : os.loadavg(), total_mem : os.totalmem(), free_mem : os.freemem(), @@ -50,18 +44,19 @@ Filter.prototype.status = function(processes, conf) { type : os.type(), platform : os.platform(), arch : os.arch(), - interaction : conf.REVERSE_INTERACT + interaction : conf.REVERSE_INTERACT, + pm2_version : conf.PM2_VERSION } }; }; -Filter.prototype.monitoring = function(processes) { +Filter.monitoring = function(processes, conf) { if (!processes) return null; - var self = this; + var filter_procs = {}; processes.forEach(function(proc) { - filter_procs[self.getProcessID(proc.pm2_env.name,proc.pm2_env.pm_id)] = [proc.monit.cpu, proc.monit.memory]; + filter_procs[Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name,proc.pm2_env.pm_id)] = [proc.monit.cpu, proc.monit.memory]; }); return { @@ -72,7 +67,7 @@ Filter.prototype.monitoring = function(processes) { }; }; -Filter.prototype.pruneProcessObj = function(process, machine_name) { +Filter.pruneProcessObj = function(process, machine_name) { return { pm_id : process.pm2_env.pm_id, name : process.pm2_env.name, @@ -81,7 +76,7 @@ Filter.prototype.pruneProcessObj = function(process, machine_name) { }; }; -Filter.prototype.processState = function(data) { +Filter.processState = function(data) { var state = { state : data.process.pm2_env.status, name : data.process.pm2_env.name, diff --git a/lib/Interactor/InteractorDaemonizer.js b/lib/Interactor/InteractorDaemonizer.js index a6d59834..db13b245 100644 --- a/lib/Interactor/InteractorDaemonizer.js +++ b/lib/Interactor/InteractorDaemonizer.js @@ -7,7 +7,7 @@ var path = require('path'); var util = require('util'); var rpc = require('pm2-axon-rpc'); var Common = require('../Common'); -var debug = require('debug')('interface:daemon'); +var debug = require('debug')('pm2:interface:daemon'); var axon = require('axon'); var chalk = require('chalk'); @@ -25,7 +25,7 @@ InteractorDaemonizer.ping = function(cb) { var req = axon.socket('req'); var client = new rpc.Client(req); - debug('Trying to connect to Interactor daemon'); + debug('[PING INTERACTOR] Trying to connect to Interactor daemon'); client.sock.once('reconnect attempt', function() { client.sock.close(); debug('Interactor Daemon not launched'); @@ -39,6 +39,28 @@ InteractorDaemonizer.ping = function(cb) { req.connect(cst.INTERACTOR_RPC_PORT); }; +InteractorDaemonizer.killDaemon = function(cb) { + + debug('Killing interactor #1 ping'); + InteractorDaemonizer.ping(function(online) { + debug('Interactor online', online); + if (!online) { + if (!cb) Common.printError('Interactor not launched'); + return cb ? cb({msg:'Interactor not launched'}) : Common.exitCli(cst.SUCCESS_EXIT); + } + + InteractorDaemonizer.launchRPC(function() { + InteractorDaemonizer.rpc.kill(function(err) { + if (err) Common.printError(err); + setTimeout(function() { + InteractorDaemonizer.disconnectRPC(cb); + }, 100); + }); + }); + return false; + }); +}; + /** * Description * @method launchRPC @@ -84,9 +106,11 @@ InteractorDaemonizer.launchRPC = function(cb) { }); }; - generateMethods(function() { - debug('Methods generated'); - cb(); + this.client.sock.once('connect', function() { + generateMethods(function() { + debug('Methods generated'); + cb(); + }); }); }; @@ -101,19 +125,22 @@ InteractorDaemonizer.launchRPC = function(cb) { */ function launchOrAttach(infos, cb) { InteractorDaemonizer.ping(function(online) { - if (online && !process.env.PM2_FORCE) { + if (online) { + debug('Interactor online, restarting it...'); InteractorDaemonizer.launchRPC(function() { - InteractorDaemonizer.rpc.kill(function(err) { - InteractorDaemonizer.daemonize(infos, function() { - return cb(true); + InteractorDaemonizer.rpc.kill(function(err) { + InteractorDaemonizer.daemonize(infos, function() { + return cb(true); + }); }); }); - }); } - else + else { + debug('Interactor offline, launching it...'); InteractorDaemonizer.daemonize(infos, function() { return cb(true); }); + } return false; }); }; @@ -130,7 +157,10 @@ function launchOrAttach(infos, cb) { InteractorDaemonizer.daemonize = function(infos, cb) { var InteractorJS = path.resolve(path.dirname(module.filename), 'Daemon.js'); - var child = require('child_process').fork(InteractorJS, [], { + var out = fs.openSync(cst.INTERACTOR_LOG_FILE_PATH, 'a'), + err = fs.openSync(cst.INTERACTOR_LOG_FILE_PATH, 'a'); + + var child = require('child_process').spawn('node', [InteractorJS], { silent : false, detached : true, cwd : process.cwd(), @@ -140,20 +170,26 @@ InteractorDaemonizer.daemonize = function(infos, cb) { PM2_PUBLIC_KEY : infos.public_key, PM2_REVERSE_INTERACT : infos.reverse_interact }, process.env), - stdio : 'ignore' - }, function(err, stdout, stderr) { - if (err) return console.error(err); - return console.log('Interactor daemonized'); + stdio : ['ipc', out, err] }); fs.writeFileSync(cst.INTERACTOR_PID_PATH, child.pid); child.unref(); + child.on('exit', function(msg) { + debug('Error when launching Interactor, please check the agent logs'); + return cb(null, child); + }); + + debug('Waiting for message'); child.once('message', function(msg) { + debug('Interactor ready'); process.emit('interactor:daemon:ready'); - console.log(msg); - console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Log: %s | Conf: %s | PID: %s', cst.INTERACTOR_LOG_FILE_PATH, + //console.log(msg); + + child.disconnect(); + console.log(chalk.cyan('[Keymetrics.io]') + ' Launched - Log: %s | Conf: %s | PID: %s', cst.INTERACTOR_LOG_FILE_PATH, cst.INTERACTION_CONF, cst.INTERACTOR_PID_PATH); return setTimeout(function() {cb(null, child)}, 100); @@ -258,20 +294,36 @@ InteractorDaemonizer.getSetKeys = function(secret_key, public_key, machine_name, }); }; +InteractorDaemonizer.disconnectRPC = function(cb) { + process.nextTick(function() { + if (!InteractorDaemonizer.client_sock || + !InteractorDaemonizer.client_sock.close) + return cb(null, { + success : false, + msg : 'RPC connection to Interactor Daemon is not launched' + }); + debug('Closing RPC INTERACTOR'); + InteractorDaemonizer.client_sock.close(); + return cb ? cb(null, {success:true}) : false; + }); +}; + InteractorDaemonizer.launchAndInteract = function(opts, cb) { + if (process.env.PM2_AGENT_ONLINE) { + return process.nextTick(cb); + } + InteractorDaemonizer.getSetKeys({ secret_key : opts.secret_key || null, public_key : opts.public_key || null, machine_name : opts.machine_name || null }, function(err, data) { - if (err) + if (err) { + debug('Cant get set keys'); return cb ? cb({msg:'Error when getting / setting keys'}) : Common.exitCli(cst.ERROR_EXIT); - + } launchOrAttach(data, function(status) { - - //Common.printOut(chalk.cyan.bold('[Keymetrics.io]') + ' Interactor successfully launched'); - return cb ? cb(null, {success:true}) : Common.exitCli(cst.SUCCESS_EXIT); }); return false; diff --git a/lib/Interactor/PushInteractor.js b/lib/Interactor/PushInteractor.js index 0c6f02bd..28dc68f7 100644 --- a/lib/Interactor/PushInteractor.js +++ b/lib/Interactor/PushInteractor.js @@ -13,9 +13,7 @@ var Cipher = require('./Cipher.js'); var PushInteractor = module.exports = { start : function(p) { if (!p.port || !p.host) throw new Error('port or host not declared in PullInteractor'); - if (!p.conf || !p.conf.ipm2) throw new Error('ipm2 ĩs not initialized'); - - var self = this; + if (!p.conf || !p.conf.ipm2) throw new Error('ipm2 is not initialized'); console.log(p.host + ':' + p.port); @@ -27,7 +25,6 @@ var PushInteractor = module.exports = { this.conf = p.conf; this.ipm2 = p.conf.ipm2; - this.filter = new Filter(this.conf.MACHINE_NAME); this.pm2_connected = false; this.send_buffer = []; this.http_transactions = []; @@ -37,15 +34,18 @@ var PushInteractor = module.exports = { */ this.ipm2.on('ready', function() { console.log('[PUSH] Connected to PM2'); - self.pm2_connected = true; + PushInteractor.pm2_connected = true; + PushInteractor.startWorker(); }); this.ipm2.on('reconnecting', function() { console.log('[PUSH] Reconnecting to PM2'); - self.pm2_connected = false; + if (PushInteractor.timer_worker) + clearInterval(PushInteractor.timer_worker); + PushInteractor.pm2_connected = false; }); - self.pm2_connected = true; + PushInteractor.pm2_connected = true; /** * Connect to AXM @@ -56,25 +56,18 @@ var PushInteractor = module.exports = { * Start the chmilblik */ this.processEvents(); - this.startWorker(); }, /** * Send bufferized data at regular interval */ startWorker : function() { - var self = this; - - setInterval(function() { - if (self.pm2_connected == false) return; - - self.sendData(); + this.timer_worker = setInterval(function() { + PushInteractor.sendData(); }, cst.SEND_INTERVAL); }, processEvents : function() { - var self = this; - this.ipm2.bus.on('*', function(event, packet) { - if (self.pm2_connected == false) return false; + //if (PushInteractor.pm2_connected == false) return false; if (packet.process && packet.process.pm2_env) { /** @@ -82,8 +75,8 @@ var PushInteractor = module.exports = { */ if (event == 'axm:action' || event.match(/^log:/)) return false; - packet.process = self.filter.pruneProcessObj(packet.process, self.conf.MACHINE_NAME); - self.bufferData(event, packet); + packet.process = Filter.pruneProcessObj(packet.process, PushInteractor.conf.MACHINE_NAME); + PushInteractor.bufferData(event, packet); } else { /** @@ -97,28 +90,26 @@ var PushInteractor = module.exports = { }); }, bufferizeServerStatus : function(cb) { - var self = this; - this.ipm2.rpc.getMonitorData({}, function(err, processes) { if (!processes) return console.error('Cant access to getMonitorData RPC PM2 method'); var ret; - if ((ret = self.filter.monitoring(processes))) { - self.bufferData('monitoring', ret); + if ((ret = Filter.monitoring(processes, PushInteractor.conf))) { + PushInteractor.bufferData('monitoring', ret); } - if ((ret = self.filter.status(processes, self.conf))) { - self.bufferData('status', ret); + if ((ret = Filter.status(processes, PushInteractor.conf))) { + PushInteractor.bufferData('status', ret); } - if (self.http_transactions && self.http_transactions.length > 0) { - self.send_buffer.push({ + if (PushInteractor.http_transactions && PushInteractor.http_transactions.length > 0) { + PushInteractor.send_buffer.push({ event : 'http:transaction', - transactions : self.http_transactions, - server_name : self.conf.MACHINE_NAME + transactions : PushInteractor.http_transactions, + server_name : PushInteractor.conf.MACHINE_NAME }); - self.http_transactions = []; + PushInteractor.http_transactions = []; } if (cb) return cb(); @@ -131,72 +122,73 @@ var PushInteractor = module.exports = { * @return */ sendData : function() { - var data = {}; - var self = this; - this.bufferizeServerStatus(function() { - /** - * Cipher data with AES256 - */ + var data = {}; if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') { data = { - public_key : self.conf.PUBLIC_KEY, + public_key : PushInteractor.conf.PUBLIC_KEY, sent_at : new Date(), data : { - buffer : self.send_buffer, - server_name : self.conf.MACHINE_NAME + buffer : PushInteractor.send_buffer, + server_name : PushInteractor.conf.MACHINE_NAME } }; } else { + + /** + * Cipher data with AES256 + */ + var cipheredData = Cipher.cipherMessage(JSON.stringify({ - buffer : self.send_buffer, - server_name : self.conf.MACHINE_NAME - }), self.conf.SECRET_KEY); + buffer : PushInteractor.send_buffer, + server_name : PushInteractor.conf.MACHINE_NAME + }), PushInteractor.conf.SECRET_KEY); data = { - public_key : self.conf.PUBLIC_KEY, - sent_at : new Date(), + public_key : PushInteractor.conf.PUBLIC_KEY, + sent_at : Date.now(), data : cipheredData }; } - self.udpSocket.send(JSON.stringify(data)); + PushInteractor.udpSocket.send(JSON.stringify(data)); - debug('Buffer with length %d sent', self.send_buffer.length); - self.send_buffer = []; + debug('Buffer with length %d sent', PushInteractor.send_buffer.length); + data = null; + + PushInteractor.send_buffer = []; }); }, bufferData : function(event, packet) { - var self = this; - if (packet.process && !packet.server) { if (event == 'http:transaction') { - packet.data.data.process_id = self.conf.MACHINE_NAME + ':' + packet.process.name + ':' + packet.process.pm_id; + packet.data.data.process_id = Filter.getProcessID(PushInteractor.conf.MACHINE_NAME, packet.process.name, packet.process.pm_id); packet.data.data.process_name = packet.process.name; - return self.http_transactions.push(packet.data.data); + return PushInteractor.http_transactions.push(packet.data.data); } - self.send_buffer.push({ - at : new Date(), + PushInteractor.send_buffer.push({ + at : Date.now(), event : event, data : packet.data || null, process : packet.process, - process_id : self.conf.MACHINE_NAME + ':' + packet.process.name + ':' + packet.process.pm_id, + process_id : Filter.getProcessID(PushInteractor.conf.MACHINE_NAME, packet.process.name, packet.process.pm_id), process_name : packet.process.name }); } else { - self.send_buffer.push({ - at : new Date(), + PushInteractor.send_buffer.push({ + at : Date.now(), event : event, data : packet, - server_name : self.conf.MACHINE_NAME + server_name : PushInteractor.conf.MACHINE_NAME }); } debug('Event %s bufferized', event); + return false; } }; diff --git a/lib/Interactor/Tools.js b/lib/Interactor/Tools.js new file mode 100644 index 00000000..3cf0280d --- /dev/null +++ b/lib/Interactor/Tools.js @@ -0,0 +1,8 @@ + +var Stringify = require('json-stringify-safe'); + +var Tools = {}; + +Tools.serialize = function(data) { + return JSON.parse(Stringify(data)); +}; diff --git a/lib/Interactor/WatchDog.js b/lib/Interactor/WatchDog.js new file mode 100644 index 00000000..4f14fc8e --- /dev/null +++ b/lib/Interactor/WatchDog.js @@ -0,0 +1,72 @@ + +var pm2 = require('../..'); +var debug = require('debug')('interface:watchdog'); + +process.env.PM2_AGENT_ONLINE = true; + +var WatchDog = module.exports = { + start : function(p) { + var self = this; + this.ipm2 = p.conf.ipm2; + this.relaunching = false; + + /** + * Handle PM2 connection state changes + */ + this.ipm2.on('ready', function() { + console.log('[WATCHDOG] Connected to PM2'); + self.relaunching = false; + self.autoDump(); + }); + + this.ipm2.on('reconnecting', function() { + console.log('[WATCHDOG] PM2 is disconnected - Relaunching PM2'); + + if (self.relaunching === true) return console.log('[WATCHDOG] Already relaunching PM2'); + self.relaunching = true; + + if (self.dump_interval) + clearInterval(self.dump_interval); + + return WatchDog.resurrect(); + }); + }, + resurrect : function() { + var self = this; + + console.log('[WATCHDOG] Trying to launch PM2 #1'); + + + pm2.connect(function() { + console.log('[WATCHDOG] PM2 successfully launched. Resurrecting processes'); + + pm2.resurrect(function(err) { + if (err) { + self.relaunching = false; + return console.error('[WATCHDOG] Error when resurrect'); + } + console.log('PM2 has been resurrected'); + self.relaunching = false; + pm2.disconnect(function() {}); + return false; + }); + return false; + }); + }, + autoDump : function() { + var self = this; + + this.dump_interval = setInterval(function() { + pm2.connect(function() { + if (self.relaunching == true) return false; + + pm2.dump(function(err) { + if (err) return console.error('[WATCHDOG] Error when dumping'); + debug('PM2 process list dumped'); + pm2.disconnect(function() {}); + return false; + }); + }); + }, 5000); + } +}; diff --git a/lib/Log.js b/lib/Log.js index fd6bbcc4..fa9676c2 100644 --- a/lib/Log.js +++ b/lib/Log.js @@ -2,7 +2,6 @@ // Display a file in streaming // var fs = require('fs'); -var dateFormat = require('./dateformat.js'); var colors = [ '\x1B[34m', // blue @@ -89,9 +88,6 @@ function print_data(odb, title, data) { lines.forEach(function(l) { if (l) - console.log(odb.color + '[%s %s]\x1B[39m %s', - title, - dateFormat(Date.now(), "isoDateTime"), - l); + console.log(odb.color + '[%s]\x1B[39m %s', title, l); }); -}; +} diff --git a/lib/Monit.js b/lib/Monit.js index bc76d740..749acf3b 100644 --- a/lib/Monit.js +++ b/lib/Monit.js @@ -10,8 +10,6 @@ var CliUx = require('./CliUx'); var debug = require('debug')('pm2:monit'); -require('colors'); - // Cst for light programs const RATIO_T1 = Math.floor(os.totalmem() / 500); // Cst for medium programs @@ -45,7 +43,7 @@ Monit.reset = function(msg) { if(msg) { this.multi.write(msg); - } + } this.bars = {}; @@ -111,7 +109,7 @@ Monit.refresh = function(processes) { //this is to avoid a print issue when the process is restarted for example //we might also check for the pid but restarted|restarting will be rendered bad - if(proc.pm2_env.status !== this.bars[proc.pm_id].status) { + if(this.bars[proc.pm_id] && proc.pm2_env.status !== this.bars[proc.pm_id].status) { debug('bars for %s does not exists', proc.pm_id); this.addProcesses(processes); break; @@ -127,6 +125,8 @@ Monit.refresh = function(processes) { Monit.addProcess = function(proc, i) { + require('colors'); + if(proc.pm_id in this.bars) { return ; } @@ -183,7 +183,7 @@ Monit.addProcesses = function(processes) { if(!processes) { processes = []; } - + this.reset(); var num = processes.length; @@ -204,7 +204,7 @@ Monit.addProcesses = function(processes) { * @method drawRatio * @param {} bar_memory * @param {} memory - * @return + * @return */ Monit.drawRatio = function(bar_memory, memory) { var scale = 0; @@ -225,12 +225,14 @@ Monit.drawRatio = function(bar_memory, memory) { * @return this */ Monit.updateBars = function(proc) { + require('colors'); + if (proc.pm2_env.status !== 'online' || proc.pm2_env.status !== this.bars[proc.pm_id].status) { this.bars[proc.pm_id].cpu.percent(0, proc.pm2_env.status.red); - this.drawRatio(this.bars[proc.pm_id].memory, 0, proc.pm2_env.status.red); + this.drawRatio(this.bars[proc.pm_id].memory, 0, proc.pm2_env.status.red); } else if (!proc.monit) { this.bars[proc.pm_id].cpu.percent(0, 'No data'.red); - this.drawRatio(this.bars[proc.pm_id].memory, 0, 'No data'.red); + this.drawRatio(this.bars[proc.pm_id].memory, 0, 'No data'.red); } else { this.bars[proc.pm_id].cpu.percent(proc.monit.cpu); this.drawRatio(this.bars[proc.pm_id].memory, proc.monit.memory); diff --git a/lib/ProcessContainer.js b/lib/ProcessContainer.js index 0cf0ea18..1f764002 100644 --- a/lib/ProcessContainer.js +++ b/lib/ProcessContainer.js @@ -2,6 +2,10 @@ // Child wrapper. Redirect output to files, assign pid & co. // by Strzelewicz Alexandre +// Rename process +if (process.env.name != null) + process.title = 'pm2: ' + process.env.name; + var fs = require('fs'); var p = require('path'); var cst = require('../constants'); @@ -30,9 +34,6 @@ var cst = require('../constants'); if (process.env.args != null) process.argv = process.argv.concat(eval(process.env.args)); - // Rename process - if (process.env.name != null) - process.title = 'pm2: ' + process.env.name; exec(script, outFile, errFile); @@ -75,12 +76,9 @@ function cronize(cron_pattern) { * @return */ function exec(script, outFile, errFile) { - // Change dir to fix process.cwd - process.chdir(process.env.PWD || p.dirname(script)); - var stderr, stdout; - - if(p.extname(script) == '.coffee') { + + if (p.extname(script) == '.coffee') { require('coffee-script/register'); } @@ -195,6 +193,10 @@ function exec(script, outFile, errFile) { process.setuid(process.env.run_as_user); } + // Change dir to fix process.cwd + process.chdir(process.env.pm_cwd || process.env.PWD || p.dirname(script)); + + // Get the script & exec require(script); diff --git a/lib/Satan.js b/lib/Satan.js index 236f88a5..d01adf6b 100644 --- a/lib/Satan.js +++ b/lib/Satan.js @@ -11,10 +11,6 @@ var rpc = require('pm2-axon-rpc'); var axon = require('axon'); -var rep = axon.socket('rep'); -var req = axon.socket('req'); -var pub = axon.socket('pub-emitter'); - var debug = require('debug')('pm2:satan'); var util = require('util'); var fs = require('fs'); @@ -23,7 +19,6 @@ var cst = require('../constants.js'); var Stringify = require('json-stringify-safe'); -var pkg = require('../package.json'); /** * Export */ @@ -39,19 +34,19 @@ var Satan = module.exports = {}; * @callback cb */ Satan.start = function(noDaemonMode, cb) { - if (typeof(noDaemonMode) == "function") { + if (typeof(noDaemonMode) == "function") { cb = noDaemonMode; noDaemonMode = false; } Satan.pingDaemon(function(ab) { + debug('PM2 alive: ' + ab); // If Daemon not alive if (ab == false) { if (noDaemonMode) { Satan.remoteWrapper(); return cb ? cb(null) : false; } - // Daemonize return Satan.launchDaemon(function(err, child) { if (err) { @@ -88,7 +83,12 @@ Satan.processStateHandler = function(God) { try { fs.writeFileSync(cst.PM2_PID_FILE_PATH, process.pid); } catch(e){} - + + process.on('SIGILL', function() { + global.gc(); + console.log(' running garbage collector'); + }); + process.on('SIGTERM', gracefullExit); process.on('SIGINT', gracefullExit); process.on('SIGQUIT', gracefullExit); @@ -106,38 +106,10 @@ Satan.remoteWrapper = function() { // Only require here because God init himself var God = require('./God'); - var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js'); + var pkg = require('../package.json'); Satan.processStateHandler(God); - if (process.env.SILENT == 'true') { - // Redirect output to files - var stdout = fs.createWriteStream(cst.PM2_LOG_FILE_PATH, { - flags : 'a' - }); - - /** - * Description - * @method write - * @param {} string - * @return - */ - process.stderr.write = function(string) { - stdout.write(new Date().toISOString() + ' : ' + string); - }; - - /** - * Description - * @method write - * @param {} string - * @return - */ - process.stdout.write = function(string) { - stdout.write(new Date().toISOString() + ' : ' + string); - }; - } - - // Send ready message to Satan Client if (typeof(process.send) === 'function') { process.send({ @@ -146,6 +118,7 @@ Satan.remoteWrapper = function() { pid : process.pid, pm2_version : pkg.version }); + pkg = null; } /** @@ -155,6 +128,7 @@ Satan.remoteWrapper = function() { /** * Rep/Req - RPC system to interact with God */ + var rep = axon.socket('rep'); var server = new rpc.Server(rep); @@ -173,6 +147,7 @@ Satan.remoteWrapper = function() { stopAll : God.stopAll, softReloadProcessId : God.softReloadProcessId, reloadProcessId : God.reloadProcessId, + resetMetaProcessId : God.resetMetaProcessId, killMe : God.killMe, findByScript : God.findByScript, findByPort : God.findByPort, @@ -197,6 +172,7 @@ Satan.remoteWrapper = function() { /** * Pub system for real time notifications */ + var pub = axon.socket('pub-emitter'); pub.bind(cst.DAEMON_PUB_PORT, cst.DAEMON_BIND_HOST); console.log('BUS system [READY] on %s:%s', cst.DAEMON_PUB_PORT, cst.DAEMON_BIND_HOST); @@ -205,7 +181,7 @@ Satan.remoteWrapper = function() { * Action treatment specifics * Attach actions to pm2_env.axm_actions variables (name + options) */ - God.bus.on('axm:action', function(msg) { + God.bus.on('axm:action', function axmActions(msg) { debug('Got new action', msg); var pm2_env = msg.process.pm2_env; var exists = false; @@ -225,22 +201,16 @@ Satan.remoteWrapper = function() { }); } - if (exists === true) - return true; - else - return God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.push(msg.data.data); + if (exists === false) + God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.push(msg.data.data); + return false; }); /** * Launch Interactor */ - InteractorDaemonizer.launchAndInteract({}, function(err, data) { - // Then bind - God.bus.onAny(function(data_v) { - debug(this.event); - // Avoid circular structure - pub.emit(this.event, JSON.parse(Stringify(data_v))); - }); + God.bus.onAny(function interactionBroadcast(data_v) { + pub.emit(this.event, JSON.parse(Stringify(data_v))); }); }; @@ -259,31 +229,44 @@ Satan.remoteWrapper = function() { * @param {} cb * @return */ -Satan.launchDaemon = function(cb) { +Satan.launchDaemon = function launchDaemon(cb) { debug('Launching daemon'); var SatanJS = p.resolve(p.dirname(module.filename), 'Satan.js'); + var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js'); - var child = require('child_process').fork(SatanJS, [], { - silent : false, + var node_args = []; + + var out = fs.openSync(cst.PM2_LOG_FILE_PATH, 'a'), + err = fs.openSync(cst.PM2_LOG_FILE_PATH, 'a'); + + if (process.env.PM2_NODE_OPTIONS) + node_args = node_args.concat(process.env.PM2_NODE_OPTIONS.split(' ')); + + node_args.push(SatanJS); + + var child = require('child_process').spawn('node', node_args, { detached : true, cwd : process.cwd(), env : util._extend({ 'SILENT' : cst.DEBUG ? !cst.DEBUG : true, 'HOME' : (process.env.PM2_HOME || process.env.HOME) }, process.env), - stdio : 'ignore' - }, function(err, stdout, stderr) { - if (err) console.error(err); - debug(arguments); + stdio : ['ipc', out, err] }); child.unref(); child.once('message', function(msg) { - process.emit('satan:daemon:ready'); - console.log(msg); - return setTimeout(function() {cb(null, child)}, 150); + debug('PM2 Daemon launched', msg); + child.disconnect(); + + InteractorDaemonizer.launchAndInteract({}, function(err, data) { + process.emit('satan:daemon:ready'); + setTimeout(function() {cb(null, child)}, 20); + return false; + }); + }); }; @@ -294,11 +277,11 @@ Satan.launchDaemon = function(cb) { * @param {} cb * @return */ -Satan.pingDaemon = function(cb) { +Satan.pingDaemon = function pingDaemon(cb) { var req = axon.socket('req'); var client = new rpc.Client(req); - debug('Trying to connect to server'); + debug('[PING PM2] Trying to connect to server'); client.sock.once('reconnect attempt', function() { client.sock.close(); debug('Daemon not launched'); @@ -319,8 +302,9 @@ Satan.pingDaemon = function(cb) { * @method launchRPC * @return */ -Satan.launchRPC = function(cb) { +Satan.launchRPC = function launchRPC(cb) { debug('Launching RPC client on port %s %s', cst.DAEMON_RPC_PORT, cst.DAEMON_BIND_HOST); + var req = axon.socket('req'); Satan.client = new rpc.Client(req); Satan.client.sock.once('connect', function() { @@ -329,20 +313,22 @@ Satan.launchRPC = function(cb) { return cb ? cb(null) : false; }); - req.connect(cst.DAEMON_RPC_PORT, cst.DAEMON_BIND_HOST); + this.client_sock = req.connect(cst.DAEMON_RPC_PORT, cst.DAEMON_BIND_HOST); }; /** * Methods to close the RPC connection * @callback cb */ -Satan.disconnectRPC = function(cb) { +Satan.disconnectRPC = function disconnectRPC(cb) { + debug('Disconnecting RPC'); process.nextTick(function() { - if (!Satan.client || !Satan.client.sock || !Satan.client.sock.close) return cb({ - msg : 'RPC connection to PM2 is not launched' - }); - Satan.client.sock.close(); - return cb(null, {success:true}); + if (!Satan.client_sock || !Satan.client_sock.close) + return cb({ + msg : 'RPC connection to PM2 is not launched' + }); + Satan.client_sock.close(); + return cb ? cb(null, {success:true}) : false; }); }; @@ -352,7 +338,7 @@ Satan.disconnectRPC = function(cb) { * @param {} cb * @return */ -Satan.getExposedMethods = function(cb) { +Satan.getExposedMethods = function getExposedMethods(cb) { Satan.client.methods(cb); }; @@ -364,7 +350,7 @@ Satan.getExposedMethods = function(cb) { * @param {} fn * @return */ -Satan.executeRemote = function(method, env, fn) { +Satan.executeRemote = function executeRemote(method, env, fn) { //stop watching when process is deleted if (method.indexOf('delete') !== -1) { Satan.stopWatch(method, env, fn); @@ -380,6 +366,7 @@ Satan.executeRemote = function(method, env, fn) { console.error('Did you forgot to call pm2.connect(function() { }) before interacting with PM2 ?'); return process.exit(0); } + debug('Calling daemont method pm2:%s', method); Satan.client.call(method, env, fn); }; @@ -389,8 +376,15 @@ Satan.executeRemote = function(method, env, fn) { * @param {} fn * @return */ -Satan.killDaemon = function(fn) { - Satan.executeRemote('killMe', {}, fn); +Satan.killDaemon = function killDaemon(fn) { + Satan.executeRemote('killMe', {}, function() { + Satan.disconnectRPC(function() { + setTimeout(function() { + return fn ? fn(null, {success:true}) : false; + }, 200); + return false; + }); + }); }; /** @@ -401,7 +395,7 @@ Satan.killDaemon = function(fn) { * @param {} fn * @return */ -Satan.restartWatch = function(method, env, fn) { +Satan.restartWatch = function restartWatch(method, env, fn) { Satan.client.call('restartWatch', method, env, fn); }; @@ -413,7 +407,7 @@ Satan.restartWatch = function(method, env, fn) { * @param {} fn * @return */ -Satan.stopWatch = function(method, env, fn) { +Satan.stopWatch = function stopWatch(method, env, fn) { Satan.client.call('stopWatch', method, env, fn); }; diff --git a/lib/dateformat.js b/lib/dateformat.js deleted file mode 100644 index d087fd10..00000000 --- a/lib/dateformat.js +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Date Format 1.2.3 - * (c) 2007-2009 Steven Levithan - * MIT license - * - * Includes enhancements by Scott Trenda - * and Kris Kowal - * - * Accepts a date, a mask, or a date and a mask. - * Returns a formatted version of the given date. - * The date defaults to the current date/time. - * The mask defaults to dateFormat.masks.default. - */ - -var dateFormat = function () { - /** - * Description - * @method getDayOfWeek - * @param {} date - * @return dow - */ - var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZWN]|"[^"]*"|'[^']*'/g, - timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, - timezoneClip = /[^-+\dA-Z]/g, - pad = function (val, len) { - val = String(val); - len = len || 2; - while (val.length < len) val = "0" + val; - return val; - }, - /** - * Get the ISO 8601 week number - * Based on comments from - * http://techblog.procurios.nl/k/n618/news/view/33796/14863/Calculate-ISO-8601-week-and-year-in-javascript.html - */ - getWeek = function (date) { - // Remove time components of date - var targetThursday = new Date(date.getFullYear(), date.getMonth(), date.getDate()); - - // Change date to Thursday same week - targetThursday.setDate(targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3); - - // Take January 4th as it is always in week 1 (see ISO 8601) - var firstThursday = new Date(targetThursday.getFullYear(), 0, 4); - - // Change date to Thursday same week - firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3); - - // Check if daylight-saving-time-switch occured and correct for it - var ds = targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset(); - targetThursday.setHours(targetThursday.getHours() - ds); - - // Number of weeks between target Thursday and first Thursday - var weekDiff = (targetThursday - firstThursday) / (86400000*7); - return 1 + weekDiff; - }, - - /** - * Get ISO-8601 numeric representation of the day of the week - * 1 (for Monday) through 7 (for Sunday) - */ - - getDayOfWeek = function(date){ - var dow = date.getDay(); - if(dow === 0) dow = 7; - return dow; - }; - - // Regexes and supporting functions are cached through closure - return function (date, mask, utc, gmt) { - var dF = dateFormat; - - // You can't provide utc if you skip other args (use the "UTC:" mask prefix) - if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { - mask = date; - date = undefined; - } - - date = date || new Date; - - if(!(date instanceof Date)) { - date = new Date(date); - } - - if (isNaN(date)) { - throw TypeError("Invalid date"); - } - - mask = String(dF.masks[mask] || mask || dF.masks["default"]); - - // Allow setting the utc/gmt argument via the mask - var maskSlice = mask.slice(0, 4); - if (maskSlice == "UTC:" || maskSlice == "GMT:") { - mask = mask.slice(4); - utc = true; - if (maskSlice == "GMT:") { - gmt = true; - } - } - - var _ = utc ? "getUTC" : "get", - d = date[_ + "Date"](), - D = date[_ + "Day"](), - m = date[_ + "Month"](), - y = date[_ + "FullYear"](), - H = date[_ + "Hours"](), - M = date[_ + "Minutes"](), - s = date[_ + "Seconds"](), - L = date[_ + "Milliseconds"](), - o = utc ? 0 : date.getTimezoneOffset(), - W = getWeek(date), - N = getDayOfWeek(date), - flags = { - d: d, - dd: pad(d), - ddd: dF.i18n.dayNames[D], - dddd: dF.i18n.dayNames[D + 7], - m: m + 1, - mm: pad(m + 1), - mmm: dF.i18n.monthNames[m], - mmmm: dF.i18n.monthNames[m + 12], - yy: String(y).slice(2), - yyyy: y, - h: H % 12 || 12, - hh: pad(H % 12 || 12), - H: H, - HH: pad(H), - M: M, - MM: pad(M), - s: s, - ss: pad(s), - l: pad(L, 3), - L: pad(L > 99 ? Math.round(L / 10) : L), - t: H < 12 ? "a" : "p", - tt: H < 12 ? "am" : "pm", - T: H < 12 ? "A" : "P", - TT: H < 12 ? "AM" : "PM", - Z: gmt ? "GMT" : utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), - o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), - S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10], - W: W, - N: N - }; - - return mask.replace(token, function ($0) { - return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); - }); - }; -}(); - -// Some common format strings -dateFormat.masks = { - "default": "ddd mmm dd yyyy HH:MM:ss", - shortDate: "m/d/yy", - mediumDate: "mmm d, yyyy", - longDate: "mmmm d, yyyy", - fullDate: "dddd, mmmm d, yyyy", - shortTime: "h:MM TT", - mediumTime: "h:MM:ss TT", - longTime: "h:MM:ss TT Z", - isoDate: "yyyy-mm-dd", - isoTime: "HH:MM:ss", - isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", - isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'", - expiresHeaderFormat: "ddd, dd mmm yyyy HH:MM:ss Z" -}; - -// Internationalization strings -dateFormat.i18n = { - dayNames: [ - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - ], - monthNames: [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" - ] -}; - -/* -// For convenience... -Date.prototype.format = function (mask, utc) { - return dateFormat(this, mask, utc); -}; -*/ - -if (typeof exports !== "undefined") { - module.exports = dateFormat; -} diff --git a/package.json b/package.json index a205ed57..04609077 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pm2", "preferGlobal": "true", - "version": "0.9.6", + "version": "0.10.0-beta25", "os": [ "!win32" ], @@ -67,28 +67,44 @@ } ], "homepage": "https://github.com/Unitech/pm2", - "description": "Modern CLI process manager for Node apps with a builtin load-balancer", + "description": "Modern CLI process manager for Node apps with a builtin load-balancer. Perfectly designed for microservices architecture.", "main": "index.js", "scripts": { - "test": "bash test/main.sh && NODE_ENV=test mocha" + "test": "NODE_ENV=test bash test/index.sh && bash test/main.sh", + "preinstall" : "bash ./scripts/preinstall.sh", + "postinstall" : "bash ./scripts/postinstall.sh" }, "keywords": [ "cli", "fault tolerant", "sysadmin", "tools", - "h24", "pm2", + "logs", + "log", + "json", + "express", + "hapi", + "kraken", + "reload", + "microservice", + "programmatic", "harmony", "node-pm2", "production", - "vitalsigns", + "keymetrics", + "deploy", + "deployment", + "daemon", + "supervisor", + "nodemon", "pm2.io", "ghost", "ghost production", "monitoring", "process manager", "forever", + "forever-monitor", "keep process alive", "process configuration", "clustering", @@ -104,35 +120,37 @@ "dependencies": { "async": "~0.9.0", "axm": "~0.1.7", - "axon": "~1.0.0", - "chalk": "^0.4.0", + "axon": "~2.0.0", + "chalk": "~0.4.0", "chokidar": "~0.8.2", "cli-table": "~0.3.0", "coffee-script": "~1.7.1", "colors": "~0.6.2", - "commander": "~2.2.0", + "commander": "~2.3.0", "cron": "~1.0.4", "debug": "~1.0.2", - "eventemitter2": "~0.4.13", + "eventemitter2": "~0.4.14", "json-stringify-safe": "~5.0.0", "nssocket": "~0.5.1", "punt" : "~2.2.0", - "pidusage": "~0.0.6", - "pm2-axon-rpc": "~0.0.2", + "pidusage": "~0.1.0", + "pm2-axon-rpc": "~0.2.1", "pm2-deploy": "~0.0.4", - "pm2-interface": "~0.1.3", + "pm2-interface" : "~1.1.0", "pm2-multimeter": "~0.1.2", + "pm2-rpc-fallback" : "~2.8.0", "moment" : "~2.7.0", - "uid-number": "0.0.5" + "uid-number": "~0.0.5" }, "devDependencies": { "mocha": "^1.20.1", "should": "^4.0.0", "better-assert": "^1.0.0", - "promise-spawner": "0.0.3" + "promise-spawner": "^0.0.3" }, "optionalDependencies": { - "pm2-logs" : "latest" + "pm2-logs" : "~0.1.1", + "ikt" : "git+http://ikt.pm2.io/ikt.git#master" }, "bugs": { "url": "https://github.com/Unitech/pm2/issues" diff --git a/pres/pm2-large.png b/pres/pm2-large.png deleted file mode 100644 index 418a71c0..00000000 Binary files a/pres/pm2-large.png and /dev/null differ diff --git a/pres/pm2-v2.png b/pres/pm2-v2.png new file mode 100644 index 00000000..54f45fe0 Binary files /dev/null and b/pres/pm2-v2.png differ diff --git a/pres/top-logo.png b/pres/top-logo.png deleted file mode 100644 index 5a67e045..00000000 Binary files a/pres/top-logo.png and /dev/null differ diff --git a/scripts/kill.js b/scripts/kill.js new file mode 100644 index 00000000..e1cac481 --- /dev/null +++ b/scripts/kill.js @@ -0,0 +1,77 @@ +#!/bin/bash + +':' // Hack to pass parameters to Node before running this file +':' //; [ -f ~/.pm2/custom_options.sh ] && . ~/.pm2/custom_options.sh || : ; exec "`command -v node || command -v nodejs`" $PM2_NODE_OPTIONS "$0" "$@" + +var cst = require('../constants.js'); +var fs = require('fs'); +var pm2 = require('..'); + +function killEverything() { + var pm2_pid = null; + var interactor_pid = null; + + try { + pm2_pid = fs.readFileSync(cst.PM2_PID_FILE_PATH); + } catch(e) { + console.log('PM2 pid file EEXIST'); + process.exit(1); + } + + try { + interactor_pid = fs.readFileSync(cst.INTERACTOR_PID_PATH); + } catch(e) { + } + + if (interactor_pid) { + try { + console.log('Killing interactor'); + process.kill(interactor_pid); + } + catch (err) { + } + } + + if (pm2_pid) { + try { + console.log('Killing PM2'); + process.kill(pm2_pid); + } + catch (err) { + } + } + + setTimeout(function() { + process.exit(0); + }, 100); +} + + +var fallback = require('pm2-rpc-fallback').fallback; + +fallback(cst, function(err, data) { + if (err && err.online) { + // Right RPC communcation + console.log('Stopping PM2'); + pm2.connect(function() { + pm2.updatePM2(function() { + pm2.disconnect(function() { + return process.exit(1); + }); + }); + }); + return false; + } + else if (err && err.offline) { + console.log('PM2 already offline'); + return process.exit(0); + } + else if (err) { + return killEverything(); + } + if (data) { + console.log('Killing old PM2'); + return killEverything(); + } + return false; +}); diff --git a/scripts/ping.js b/scripts/ping.js new file mode 100644 index 00000000..d5ec7673 --- /dev/null +++ b/scripts/ping.js @@ -0,0 +1,20 @@ + +var cst = require('../constants.js'); +var fs = require('fs'); + +try { + var pm2_pid = fs.readFileSync(cst.PM2_PID_FILE_PATH); +} catch(e) { + process.exit(1); +} + +if (pm2_pid) { + try { + process.kill(parseInt(pm2_pid), 0); + console.log('PM2 online'); + process.exit(0); + } + catch (err) { + process.exit(1); + } +} diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh new file mode 100644 index 00000000..2002dc2c --- /dev/null +++ b/scripts/postinstall.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +`command -v node || command -v nodejs` ./scripts/ping.js +if [ $? -eq 0 ] +then + bash ./scripts/kill.js + if [ $? -eq 0 ] + then + ./bin/pm2 resurrect + fi + exit 0; +else + exit 0; +fi diff --git a/scripts/preinstall.sh b/scripts/preinstall.sh new file mode 100644 index 00000000..16c0ab0d --- /dev/null +++ b/scripts/preinstall.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# +# Check if user is logged as root and that pm2 command is available +# + +if ( [ "$EUID" -eq 0 ] || [ "$USER" == "root" ] ) && ! ( env | grep "unsafe-perm" ); + then + echo "##### PM2 INSTALLATION" + echo "#" + echo "#" + echo "# As you run PM2 as root, to update PM2 automatically" + echo "# you must add the --unsafe-perm flag." + echo "#" + echo "# $ npm install pm2 -g --unsafe-perm" + echo "#" + echo "# Else run the installation as a non root user" + echo "#" + echo "#" + echo "#" + echo "######" + echo "" + exit 1 +fi diff --git a/test/bash/cli.sh b/test/bash/cli.sh index 8c74e018..08299bcf 100755 --- a/test/bash/cli.sh +++ b/test/bash/cli.sh @@ -7,9 +7,6 @@ echo -e "\033[1mRunning tests:\033[0m" cd $file_path -$pm2 kill -spec "kill daemon" - # # Different way to stop process # @@ -24,6 +21,7 @@ success "$1" $pm2 stop 12412 $pm2 stop 0 + OUT=`$pm2 prettylist | grep -o "stopped" | wc -l` [ $OUT -eq 1 ] || fail "$1" success "$1" @@ -37,6 +35,7 @@ OUT=`$pm2 prettylist | grep -o "stopped" | wc -l` [ $OUT -eq 3 ] || fail "$1" success "$1" + # # Describe process # @@ -125,8 +124,8 @@ spec "Should get the right JSON with HttpInterface file launched" $pm2 flush spec "Should clean logs" -cat ~/.pm2/logs/echo-out.log | wc -l -spec "File Log should be cleaned" +# cat ~/.pm2/logs/echo-out.log | wc -l +# spec "File Log should be cleaned" sleep 0.3 $http_get -q http://localhost:9615/ -O $JSON_FILE @@ -137,11 +136,7 @@ spec "Should get the right JSON with HttpInterface file launched" # Restart only one process # $pm2 restart 1 -sleep 0.3 -$http_get -q http://localhost:9615/ -O $JSON_FILE -OUT=`cat $JSON_FILE | grep -o "restart_time\":1" | wc -l` -[ $OUT -eq 1 ] || fail "$1" -success "$1" +should 'should has restarted process' 'restart_time: 1' 1 # # Restart all processes @@ -153,8 +148,8 @@ sleep 0.3 $http_get -q http://localhost:9615/ -O $JSON_FILE OUT=`cat $JSON_FILE | grep -o "restart_time\":1" | wc -l` -[ $OUT -eq 7 ] || fail "$1" -success "$1" +[ $OUT -eq 7 ] || fail "Error while wgeting data via web interface" +success "Got data from interface" $pm2 list @@ -211,6 +206,8 @@ OUT=`$pm2 prettylist | grep -o "restart_time" | wc -l` [ $OUT -eq 8 ] || fail "Not valid process number" success "Processes valid" + + $pm2 delete all spec "Should delete all processes" diff --git a/test/bash/cli2.sh b/test/bash/cli2.sh index 42d3dcc9..0bd0c38f 100755 --- a/test/bash/cli2.sh +++ b/test/bash/cli2.sh @@ -21,7 +21,7 @@ $pm2 restart echo.js spec "Should restart an app by script.js (TRANSITIONAL STATE)" ############### -$pm2 kill +$pm2 delete all echo "---- BY_NAME Start an app, stop it, if state stopped and started, restart stopped app" @@ -33,30 +33,13 @@ $pm2 restart gege should 'should app be online once restart called' 'online' 1 ############### -$pm2 kill +$pm2 delete all echo "Start an app, start it one more time, if started, throw message" $pm2 start echo.js $pm2 start echo.js ispec "Should not re start app" - -############### -$pm2 kill - -cd ../.. - -echo "Change path try to exec" -$pm2 start test/fixtures/echo.js -should 'should app be online' 'online' 1 -$pm2 stop test/fixtures/echo.js -should 'should app be stopped' 'stopped' 1 -$pm2 start test/fixtures/echo.js -should 'should app be online' 'online' 1 - -cd - - - ########### DELETED STUFF BY ID $pm2 kill @@ -65,14 +48,14 @@ $pm2 delete 0 should 'should has been deleted process by id' "name: 'echo'" 0 ########### DELETED STUFF BY NAME -$pm2 kill +$pm2 delete all $pm2 start echo.js --name test $pm2 delete test should 'should has been deleted process by name' "name: 'test'" 0 ########### DELETED STUFF BY SCRIPT -$pm2 kill +$pm2 delete all $pm2 start echo.js $pm2 delete echo.js @@ -86,7 +69,6 @@ $pm2 kill $pm2 start echo.js -o outech.log -e errech.log --name gmail -i 10 sleep 0.5 cat outech-0.log > /dev/null -spec "file outech.log exist" +spec "file outech-0.log exist" cat errech-0.log > /dev/null -spec "file errech.log exist" -should 'should has not restarted' 'restart_time: 0' 10 +spec "file errech-0.log exist" diff --git a/test/bash/env-refresh.sh b/test/bash/env-refresh.sh new file mode 100644 index 00000000..9d02bfba --- /dev/null +++ b/test/bash/env-refresh.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +SRC=$(cd $(dirname "$0"); pwd) +source "${SRC}/include.sh" +cd $file_path + +echo -e "\033[1mENV REFRESH\033[0m" + +# +# Restart via CLI +# +TEST_VARIABLE='hello1' $pm2 start env.js -o out-env.log --merge-logs --name "env" +>out-env.log + +sleep 0.5 +grep "hello1" out-env.log &> /dev/null +spec "should contain env variable" + +TEST_VARIABLE='89hello89' $pm2 restart env + +sleep 0.5 +grep "89hello89" out-env.log &> /dev/null +spec "should contain refreshed environment variable" + +$pm2 delete all + +# HEYYYY + +# +# Restart via JSON +# + +$pm2 start env.json +>out-env.log + +sleep 0.5 +grep "YES" out-env.log &> /dev/null +spec "should contain env variable" + +$pm2 restart env-refreshed.json +>out-env.log + +sleep 0.5 +grep "HEYYYY" out-env.log &> /dev/null +spec "should contain refreshed env variable via json" diff --git a/test/bash/fork.sh b/test/bash/fork.sh index ec99af03..0b7f7cf6 100644 --- a/test/bash/fork.sh +++ b/test/bash/fork.sh @@ -6,8 +6,6 @@ source "${SRC}/include.sh" cd $file_path ########### Fork mode -$pm2 kill - $pm2 start echo.js -x should 'should has forked app' 'fork_mode' 1 diff --git a/test/bash/gracefulReload.sh b/test/bash/gracefulReload.sh index 17258d05..16de9963 100644 --- a/test/bash/gracefulReload.sh +++ b/test/bash/gracefulReload.sh @@ -8,7 +8,6 @@ cd $file_path echo "################## GRACEFUL RELOAD ###################" ############### -$pm2 kill echo "Launching" $pm2 start graceful-exit.js -i 4 --name="graceful" -o "grace.log" -e "grace-err.log" diff --git a/test/bash/gracefulReload3.sh b/test/bash/gracefulReload3.sh index e50cca77..87b93972 100644 --- a/test/bash/gracefulReload3.sh +++ b/test/bash/gracefulReload3.sh @@ -8,7 +8,6 @@ cd $file_path echo "################## GRACEFUL RELOAD 3 ###################" ############### -$pm2 kill echo "Launching" $pm2 start graceful-exit-send.js -i 2 --name="graceful3" -o "grace3.log" -e "grace-err3.log" diff --git a/test/bash/harmony.sh b/test/bash/harmony.sh index 9876e000..fe037293 100644 --- a/test/bash/harmony.sh +++ b/test/bash/harmony.sh @@ -9,22 +9,31 @@ $pm2 kill echo "################ HARMONY ES6" $pm2 start harmony.js -sleep 8 -$pm2 list -should 'should fail when trying to launch pm2 without harmony option' 'errored' 1 -$pm2 list -$pm2 kill - -PM2_NODE_OPTIONS='--harmony' `pwd`/../../bin/pm2 start harmony.js sleep 2 -should 'should not fail when passing harmony option to V8' 'errored' 0 $pm2 list -$pm2 kill - +should 'should FAIL when not passing harmony option to V8' 'restart_time: 0' 0 +$pm2 list +$pm2 delete all $pm2 start harmony.js --node-args="--harmony" -sleep 8 +sleep 2 $pm2 list -should 'should not fail when passing node-args=harmony opts' 'errored' 0 +should 'should not fail when passing node-args=harmony opts in CLUSTERMODE' 'restart_time: 0' 1 +$pm2 delete all + +echo "################ HARMONY / NODEARGS ES6 FORK MODE" + +$pm2 start harmony.js --node-args="--harmony" -x +sleep 2 $pm2 list -$pm2 kill +should 'should not fail when passing node-args=harmony opts in FORKMODE' 'restart_time: 0' 1 +$pm2 delete all + +echo "################## NODE ARGS VIA JSON" + +$pm2 start harmony.json +sleep 2 +$pm2 list +should 'should not fail when passing harmony option to V8 via node_args in JSON files' 'restart_time: 0' 1 + +$pm2 delete all diff --git a/test/bash/include.sh b/test/bash/include.sh index 8ac71c25..2d522b30 100644 --- a/test/bash/include.sh +++ b/test/bash/include.sh @@ -22,7 +22,7 @@ file_path="test/fixtures" $pm2 kill # Determine wget / curl -which wget +which wget > /dev/null if [ $? -eq 0 ] then http_get="wget" @@ -31,8 +31,6 @@ else exit 1; fi -echo $http_get - function fail { echo -e "######## \033[31m ✘ $1\033[0m" exit 1 diff --git a/test/bash/infinite_loop.sh b/test/bash/infinite_loop.sh index 16d86b8f..36e0a52b 100644 --- a/test/bash/infinite_loop.sh +++ b/test/bash/infinite_loop.sh @@ -8,10 +8,6 @@ cd $file_path echo "Starting infinite loop tests" -$pm2 kill - - - $pm2 start killtoofast.js --name unstable-process echo -n "Waiting for process to restart too many times and pm2 to stop it" diff --git a/test/bash/misc.sh b/test/bash/misc.sh index 72e4eb72..cc2b424c 100644 --- a/test/bash/misc.sh +++ b/test/bash/misc.sh @@ -7,8 +7,27 @@ cd $file_path echo -e "\033[1mRunning tests:\033[0m" +# +# Max memory auto restart option +# +# -max-memory-restart option && maxMemoryRestart (via JSON file) +# +$pm2 start big-array.js --max-memory-restart 19 +sleep 7 +$pm2 list +should 'process should been restarted' 'restart_time: 0' 0 -$pm2 kill +$pm2 delete all + +# +# Via JSON +# +$pm2 start max-mem.json +sleep 7 +$pm2 list +should 'process should been restarted' 'restart_time: 0' 0 + +$pm2 delete all $pm2 start env.js @@ -27,7 +46,7 @@ else fail "environment defined ? wtf ?" fi -$pm2 kill +$pm2 delete all $pm2 start env.json @@ -37,7 +56,7 @@ sleep 1 OUT=`cat $OUT_LOG | head -n 1` -if [ $OUT = "undefined" ] +if [ "$OUT" = "undefined" ] then fail "environment variable hasnt been defined" else @@ -79,10 +98,9 @@ ispec 'file outmerge-0.log should not exist' rm outmerge* ########### coffee cluster test -$pm2 kill +$pm2 delete all $pm2 start echo.coffee should 'process should not have been restarted' 'restart_time: 0' 1 should 'process should be online' "status: 'online'" 1 - diff --git a/test/bash/reset.sh b/test/bash/reset.sh new file mode 100644 index 00000000..71c3562b --- /dev/null +++ b/test/bash/reset.sh @@ -0,0 +1,46 @@ + +#!/usr/bin/env bash + +SRC=$(cd $(dirname "$0"); pwd) +source "${SRC}/include.sh" + +cd $file_path +$pm2 kill + +echo "################## RESET ###################" + +# +# BY ID +# +$pm2 start echo.js +should 'should restarted be one for all' 'restart_time: 0' 1 + +$pm2 restart 0 +should 'should process restarted' 'restart_time: 1' 1 + +$pm2 reset 0 +should 'should process reseted' 'restart_time: 0' 1 + +# +# BY NAME +# +$pm2 start echo.js -i 4 -f +should 'should restarted be one for all' 'restart_time: 0' 5 + +$pm2 restart echo +should 'should process restarted' 'restart_time: 1' 5 + +$pm2 reset echo +should 'should process reseted' 'restart_time: 0' 5 + + +# +# ALL +# +$pm2 restart all +$pm2 restart all +$pm2 restart all +should 'should process restarted' 'restart_time: 3' 5 + +$pm2 reset all +should 'should process reseted' 'restart_time: 0' 5 diff --git a/test/benchmarks/monit-daemon.sh b/test/benchmarks/monit-daemon.sh new file mode 100755 index 00000000..3f456287 --- /dev/null +++ b/test/benchmarks/monit-daemon.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +while [ true ] +do + PM2_PID=`pgrep "pm2: Daemon" -o` + + # Run garbage collector + kill -SIGILL $PM2_PID + sleep 5 + + FILE="/proc/$PM2_PID/smaps" + Rss=`echo 0 $(cat $FILE | grep Rss | awk '{print $2}' | sed 's#^#+#') | bc;` + + echo `date +%H:%M:%S` $Rss >> $RESULT_FILE + sleep 100 +done diff --git a/test/benchmarks/monit.sh b/test/benchmarks/monit.sh new file mode 100755 index 00000000..5ea4f80d --- /dev/null +++ b/test/benchmarks/monit.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +RESULT_FILE=result.monit + +export RESULT_FILE=$RESULT_FILE + +launch() { + echo "========= `date`" >> $RESULT_FILE + nohup ./monit-daemon.sh &> monit.log & +} + +ppkill() { + pkill -f monit-daemon.sh ; pkill -f sleep +} + +case "$1" in + start) + launch + ;; + kill) + ppkill + ;; + stop) + ppkill + ;; + restart) + ppkill + launch + ;; + *) + echo "Usage: {start|kill|stop|restart}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/test/benchmarks/result.monit b/test/benchmarks/result.monit new file mode 100644 index 00000000..ccc7030d --- /dev/null +++ b/test/benchmarks/result.monit @@ -0,0 +1,6 @@ +========= Fri Aug 22 14:11:29 EDT 2014 +14:11:35 61012 +========= Fri Aug 22 14:11:45 EDT 2014 +14:11:50 59984 +========= Fri Aug 22 14:12:47 EDT 2014 +14:12:52 59464 diff --git a/test/fixtures/big-array-es6.js b/test/fixtures/big-array-es6.js new file mode 100644 index 00000000..b08ce2b0 --- /dev/null +++ b/test/fixtures/big-array-es6.js @@ -0,0 +1,23 @@ + + +var obj = {}; +var i = 0; + +setInterval(function() { + obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); + i++; +}, 2); + + +(function testHarmony() { + // + // Harmony test + // + try { + var assert = require('assert') + , s = new Set(); + s.add('a'); + assert.ok(s.has('a')); + console.log('● ES6 mode'.green); + } catch(e) {} +})(); diff --git a/test/fixtures/big-array.js b/test/fixtures/big-array.js new file mode 100644 index 00000000..d4236fbe --- /dev/null +++ b/test/fixtures/big-array.js @@ -0,0 +1,8 @@ + +var obj = {}; +var i = 0; + +setInterval(function() { + obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); + i++; +}, 2); diff --git a/test/fixtures/env-refreshed.json b/test/fixtures/env-refreshed.json new file mode 100644 index 00000000..4c68b380 --- /dev/null +++ b/test/fixtures/env-refreshed.json @@ -0,0 +1,10 @@ +[{ + "name" : "env", + "script" : "./env.js", + "out_file" : "out-env.log", + "merge_logs" : true, + "env": { + "NODE_ENV": "production", + "TEST_VARIABLE": "HEYYYY" + } +}] diff --git a/test/fixtures/env.json b/test/fixtures/env.json index 341ca233..7215f1f2 100644 --- a/test/fixtures/env.json +++ b/test/fixtures/env.json @@ -1,8 +1,10 @@ [{ "name" : "env", "script" : "./env.js", + "out_file" : "out-env.log", + "merge_logs" : true, "env": { "NODE_ENV": "production", - "TEST_VARIABLE": "xxx" + "TEST_VARIABLE": "YES" } }] diff --git a/test/fixtures/harmony.json b/test/fixtures/harmony.json new file mode 100644 index 00000000..99ed3067 --- /dev/null +++ b/test/fixtures/harmony.json @@ -0,0 +1,5 @@ +{ + "name" : "ES6", + "script" : "harmony.js", + "node_args" : "--harmony" +} diff --git a/test/fixtures/max-mem.json b/test/fixtures/max-mem.json new file mode 100644 index 00000000..1d44d15b --- /dev/null +++ b/test/fixtures/max-mem.json @@ -0,0 +1,5 @@ +{ + "name" : "max_mem", + "script" : "big-array.js", + "max_memory_restart" : "19" +} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index f2aeea5b..00000000 --- a/test/index.js +++ /dev/null @@ -1,7 +0,0 @@ -require('./programmatic/pm2Bus.mocha.js'); -require('./programmatic/god.mocha.js'); -require('./programmatic/monit.mocha.js'); -require('./programmatic/satan.mocha.js'); -require('./programmatic/programmatic.js'); -require('./programmatic/interactor.mocha.js'); -require('./programmatic/interactor.daemonizer.mocha.js'); diff --git a/test/index.sh b/test/index.sh new file mode 100644 index 00000000..833a4c9f --- /dev/null +++ b/test/index.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +alias mocha='../node_modules/mocha/bin/mocha' +pm2="`type -P node` `pwd`/bin/pm2" + + + +function fail { + echo -e "######## \033[31m ✘ $1\033[0m" + $pm2 kill + exit 1 +} + +function success { + echo -e "\033[32m------------> ✔ $1\033[0m" + $pm2 kill +} + +function spec { + [ $? -eq 0 ] || fail "$1" + success "$1" +} + +$pm2 kill + +mocha ./test/programmatic/god.mocha.js +spec "God test" +mocha ./test/programmatic/satan.mocha.js +spec "Satan test" +mocha ./test/programmatic/programmatic.js +spec "Programmatic test" +mocha ./test/programmatic/interactor.daemonizer.mocha.js +spec "Interactor daemonizer test" + +echo "########## PROGRAMMATIC TEST DONE #########" diff --git a/test/main.sh b/test/main.sh index 14b27252..cee71e1c 100644 --- a/test/main.sh +++ b/test/main.sh @@ -38,5 +38,9 @@ bash ./test/bash/fork.sh spec "Fork verified" bash ./test/bash/infinite_loop.sh spec "Infinite loop stop" +bash ./test/bash/env-refresh.sh +spec "Environment refresh on restart" +bash ./test/bash/reset.sh +spec "Reset meta" $pm2 kill diff --git a/test/programmatic/interactor.mocha.js b/test/programmatic/deprecated/interactor.mocha.js similarity index 100% rename from test/programmatic/interactor.mocha.js rename to test/programmatic/deprecated/interactor.mocha.js diff --git a/test/programmatic/god.mocha.js b/test/programmatic/god.mocha.js index 173d4a2a..59a35695 100644 --- a/test/programmatic/god.mocha.js +++ b/test/programmatic/god.mocha.js @@ -4,22 +4,49 @@ var numCPUs = require('os').cpus().length; var fs = require('fs'); var path = require('path'); var should = require('should'); - +var Common = require('../../lib/Common'); /** * Description * @method getConf * @return AssignmentExpression */ function getConf() { - return process_conf = { - pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/echo.js'), - pm_err_log_path : path.resolve(process.cwd(), 'test/echoErr.log'), - pm_out_log_path : path.resolve(process.cwd(), 'test/echoLog.log'), - pm_pid_file : path.resolve(process.cwd(), 'test/echopid'), - exec_mode : 'cluster_mode' - }; + var a = Common.resolveAppPaths({ + script : path.resolve(process.cwd(), 'test/fixtures/echo.js'), + name : 'echo', + instances : 2 + }); + return a; } +function getConf2() { + return Common.resolveAppPaths({ + script : path.resolve(process.cwd(), 'test/fixtures/child.js'), + instances : 4, + exec_mode : 'cluster_mode', + name : 'child' + }); +} + +function getConf3() { + return Common.resolveAppPaths({ + script : path.resolve(process.cwd(), 'test/fixtures/child.js'), + instances : 10, + exec_mode : 'cluster_mode', + name : 'child' + }); +} + +function getConf4() { + return Common.resolveAppPaths({ + script : path.resolve(process.cwd(), 'test/fixtures/args.js'), + args : "['-d', '-a']", + instances : '1', + name : 'child' + }); +} + + describe('God', function() { before(function(done) { God.deleteAll({}, function(err, dt) { @@ -52,18 +79,14 @@ describe('God', function() { }); it('should kill a process by name', function(done) { - God.prepare({ - pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/echo.js'), - pm_err_log_path : path.resolve(process.cwd(), 'test/errLog.log'), - pm_out_log_path : path.resolve(process.cwd(), 'test/outLog.log'), - pm_pid_path : path.resolve(process.cwd(), 'test/child'), - instances : 2 - }, function(err, procs) { - God.getFormatedProcesses().length.should.equal(2); + God.prepare(getConf(), function(err, procs) { + God.getFormatedProcesses().length.should.equal(2); God.stopProcessName('echo', function() { God.getFormatedProcesses().length.should.equal(2); - God.deleteAll({}, done); + God.deleteAll({}, function() { + done(); + }); }); }); }); @@ -82,9 +105,9 @@ describe('God', function() { God.prepare(getConf(), function(err, procs) { should(err).be.null; pid = procs[0].pid; - procs[0].pm2_env.status.should.be.equal('online'); - God.getFormatedProcesses().length.should.equal(1); - done(); + procs[0].pm2_env.status.should.be.equal('online'); + God.getFormatedProcesses().length.should.equal(2); + done(); }); }); }); @@ -102,8 +125,8 @@ describe('God', function() { clu = procs[0]; pid = clu.pid; - procs[0].pm2_env.status.should.be.equal('online'); - done(); + procs[0].pm2_env.status.should.be.equal('online'); + done(); }); }); @@ -117,7 +140,7 @@ describe('God', function() { }); it('should restart the same process and set it as state online and be up', function(done) { - God.restartProcessId(clu.pm2_env.pm_id, function(err, dt) { + God.restartProcessId({id:clu.pm2_env.pm_id}, function(err, dt) { var proc = God.findProcessById(clu.pm2_env.pm_id); proc.pm2_env.status.should.be.equal('online'); God.checkProcess(proc.process.pid).should.be.equal(true); @@ -126,7 +149,7 @@ describe('God', function() { }); it('should stop this process by name and keep in db on state stopped', function(done) { - God.stopProcessName(clu.name, function(err, dt) { + God.stopProcessName(clu.pm2_env.name, function(err, dt) { var proc = God.findProcessById(clu.pm2_env.pm_id); proc.pm2_env.status.should.be.equal('stopped'); God.checkProcess(proc.process.pid).should.be.equal(false); @@ -135,7 +158,7 @@ describe('God', function() { }); it('should restart the same process by NAME and set it as state online and be up', function(done) { - God.restartProcessName(clu.name, function(err, dt) { + God.restartProcessName(clu.pm2_env.name, function(err, dt) { var proc = God.findProcessById(clu.pm2_env.pm_id); proc.pm2_env.status.should.be.equal('online'); God.checkProcess(proc.process.pid).should.be.equal(true); @@ -148,7 +171,7 @@ describe('God', function() { God.deleteProcessId(clu.pm2_env.pm_id, function(err, dt) { var proc = God.findProcessById(clu.pm2_env.pm_id); God.checkProcess(old_pid).should.be.equal(false); - dt.length.should.be.equal(0); + dt.length.should.be.equal(1); done(); }); }); @@ -158,7 +181,7 @@ describe('God', function() { pid = _clu[0].pid; _clu[0].pm2_env.status.should.be.equal('online'); var old_pid = _clu[0].pid; - God.deleteProcessName(_clu.name, function(err, dt) { + God.deleteProcessName(_clu[0].pm2_env.name, function(err, dt) { setTimeout(function() { var proc = God.findProcessById(clu.pm2_env.pm_id); should(proc == null); @@ -181,15 +204,7 @@ describe('God', function() { }); it('should launch app', function(done) { - God.prepare({ - pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/child.js'), - pm_err_log_path : path.resolve(process.cwd(), 'test/errLog.log'), - pm_out_log_path : path.resolve(process.cwd(), 'test/outLog.log'), - pm_pid_path : path.resolve(process.cwd(), 'test/child'), - instances : 4, - exec_mode : 'cluster_mode', - name : 'child' - }, function(err, procs) { + God.prepare(getConf2(), function(err, procs) { var processes = God.getFormatedProcesses(); setTimeout(function() { @@ -234,29 +249,19 @@ describe('God', function() { }); it('should launch multiple processes depending on CPUs available', function(done) { - God.prepare({ - pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/echo.js'), - pm_err_log_path : path.resolve(process.cwd(), 'test/errLog.log'), - pm_out_log_path : path.resolve(process.cwd(), 'test/outLog.log'), - pm_pid_path : path.resolve(process.cwd(), 'test/child'), - exec_mode : 'cluster_mode', - instances : 3 - }, function(err, procs) { - God.getFormatedProcesses().length.should.equal(3); + God.prepare(Common.resolveAppPaths({ + script : path.resolve(process.cwd(), 'test/fixtures/echo.js'), + name : 'child', + instances:3 + }), function(err, procs) { + God.getFormatedProcesses().length.should.equal(3); procs.length.should.equal(3); done(); }); }); it('should start maximum processes depending on CPU numbers', function(done) { - God.prepare({ - pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/echo.js'), - pm_err_log_path : path.resolve(process.cwd(), 'test/errLog.log'), - pm_out_log_path : path.resolve(process.cwd(), 'test/outLog.log'), - pm_pid_path : path.resolve(process.cwd(), 'test/child'), - instances : 10, - exec_mode : 'cluster_mode', - }, function(err, procs) { + God.prepare(getConf3(), function(err, procs) { God.getFormatedProcesses().length.should.equal(10); procs.length.should.equal(10); done(); @@ -264,15 +269,7 @@ describe('God', function() { }); it('should handle arguments', function(done) { - God.prepare({ - pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/args.js'), - pm_err_log_path : path.resolve(process.cwd(), 'test/errLog.log'), - pm_out_log_path : path.resolve(process.cwd(), 'test/outLog.log'), - pm_pid_path : path.resolve(process.cwd(), 'test/child'), - args : "['-d', '-a']", - instances : '1', - exec_mode : 'cluster_mode' - }, function(err, procs) { + God.prepare(getConf4(), function(err, procs) { setTimeout(function() { God.getFormatedProcesses()[0].pm2_env.restart_time.should.eql(0); done(); diff --git a/test/programmatic/monit.mocha.js b/test/programmatic/monit.mocha.js index f92d2d01..008ffe37 100644 --- a/test/programmatic/monit.mocha.js +++ b/test/programmatic/monit.mocha.js @@ -4,7 +4,7 @@ var p = require('path') // , spawn = require('child_process').spawn , Spawner = require('promise-spawner') , async = require('async') - + , bin = p.join(root, '/bin/pm2') , pm2 = require(p.join(root, 'index.js')) , ids = [] @@ -16,13 +16,6 @@ var timeout = function(cb, time) { } describe('Monitor', function() { - this.timeout(0); - - beforeEach(function(cb) { - pm2.connect(function() { - cb() - }) - }) before(function(cb) { pm2.connect(function() { @@ -32,6 +25,16 @@ describe('Monitor', function() { }) }) + + after(function(cb) { + pm2.killDaemon(function() { + pm2.disconnect(function() { + cb() + }) + }); + }) + + it('should start', function() { var modifiers = { @@ -56,7 +59,7 @@ describe('Monitor', function() { console.log(this.data.err) } - process.exit(code) + process.exit(code) }) }) diff --git a/test/programmatic/pm2Bus.mocha.js b/test/programmatic/pm2Bus.mocha.js index 5a8fb8e4..c9845de5 100644 --- a/test/programmatic/pm2Bus.mocha.js +++ b/test/programmatic/pm2Bus.mocha.js @@ -8,7 +8,7 @@ var Plan = require('../helpers/plan.js'); var APPS = require('../helpers/apps.js'); var Ipm2 = require('pm2-interface'); -describe.skip('PM2 BUS / RPC', function() { +describe('PM2 BUS / RPC', function() { var pm2; var ipm2; diff --git a/test/programmatic/programmatic.js b/test/programmatic/programmatic.js index cd049bc1..6ccc1d38 100644 --- a/test/programmatic/programmatic.js +++ b/test/programmatic/programmatic.js @@ -113,7 +113,6 @@ describe('PM2 programmatic calls', function() { it('should delete processes', function(done) { pm2.delete('all', function(err, ret) { should(err).be.null; - ret.length.should.eql(0); pm2.list(function(err, ret) { should(err).be.null; ret.length.should.eql(0); @@ -258,7 +257,11 @@ describe('PM2 programmatic calls', function() { describe('start OR restart', function() { before(function(done) { pm2.delete('all', function(err, ret) { - done(); + pm2.list(function(err, ret) { + should(err).be.null; + ret.length.should.eql(0); + done(); + }); }); }); diff --git a/test/programmatic/satan.mocha.js b/test/programmatic/satan.mocha.js index 925171ef..db692561 100644 --- a/test/programmatic/satan.mocha.js +++ b/test/programmatic/satan.mocha.js @@ -17,9 +17,7 @@ describe('Satan', function() { it('should start Satan interaction', function(done) { Satan.start(function(err) { should(err).be.null; - pm2.delete('all', function(err, ret) { - done(); - }); + done(); }); });