refactor communication protocol - upgrade axon & rpc

This commit is contained in:
tknew2 2014-08-18 15:37:51 +02:00
commit 3f6323f957
51 changed files with 600 additions and 984 deletions

View File

@ -1,4 +1,27 @@
# 0.10.0 - PM2 Hellfire release
- PM2 hearth code has been refactored and now it handles extreme scenario without any leak or bug
- 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

View File

@ -5,27 +5,33 @@
When you got an issue by using pm2, you will fire an issue on [github](https://github.com/Unitech/pm2). We'll be glad to help or to fix it but the more data you give the most fast it would be resolved.
Please try following these rules it will make the task easier for you and for us:
#### 1) Search through issues if it hasn't been resolved yet
#### 2) Make sure that you provide following informations:
#### 1. Search through issues if it hasn't been resolved yet
#### 2. Make sure that you provide following informations:
- pm2 version `pm2 --version`
- nodejs version `node --version`
- operating system
#### 3) Provide details about your issue:
#### 3. Provide details about your issue:
- What are the steps that brought me to the issue?
- How may I reproduce this? (this isn't easy in some cases)
- Are you using a cluster module? Are you trying to catch SIGTERM signals? With `code` if possible.
#### 4) Think global if your issue is too specific we might not be able to help
#### 4. Think global
If your issue is too specific we might not be able to help and stackoverflow might be a better place to seak for an answer
#### 5) Be clear and format issues with [markdown](http://daringfireball.net/projects/markdown/)
#### 5. Be clear and format issues with [markdown](http://daringfireball.net/projects/markdown/)
Note that we might understand english, german and french
#### 6) Use debugging functions:
#### 6. Use debugging functions:
```DEBUG=pm2:* PM2_DEBUG=true ./bin/pm2 --no-daemon start my-buggy-thing.js```
If your issue is flagged as `need data` be sure that there won't be any upgrade unless we can have enough data to reproduce.
## Pull-Requests
@Todo
1. Fork pm2
2. Create a different branch to do your fixes/improvements if it's core-related.
3. Please add unit tests! There are lots of tests take examples from there!
4. Try to be as clear as possible in your commits
5. Pull request on pm2 from your branch

View File

@ -12,6 +12,7 @@ pm2 is perfect when you need to spread your stateless Node.js code across all CP
- 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
@ -62,6 +63,7 @@ Thanks in advance and we hope that you like pm2!
- [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)
@ -179,6 +181,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
@ -219,13 +222,15 @@ For scripts in other languages:
```bash
$ pm2 start echo.coffee
$ pm2 start echo.php
$ pm2 start echo.py
$ pm2 start echo.sh
$ pm2 start echo.rb
$ pm2 start echo.pl
$ pm2 start -x echo.php
$ pm2 start -x echo.py
$ pm2 start -x echo.sh
$ pm2 start -x echo.rb
$ pm2 start -x echo.pl
```
The not javascript languages will have to be run in [fork mode](#a23).
<a name="a987"/>
## Options
@ -325,6 +330,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
@ -526,7 +550,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"/>
@ -543,6 +566,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",
@ -611,6 +635,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",
@ -913,7 +938,6 @@ PM2_BIND_ADDR
PM2_API_PORT
PM2_GRACEFUL_TIMEOUT
PM2_MODIFY_REQUIRE
PM2_NODE_OPTIONS
```
@ -926,36 +950,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
@ -1029,6 +1038,8 @@ $ pm2 start my-bash-script.sh -x --interpreter bash
$ pm2 start my-python-script.py -x --interpreter python
```
The interpreter is deduced from the file extension from the [following list](https://github.com/Unitech/pm2/blob/master/lib/interpreter.json).
<a name="a96"/>
## JSON app configuration via pipe from stdout
@ -1123,6 +1134,13 @@ For more information about this, see [issue #74](https://github.com/Unitech/pm2/
When using the cluster mode (by default) you can't use ports from 0 to 1024. If you really need to exec in this range use the [fork mode](#a23) with the `-x` parameter.
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.
### User tips from issues
- [Vagrant and pm2 #289](https://github.com/Unitech/pm2/issues/289#issuecomment-42900019)
- [Start the same app on different ports #322](https://github.com/Unitech/pm2/issues/322#issuecomment-46792733)
- [Using ansible with pm2](https://github.com/Unitech/pm2/issues/88#issuecomment-49106686)
- [Cron string as argument](https://github.com/Unitech/pm2/issues/496#issuecomment-49323861)
- [Restart when process reaches a specific memory amount](https://github.com/Unitech/pm2/issues/141)
<a name="a20"/>
## External resources and articles
@ -1142,12 +1160,6 @@ By using the fork mode you will lose core features of pm2 like the automatic clu
- http://revdancatt.com/2013/09/17/node-day-1-getting-the-server-installing-node-and-pm2/
- https://medium.com/tech-talk/e7c0b0e5ce3c
## Some tips
- [Vagrant and pm2 #289](https://github.com/Unitech/pm2/issues/289#issuecomment-42900019)
- [Start the same app on different ports #322](https://github.com/Unitech/pm2/issues/322#issuecomment-46792733)
- [Using ansible with pm2](https://github.com/Unitech/pm2/issues/88#issuecomment-49106686)
- [Cron string as argument](https://github.com/Unitech/pm2/issues/496#issuecomment-49323861)
## Contributors
```

16
bin/pm2
View File

@ -10,6 +10,7 @@ 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');
@ -25,6 +26,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')
@ -470,6 +472,7 @@ if (process.argv.length == 2) {
// in file Satan.js, method Satan.launchRPC
//
process.once('satan:client:ready', function() {
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('');
@ -507,16 +510,3 @@ process.once('satan:client:ready', function() {
}
});
})();
(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

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

View File

@ -43,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)
@ -95,9 +100,12 @@ CLI.start = function(script, opts, cb) {
appConf['exec_mode'] = opts.execMode;
}
// 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 (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
@ -464,6 +472,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,
@ -482,22 +491,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.ERROR_EXIT);
});
};
@ -540,7 +535,7 @@ CLI.resetMetaProcess = function(process_name, cb) {
async.eachLimit(ids, 4, function(id, next) {
Satan.executeRemote('resetMetaProcessId', id, function(err, res) {
if (err) console.error(err);
printOut('Reseting meta for process id %d', id);
printOut(cst.PREFIX_MSG + 'Reseting meta for process id %d', id);
return next();
});
}, function(err) {
@ -610,29 +605,35 @@ 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');
CLI.killDaemon(function(err) {
printOut(cst.PREFIX_MSG + '--- killed');
Satan.launchDaemon(function(err, child) {
CLI.killDaemon(function() {
Satan.disconnectRPC(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();
Satan.launchDaemon(function(err, child) {
Satan.launchRPC(function() {
CLI.resurrect(function() {
printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
return cb ? cb(null, {success:true}) : speedList();
});
});
});
return false;
});
});
});
});
return false;
};
/**
@ -680,7 +681,7 @@ CLI.dump = function(cb) {
CLI.web = function(cb) {
var filepath = p.resolve(p.dirname(module.filename), 'HttpInterface.js');
pm2.start(filepath, {
CLI.start(filepath, {
name : 'pm2-http-interface',
execMode : 'fork_mode'
}, function(err, proc) {
@ -962,7 +963,7 @@ 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);
}
@ -971,7 +972,7 @@ CLI._restartAll = function(cb) {
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) {
@ -1004,35 +1005,35 @@ CLI.delete = function(process_name, jsonVia, cb) {
process_name = process_name.toString();
}
printOut(cst.PREFIX_MSG + 'Deleting ' + process_name);
if (jsonVia == 'pipe')
return CLI.actionFromJson('deleteProcessName', process_name, 'pipe');
if (process_name.indexOf('.json') > 0)
return CLI.actionFromJson('deleteProcessName', process_name, 'file');
else if (process_name == 'all') {
printOut(cst.PREFIX_MSG + 'Stopping and deleting all processes');
Satan.executeRemote('deleteAll', {}, 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 has been stopped and deleted');
return cb ? cb(null, list) : speedList();
});
}
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();
@ -1045,6 +1046,8 @@ 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');
@ -1059,7 +1062,6 @@ CLI.stop = function(process_name, 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);
}
};
@ -1072,9 +1074,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();
});
};
@ -1088,7 +1091,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);
@ -1105,10 +1108,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();
});
};
@ -1186,6 +1189,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);
});
};
@ -1357,7 +1361,7 @@ CLI.ilogs = function() {
});
} catch(e) {
printOut('pm2-logs module is not installed');
fallbackLogStream(id);
fallbackLogStream();
}
};
@ -1368,35 +1372,17 @@ 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);
}
printOut(cst.PREFIX_MSG + 'Stopping & Deleting all processes...');
Satan.executeRemote('deleteAll', {}, 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;
@ -1455,6 +1441,7 @@ function speedList() {
* @return
*/
function getInteractInfo(cb) {
debug('Getting interaction info');
InteractorDaemonizer.ping(function(online) {
if (!online) {
return cb({msg : 'offline'});
@ -1464,7 +1451,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

@ -58,6 +58,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 },

View File

@ -14,6 +14,7 @@ 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)
*/
@ -35,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.
@ -63,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);
}
@ -74,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;
@ -85,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;
@ -93,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;
@ -149,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);
});
});
};

View File

@ -30,7 +30,7 @@ var God = module.exports = {
next_id : 0,
clusters_db : {},
bus : new EventEmitter2({
wildcard: true,
wildcard: false,
delimiter: ':',
maxListeners: 1000
})
@ -169,7 +169,8 @@ God.executeApp = function executeApp(env, cb) {
*/
God.forkMode(env_copy, function forkMode(err, clu) {
if (cb && err) return cb(err);
if (err) return false;
var old_env = God.clusters_db[clu.pm2_env.pm_id];
if (old_env) old_env = null;
@ -203,6 +204,10 @@ God.executeApp = function executeApp(env, cb) {
if (old_env) {
old_env = null;
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] = null;
}

View File

@ -16,6 +16,9 @@ var pkg = require('../../package.json');
var pidusage = require('pidusage');
var Common = require('../Common');
var debug = require('debug')('pm2:ActionMethod');
/**
* Description
* @method exports
@ -450,7 +453,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();
});
@ -472,15 +477,17 @@ module.exports = function(God) {
* @return
*/
God.killMe = function(env, cb) {
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);
};
@ -558,10 +565,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

@ -11,6 +11,7 @@ var fs = require('fs');
var cst = require('../../constants.js');
var util = require('util');
var Common = require('../Common');
/**
* Description
* @method exports
@ -36,8 +37,8 @@ module.exports = function ClusterMode(God) {
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 {
@ -71,8 +72,8 @@ module.exports = function ClusterMode(God) {
}
});
// Avoid circular dependency
delete clu.process._handle.owner;
//delete clu.process._handle.owner;
return cb(null, clu);
return false;

View File

@ -28,7 +28,8 @@ module.exports = function ForkMode(God) {
* @return
*/
God.forkMode = function forkMode(pm2_env, cb) {
var command, args;
var command = '';
var args = [];
log('Entering in fork mode');
var spawn = require('child_process').spawn;
@ -38,16 +39,26 @@ module.exports = function ForkMode(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,15 +86,31 @@ module.exports = function ForkMode(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){
God.logAndGenerateError(e);

View File

@ -60,6 +60,7 @@ module.exports = function(God) {
});
}
}
db = null;
return arr;
};

View File

@ -1,7 +1,7 @@
var fs = require('fs');
var ipm2 = require('../../pm2-interface');
var rpc = require('axon-rpc');
var ipm2 = require('pm2-interface');
var rpc = require('pm2-axon-rpc');
var axon = require('axon');
var debug = require('debug')('interface:driver'); // Interface
var chalk = require('chalk');
@ -22,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) {
@ -89,9 +89,9 @@ var Daemon = {
self.activateRPC();
self.opts.ipm2 = self.connectToPM2();
WatchDog.start({
conf : self.opts
});
// WatchDog.start({
// conf : self.opts
// });
// Then connect to external services
if (cst.DEBUG) {

View File

@ -5,9 +5,9 @@ var fs = require('fs');
var cst = require('../../constants.js');
var path = require('path');
var util = require('util');
var rpc = require('axon-rpc');
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,8 +157,6 @@ function launchOrAttach(infos, cb) {
InteractorDaemonizer.daemonize = function(infos, cb) {
var InteractorJS = path.resolve(path.dirname(module.filename), 'Daemon.js');
console.log('Launching interactor');
var out = fs.openSync(cst.INTERACTOR_LOG_FILE_PATH, 'a'),
err = fs.openSync(cst.INTERACTOR_LOG_FILE_PATH, 'a');
@ -152,12 +177,19 @@ InteractorDaemonizer.daemonize = function(infos, cb) {
child.unref();
child.on('exit', function(msg) {
console.error('Error when launching Interactor, please check the agent logs');
return setTimeout(function() {cb(null, child)}, 100);
});
debug('Waiting for message');
child.once('message', function(msg) {
debug('Interactor ready');
process.emit('interactor:daemon:ready');
console.log(msg);
//console.log(msg);
child.disconnect();
console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Log: %s | Conf: %s | PID: %s', cst.INTERACTOR_LOG_FILE_PATH,
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);
@ -262,6 +294,20 @@ 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);
@ -272,14 +318,12 @@ InteractorDaemonizer.launchAndInteract = function(opts, cb) {
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

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

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

View File

@ -2,12 +2,14 @@
// 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');
require('coffee-script/register');
/**
* Main entrance to wrap the desired code
*/
@ -32,9 +34,6 @@ require('coffee-script/register');
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);
@ -77,11 +76,12 @@ 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') {
require('coffee-script/register');
}
process.on('message', function (msg) {
if (msg.type === 'log:reload') {
stdout.end();
@ -193,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

@ -9,7 +9,7 @@
* Dependencies
*/
var rpc = require('axon-rpc');
var rpc = require('pm2-axon-rpc');
var axon = require('axon');
var rep = axon.socket('rep');
var req = axon.socket('req');
@ -39,12 +39,13 @@ 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) {
@ -195,10 +196,9 @@ 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;
});
/**
@ -254,11 +254,13 @@ Satan.launchDaemon = function launchDaemon(cb) {
child.once('message', function(msg) {
debug('PM2 Daemon launched', msg);
child.disconnect();
InteractorDaemonizer.launchAndInteract({}, function(err, data) {
process.emit('satan:daemon:ready');
child.disconnect();
return setTimeout(function() {cb(null, child)}, 150);
});
});
};
@ -273,7 +275,7 @@ 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');
@ -304,7 +306,7 @@ Satan.launchRPC = function launchRPC(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);
};
/**
@ -312,12 +314,13 @@ Satan.launchRPC = function launchRPC(cb) {
* @callback cb
*/
Satan.disconnectRPC = function disconnectRPC(cb) {
debug('Disconnecting RPC');
process.nextTick(function() {
if (!Satan.client || !Satan.client.sock || !Satan.client.sock.close)
if (!Satan.client_sock || !Satan.client_sock.close)
return cb({
msg : 'RPC connection to PM2 is not launched'
});
Satan.client.sock.close();
Satan.client_sock.close();
return cb ? cb(null, {success:true}) : false;
});
};
@ -356,6 +359,7 @@ Satan.executeRemote = function executeRemote(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);
};
@ -366,7 +370,15 @@ Satan.executeRemote = function executeRemote(method, env, fn) {
* @return
*/
Satan.killDaemon = function killDaemon(fn) {
Satan.executeRemote('killMe', {}, fn);
debug('Killing interactor');
Satan.executeRemote('killMe', {}, function() {
Satan.disconnectRPC(function() {
setTimeout(function() {
return fn ? fn(null, {success:true}) : false;
}, 20);
return false;
});
});
};
/**

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.10.0",
"version": "0.10.1",
"os": [
"!win32"
],
@ -70,7 +70,7 @@
"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"
},
"keywords": [
"cli",
@ -119,7 +119,7 @@
"async": "~0.9.0",
"axm": "~0.1.7",
"axon": "~2.0.0",
"chalk": "^0.4.0",
"chalk": "~0.4.0",
"chokidar": "~0.8.2",
"cli-table": "~0.3.0",
"coffee-script": "~1.7.1",
@ -132,20 +132,22 @@
"nssocket": "~0.5.1",
"punt" : "~2.2.0",
"pidusage": "~0.0.7",
"axon-rpc": "~0.0.3",
"pm2-axon-rpc": "~0.2.0",
"pm2-deploy": "~0.0.4",
"pm2-interface" : "~1.1.0",
"pm2-multimeter": "~0.1.2",
"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" : "latest",
"ikst" : "~0.1.2"
},
"bugs": {
"url": "https://github.com/Unitech/pm2/issues"

View File

@ -1,3 +0,0 @@
node_modules
*.log
*.pid

View File

@ -1,13 +0,0 @@
Copyright [2013] [Strzelewicz Alexandre]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,147 +0,0 @@
# pm2-interface (for pm2 version >= 0.6.0)
pm2-interface permits you to interact with [PM2](https://github.com/Unitech/pm2) the process manager for NodeJS.
You can **control all exposed methods** by the pm2 deamon [God](https://github.com/Unitech/pm2/blob/master/lib/God.js) and also **receive real time notifications** for example for a process who got an unexpectedException, who's starting/stopping.
## RPC methods
- `ipm2.rpc.prepareJson(json_app, cwd, fn)` send a JSON configuration to start app(s) in the cwd folder
- `ipm2.rpc.getMonitorData({}, fn)` receive all related informations about supervised process (cpu/ram/pid...)
- `ipm2.rpc.getSystemData({}, fn)` receive all data about process managed by pm2 and computer resources usage
- `ipm2.rpc.startProcessId(integer, fn)` start a process by id (pm_id) who his state is stopped
- `ipm2.rpc.stopProcessId(integer, fn)` stop a process by id (pm_id)
- `ipm2.rpc.stopAll({}, fn)` stop all process
- `ipm2.rpc.reload(data, fn)` reload all apps (only for networked apps)
- `ipm2.rpc.killMe(data, fn)` kill pm2 daemon
- `ipm2.rpc.findByScript(string, fn)` send you back the informations about a specific process
- `ipm2.rpc.restartProcessId(integer, fn)` restart a process by id (pm_id)
- `ipm2.rpc.restartProcessName(string, fn)` restart all processes who have the given name
- `ipm2.rpc.deleteProcess(string, fn)` stop and delete all processes from the pm2 database
- `ipm2.rpc.deleteAll(data, fn)` stop and delete all processes
- `ipm2.rpc.msgProcess(opts, fn)` send msg `opts.msg` to process at `opts.id` or all processes with `opts.name`
## Notifications
- `process:online` when a process is started/restarted
- `process:exit` when a process is exited
- `process:exception` When a process has received an uncaughtException
**Advanced feature** : You can use `process.send({ type : 'my:message', data : {}})` in your Node apps. When you emit a message, they will be redirected to pm2 and sent back to the pm2-interface bus. This can be coupled with `rpc.msgProcess(opts, fn)` to allow 2-way communication between managed processes and pm2-interface - see second Example below.
> It should be noted that `process.send` will be undefined if there is no parent process. Therefore a check of `if (process.send)` may be advisable.
## Example
```javascript
var ipm2 = require('pm2-interface')();
ipm2.on('ready', function() {
console.log('Connected to pm2');
ipm2.bus.on('*', function(event, data){
console.log(event, data.pm2_env.name);
});
setTimeout(function() {
ipm2.rpc.restartProcessId(0, function(err, dt) {
console.log(dt);
});
}, 2000);
ipm2.rpc.getMonitorData({}, function(err, dt) {
console.log(dt);
});
});
```
## Example 2-way
in your process script
```javascript
if (send in process) {
process.on("message", function (msg) {
if ( "type" in msg && msg.type === "god:heap" ) {
var heap = process.memoryUsage().heapUsed
process.send({type:"process:heap", heap:heap})
}
})
}
var myMemoryLeak = []
setInterval( function () {
var object = {}
for (var i = 0; i < 10000; i++) {
object["key"+i] = Math.random().toString(36).substring(7)
}
myMemoryLeak.push(object)
}, Math.round(Math.random()*2000))
```
in monitoring script
```javascript
var ipm2 = require('pm2-interface')()
ipm2.on('ready', function() {
console.log('Connected to pm2')
ipm2.bus.on('process:heap', function(data){
console.log("process heap:", data)
})
setInterval( function () {
var msg = {type:"god:heap"} // god: is arbitrary and used to distinguish incoming & outgoing msgs
ipm2.rpc.msgProcess({name:"worker", msg:msg}, function (err, res) {
if (err) console.log(err)
else console.log(res)
})
}, 5000)
})
```
Start pm2 and monitoring script + output:
```shell
pm2 start worker.js -i 3 --name worker
node monitor.js
sent 3 messages # coming from the console.log(res)
process heap: { pm_id: 0, msg: { type: 'process:heap', heap: 43416064 } }
process heap: { pm_id: 1, msg: { type: 'process:heap', heap: 18373704 } }
process heap: { pm_id: 2, msg: { type: 'process:heap', heap: 80734256 } }
sent 3 messages
process heap: { pm_id: 0, msg: { type: 'process:heap', heap: 61994096 } }
process heap: { pm_id: 1, msg: { type: 'process:heap', heap: 22437400 } }
process heap: { pm_id: 2, msg: { type: 'process:heap', heap: 116622432 } }
sent 3 messages
process heap: { pm_id: 0, msg: { type: 'process:heap', heap: 79641168 } }
process heap: { pm_id: 1, msg: { type: 'process:heap', heap: 32260112 } }
process heap: { pm_id: 2, msg: { type: 'process:heap', heap: 156047904 } }
pm2 delete all
```
## Disconnect
Since pm2-interface interacts with PM2 via sockets, any script which uses pm2-interface will remain alive even when the node.js event loop is empty. `process.exit()` can be called to forcefully exit, or, if your script has finished making calls to PM2, you may call `ipm2.disconnect()` to disconnect the socket connections and allow node to exit automatically.
```javascript
ipm2.on('ready', function() {
// ...
ipm2.disconnect();
});
```
> Calling `disconnect()` means "I am entirely done interacting with PM2." You will no longer be able to receive messages on `ipm2.bus` or send requests on `ipm2.rpc`. To reconnect you must completely start over with a new ipm2 object.
## Ideas
- Catching exceptions and fowarding them by mail
- A web interface to control PM2
## Apache v2 License

View File

@ -1,12 +0,0 @@
//
// Modifying these values break tests and can break
// pm2-interface intercommunication (because of ports)
//
module.exports = {
DAEMON_BIND_HOST : 'localhost',
DAEMON_RPC_PORT : 6666, // RPC commands
DAEMON_PUB_PORT : 6667, // Realtime events
SUCCESS_EXIT : 0,
ERROR_EXIT : 1
};

View File

@ -1,48 +0,0 @@
var ipm2 = require('..');
var ipm2a = ipm2({bind_host : 'localhost'});
ipm2a.on('ready', function() {
console.log('Connected to pm2');
ipm2a.bus.on('*', function(event, data){
console.log(event);
});
// ipm2a.bus.on('log:err', function(event, data){
// console.log(event, data);
// });
// ipm2a.bus.on('log:out', function(event, data){
// console.log(event, data);
// });
ipm2a.on('rpc_sock:reconnecting', function() {
console.log('rpc_sock:reconnecting');
});
ipm2a.on('sub_sock:ready', function() {
console.log('sub_sock:ready');
});
ipm2a.on('sub_sock:closed', function() {
console.log('sub_sock:closed');
});
ipm2a.on('sub_sock:reconnecting', function() {
console.log('sub_sock:reconnecting');
});
// setTimeout(function() {
// ipm2.rpc.restartProcessId(0, function(err, dt) {
// console.log(dt);
// });
// }, 2000);
// ipm2.rpc.getMonitorData({}, function(err, dt) {
// console.log(dt);
// });
});

View File

@ -1,27 +0,0 @@
var ipm2 = require('..');
var ipm2a = ipm2({bind_host : 'localhost'});
ipm2a.on('ready', function() {
console.log('Connected to pm2');
ipm2a.bus.on('*', function(event, data){
console.log(event);
});
setInterval(function() {
var msg = {type:"god:heap"};
ipm2a.rpc.msgProcess({name:"expose_method", msg:msg}, function (err, res) {
if (err) console.log(err)
else console.log(res)
console.log(arguments);
})
}, 2000);
});

View File

@ -1,3 +0,0 @@
module.exports = exports = require("./lib");

View File

@ -1,123 +0,0 @@
/**
* Dependencies
*/
var axon = require('axon');
var cst = require('../constants.js');
var sys = require('sys');
var rpc = require('axon-rpc');
var log = require('debug')('pm2:interface');
var EventEmitter = require('events').EventEmitter;
/**
* Export with conf
*/
module.exports = function(opts){
var sub_port = opts && opts.sub_port || cst.DAEMON_PUB_PORT;
var rpc_port = opts && opts.rpc_port || cst.DAEMON_RPC_PORT;
var bind_host = opts && opts.bind_host || cst.DAEMON_BIND_HOST;
return new IPM2(sub_port, rpc_port, bind_host);
};
/**
* IPM2, Pm2 Interface
*/
var IPM2 = function(sub_port, rpc_port, bind_host) {
//if (!(this instanceof Bash)) return new Bash(opts);
var self = this;
EventEmitter.call(this);
this.sub_port = sub_port;
this.rpc_port = rpc_port;
this.bind_host = bind_host;
var sub = axon.socket('sub-emitter');
var sub_sock = this.sub_sock = sub.connect(sub_port, bind_host);
this.bus = sub;
var req = axon.socket("req");
var rpc_sock = this.rpc_sock = req.connect(rpc_port, bind_host);
this.rpc_client = new rpc.Client(req);
this.rpc = {};
/**
* Disconnect socket connections. This will allow Node to exit automatically.
* Further calls to PM2 from this object will throw an error.
*/
this.disconnect = function () {
self.sub_sock.close();
self.rpc_sock.close();
};
/**
* Generate method by requesting exposed methods by PM2
* You can now control/interact with PM2
*/
var generateMethods = function(cb) {
log('Requesting and generating RPC methods');
self.rpc_client.methods(function(err, methods) {
Object.keys(methods).forEach(function(key) {
var method_signature, md;
method_signature = md = methods[key];
log('+-- Creating %s method', md.name);
(function(name) {
self.rpc[name] = function() {
log(name);
var args = Array.prototype.slice.call(arguments);
args.unshift(name);
self.rpc_client.call.apply(self.rpc_client, args);
};
})(md.name);
});
return cb();
});
};
/**
* Waiting to connect to sub channel
* and RPC
*/
rpc_sock.on('connect', function() {
log('rpc_sock:ready');
self.emit('rpc_sock:ready');
generateMethods(function() {
self.emit('ready');
});
});
rpc_sock.on('close', function() {
log('rpc_sock:closed');
self.emit('close');
});
rpc_sock.on('reconnect attempt', function() {
log('rpc_sock:reconnecting');
self.emit('reconnecting');
});
sub_sock.on('connect', function() {
log('sub_sock ready');
self.emit('sub_sock:ready');
});
sub_sock.on('close', function() {
log('sub_sock:closed');
self.emit('closed');
});
sub_sock.on('reconnect attempt', function() {
log('sub_sock:reconnecting');
self.emit('reconnecting');
});
};
sys.inherits(IPM2, EventEmitter);

View File

@ -1,42 +0,0 @@
{
"name": "pm2-interface",
"version": "0.1.4",
"description": "Interact with pm2 via RPC and receive real time notifications",
"author": {
"name": "Strzelewicz Alexandre",
"email": "as@unitech.io",
"url": "http://unitech.io"
},
"engines" : {
"node" : ">=0.8"
},
"main": "index.js",
"dependencies" : {
"axon" : "2.0.0",
"axon-rpc" : "0.0.3",
"debug" : "*"
},
"keywords": [
"cli",
"fault tolerant",
"sysadmin",
"tools",
"pm2",
"node-pm2",
"monitoring",
"process manager",
"forever",
"process configuration",
"clustering",
"cluster cli",
"cluster",
"cron",
"devops",
"dev ops"
],
"repository": {
"type" : "git",
"url" : "git://github.com/Unitech/pm2-interface.git"
},
"license": "Apache v2"
}

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

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,8 +69,8 @@ $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"
spec "file errech-0.log exist"
should 'should has not restarted' 'restart_time: 0' 10

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,20 +9,31 @@ $pm2 kill
echo "################ HARMONY ES6"
$pm2 start harmony.js
sleep 8
sleep 2
$pm2 list
should 'should fail when trying to launch pm2 without harmony option' 'errored' 1
should 'should FAIL when not passing harmony option to V8' 'restart_time: 0' 0
$pm2 list
$pm2 kill
PM2_NODE_OPTIONS='--harmony' `pwd`/../../bin/pm2 start harmony.js
sleep 4
$pm2 list
should 'should not fail when passing harmony option to V8' 'restart_time: 0' 1
$pm2 kill
$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
$pm2 kill
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
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
@ -77,3 +96,11 @@ cat outmerge-0.log > /dev/null
ispec 'file outmerge-0.log should not exist'
rm outmerge*
########### coffee cluster test
$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

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

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

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

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