merge development with master

This commit is contained in:
tknew2 2014-08-22 20:35:59 +02:00
commit 852ef05a2c
68 changed files with 1752 additions and 1158 deletions

View File

@ -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 <json|id|name|all> refresh current environment variables #528
- PM2 delete all more verbose
- PM2 reset <all|id|name> 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

144
README.md
View File

@ -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.
<!-- ### Build Status -->
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!
<a name="a1"/>
## 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 <process> # 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).
<a name="a987"/>
## Options
@ -259,7 +263,7 @@ Options:
```
<a name="update-pm2"/>
## 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
<a name="a4"/>
## 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
```
<a name="max-memory-restart"/>
## 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"
}
```
<a name="a7"/>
## 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')`.
<a name="a8"/>
## 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
<a name="a890"/>
## 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"]
}
```
<a name="a10"/>
@ -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) {
<a name="a77"/>
# 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
<a name="a66"/>
## 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"
}]
```
<a name="a19"/>
## CoffeeScript
@ -991,7 +1001,7 @@ We recommend following the 12 factor convention : [http://12factor.net/](http://
<a name="a34"/>
## 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
<a name="a23"/>
## 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 -
```
<a name="a149"/>
## 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
<a name="a27"/>
## 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
<a name="a21"/>
## 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

181
bin/pm2
View File

@ -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 <path>', 'specify out log file')
.option('-e --error <path>', 'specify error log file')
.option('-p --pid <pid>', 'specify pid file')
.option('--max-memory-restart <memory>', 'specify max memory amount used to autorestart (in megaoctets)')
.option('--env <environment_name>', 'specify environment to get specific env variables (for JSON declaration)')
.option('-x --execute-command', 'execute a program using fork system')
.option('-u --user <username>', '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 <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 <run_as_user>', 'The user or uid to run a managed process as')
.option('--run-as-group <run_as_group>', 'The group or gid to run a managed process as')
@ -121,7 +152,7 @@ function failOnUnknown(fn) {
//
commander.command('start <file|json|stdin>')
.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 <file|json|stdin>')
});
commander.command('deploy <file|environment>')
.description(' deploy your json')
.description('deploy your json')
.action(function(cmd) {
CLI.deploy(cmd, commander);
});
commander.command('startOrRestart <json>')
.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 <json>')
//
commander.command('stop <id|name|all|json|stdin>')
.option('--watch', 'Stop watching folder for changes')
.description(' stop a process (to start it again, do pm2 restart <app>)')
.description('stop a process (to start it again, do pm2 restart <app>)')
.action(function(param) {
CLI.stop(param);
});
@ -163,7 +194,7 @@ commander.command('stop <id|name|all|json|stdin>')
//
commander.command('restart <id|name|all|json|stdin>')
.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 <id|name|all|json|stdin>')
// Reload process(es)
//
commander.command('reload <name|all>')
.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 <name|all>')
// Reload process(es)
//
commander.command('gracefulReload <name|all>')
.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 <name|all>')
// .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 <name|all>')
// Stop and delete a process by name from database
//
commander.command('delete <name|id|script|all|json|stdin>')
.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 <name|id|script|all|json|stdin>')
// Send system signal to process
//
commander.command('sendSignal <signal> <pm2_id|name>')
.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 <signal> <pm2_id|name>')
// 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 <name|id|all>')
.description('reset counters for process')
.action(function(proc_id) {
CLI.resetMetaProcess(proc_id);
});
commander.command('describe <id>')
.description('describe all parameters of a process id')
.action(function(proc_id) {
CLI.describe(proc_id);
});
CLI.describe(proc_id);
});
commander.command('desc <id>')
.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 <id>')
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) {}
})();

View File

@ -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
};

View File

@ -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);

View File

@ -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});
});

0
examples/echo.js Normal file → Executable file
View File

View File

@ -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); });
});
});
});
});
});
});
});
});

View File

@ -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); });
});
//});
});
//});
});
});
});
});
});
});
});
});
});

View File

@ -0,0 +1,7 @@
console.log('start');
setTimeout(function() {
console.log('exit');
throw new Error('Exitasdsadasdsda unacepted !!');
}, 300);

View File

@ -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;

View File

@ -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 = {};

View File

@ -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);
});
};

View File

@ -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);

View File

@ -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);
});

View File

@ -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);
};
};

View File

@ -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;
});
};
};

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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();
}

View File

@ -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,

View File

@ -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;

View File

@ -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;
}
};

8
lib/Interactor/Tools.js Normal file
View File

@ -0,0 +1,8 @@
var Stringify = require('json-stringify-safe');
var Tools = {};
Tools.serialize = function(data) {
return JSON.parse(Stringify(data));
};

View File

@ -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);
}
};

View File

@ -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);
});
};
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
};

View File

@ -1,189 +0,0 @@
/*
* Date Format 1.2.3
* (c) 2007-2009 Steven Levithan <stevenlevithan.com>
* MIT license
*
* Includes enhancements by Scott Trenda <scott.trenda.net>
* and Kris Kowal <cixar.com/~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;
}

View File

@ -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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

BIN
pres/pm2-v2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

77
scripts/kill.js Normal file
View File

@ -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;
});

20
scripts/ping.js Normal file
View File

@ -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);
}
}

14
scripts/postinstall.sh Normal file
View File

@ -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

24
scripts/preinstall.sh Normal file
View File

@ -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

View File

@ -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"

View File

@ -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"

45
test/bash/env-refresh.sh Normal file
View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

46
test/bash/reset.sh Normal file
View File

@ -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

16
test/benchmarks/monit-daemon.sh Executable file
View File

@ -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

35
test/benchmarks/monit.sh Executable file
View File

@ -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

View File

@ -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

23
test/fixtures/big-array-es6.js vendored Normal file
View File

@ -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) {}
})();

8
test/fixtures/big-array.js vendored Normal file
View File

@ -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);

10
test/fixtures/env-refreshed.json vendored Normal file
View File

@ -0,0 +1,10 @@
[{
"name" : "env",
"script" : "./env.js",
"out_file" : "out-env.log",
"merge_logs" : true,
"env": {
"NODE_ENV": "production",
"TEST_VARIABLE": "HEYYYY"
}
}]

View File

@ -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"
}
}]

5
test/fixtures/harmony.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"name" : "ES6",
"script" : "harmony.js",
"node_args" : "--harmony"
}

5
test/fixtures/max-mem.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"name" : "max_mem",
"script" : "big-array.js",
"max_memory_restart" : "19"
}

View File

@ -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');

35
test/index.sh Normal file
View File

@ -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 #########"

View File

@ -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

View File

@ -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();

View File

@ -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)
})
})

View File

@ -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;

View File

@ -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();
});
});
});

View File

@ -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();
});
});