This commit is contained in:
Alexandre Strzelewicz 2013-05-21 18:27:49 +08:00
parent c82a981f2e
commit 133cf24dee
45 changed files with 463892 additions and 1817 deletions

4
.gitignore vendored
View File

@ -1 +1,3 @@
node_modules/*
node_modules/*
pids/*
logs/*

5
apps/auto-kill-echo.json Normal file
View File

@ -0,0 +1,5 @@
{
"name" : "auto-kill",
"script" : "./examples/echokill.js",
"max" : "10"
}

8
apps/cluster-pm2.json Normal file
View File

@ -0,0 +1,8 @@
{
"script" : "./examples/child.js",
"fileError" : "logs/errLog.log",
"fileOutput" : "logs/outLog.log",
"pidFile" : "pids/child",
"max" : "10",
"instances" : "max"
}

View File

@ -0,0 +1,5 @@
{
"name" : "echo-default",
"script" : "examples/echo.js",
"max" : "1"
}

7
apps/echo-pm2.json Normal file
View File

@ -0,0 +1,7 @@
{
"script" : "examples/echo.js",
"fileError" : "logs/errEcho.log",
"fileOutput" : "logs/outEcho.log",
"pidFile" : "pids/echo",
"max" : "1"
}

20
apps/multi-pm2.json Normal file
View File

@ -0,0 +1,20 @@
[{
"script" : "examples/echo.js",
"fileError" : "logs/errEcho.log",
"fileOutput" : "logs/outEcho.log",
"pidFile" : "pids/echo",
"max" : "1"
},{
"script" : "./examples/child.js",
"fileError" : "logs/errLog.log",
"fileOutput" : "logs/outLog.log",
"pidFile" : "pids/child",
"max" : "10",
"instances" : "max"
},{
"script" : "./examples/echokill.js",
"fileError" : "logs/rLog.log",
"fileOutput" : "logs/tLog.log",
"pidFile" : "pids/kiii",
"max" : "10"
}]

4
apps/no-name-echo.json Normal file
View File

@ -0,0 +1,4 @@
{
"script" : "examples/echo.js",
"max" : "1"
}

355
bin/god
View File

@ -2,12 +2,253 @@
var Satan = require('../satan.js');
var commander = require('commander');
var Table = require('cli-table');
var Monit = require('../lib/monit');
var UX = require('../lib/cli-ux.js');
var Log = require('../lib/Log.js');
var fs = require('fs');
var path = require('path');
const VERSION = '1.0.1';
const SUCCESS_EXIT = 0;
const ERROR_EXIT = 1;
const SAMPLE_FILE_PATH = '../lib/sample.json';
const DEFAULT_FILE_PATH = path.resolve(process.env.HOME, '.pm2');
const DEFAULT_LOG_PATH = path.join(DEFAULT_FILE_PATH, 'logs');
const DEFAULT_PID_PATH = path.join(DEFAULT_FILE_PATH, 'pids');
commander.version(VERSION)
.option('-v --verbose', 'Display all data')
.option('-f --force', 'Force actions')
.usage('[cmd] app');
//
// Start command
//
commander.command('start <part>')
.description('start specific part')
.action(function(cmd) {
var data = fs.readFileSync(cmd);
var appConf = JSON.parse(data);
if (Array.isArray(appConf)) {
(function ex(apps) {
if (!apps[0]) return speedList();
Satan.executeRemote('prepare', preProcess(apps[0]), function() {
apps.shift();
return ex(apps);
});
})(appConf);
}
else {
Satan.executeRemote('findByScript', {script : appConf.script}, function(err, exec) {
if (exec && !commander.force) {
console.log('Script already launched, add -f option to force re execution');
process.exit(ERROR_EXIT);
}
Satan.executeRemote('prepare', preProcess(appConf), function() {
console.log('Process launched');
speedList();
});
});
}
});
//
// Stop All processes
//
commander.command('stop')
.description('stop all processes')
.action(function(opts, cmd) {
console.log('Stopping all processes');
Satan.executeRemote('stop', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
UX.dispAsTable(list);
process.exit(SUCCESS_EXIT);
});
});
//
// Sample generate
//
commander.command('generate <name>')
.description('generate sample JSON')
.action(function(name) {
var sample = fs.readFileSync(path.join(__dirname, SAMPLE_FILE_PATH));
var dt = sample.toString().replace(/VARIABLE/g, name);
var f_name = name + '-pm2.json';
fs.writeFileSync(path.join(process.env.PWD, f_name), dt);
console.info('Sample generated on current folder\n%s :\n', f_name);
console.info(dt);
process.exit(SUCCESS_EXIT);
});
//
// List command
//
commander.command('list')
.description('list all processes')
.action(function(opts, cmd) {
Satan.executeRemote('list', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
UX.dispAsTable(list);
process.exit(SUCCESS_EXIT);
});
});
//
// Monitoring command
//
commander.command('monit')
.description('list all processes')
.action(function(opts, cmd) {
Satan.executeRemote('list', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
Monit.init(list);
function refresh(cb) {
Satan.executeRemote('list', {}, function(err, list) {
setTimeout(function() {
Monit.refresh(list);
refresh();
}, 400);
});
}
refresh();
});
});
//
// Log streaming
//
commander.command('logs')
.description('stream logs file')
.action(function(opts, cmd) {
Satan.executeRemote('list', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
if (list && list.length == 0) {
console.log('No processes online');
}
list.forEach(function(l) {
if (l.opts.fileOutput) Log.stream(l.opts.fileOutput, l.opts.script + ' ' + l.pid);
if (l.opts.fileError) Log.stream(l.opts.fileError);
});
});
});
//
// Kill
//
commander.command('kill')
.description('kill daemon')
.action(function() {
Satan.killDaemon(function(err, res) {
if (err) {
console.error('Error when killing daemon');
process.exit(ERROR_EXIT);
}
console.info('Daemon killed');
process.exit(SUCCESS_EXIT);
});
});
commander.command('*')
.action(function() {
console.log('\nCommand not found');
commander.outputHelp();
process.exit(ERROR_EXIT);
});
if (process.argv.length == 2) {
commander.outputHelp();
process.exit(ERROR_EXIT);
}
//
// Wait Satan is connected to God to launch parsing
//
process.on('satan:client:ready', function() {
commander.parse(process.argv);
});
//
// Init
//
(function init() {
fs.exists(DEFAULT_FILE_PATH, function(exist) {
if (!exist) {
fs.mkdirSync(DEFAULT_FILE_PATH);
fs.mkdirSync(DEFAULT_LOG_PATH);
fs.mkdirSync(DEFAULT_PID_PATH);
}
});
})();
//
// Private methods
//
function speedList() {
Satan.executeRemote('list', {}, function(err, list) {
UX.dispAsTable(list);
process.exit(SUCCESS_EXIT);
});
}
//
// Resolving path, seing if default ...
//
function preProcess(app) {
app["pm_exec_path"] = path.resolve(process.cwd(), app.script);
fs.statSync(app.pm_exec_path);
if (app.fileOutput)
app["pm_out_log_path"] = path.resolve(process.cwd(), app.fileOutput);
else {
if (!app.name) {
console.log('You havent specified log path, please specify at least a "name" field in the JSON');
process.exit(ERROR_EXIT);
}
app["pm_out_log_path"] = path.resolve(DEFAULT_LOG_PATH, [app.name, '-out.log'].join(''));
app.fileOutput = app["pm_out_log_path"];
}
if (app.fileError)
app["pm_err_log_path"] = path.resolve(process.cwd(), app.fileError);
else {
app["pm_err_log_path"] = path.resolve(DEFAULT_LOG_PATH, [app.name, '-err.log'].join(''));
app.fileError = app["pm_err_log_path"];
}
if (app.pidFile)
app["pm_pid_path"] = path.resolve(process.cwd(), app.pidFile);
else {
app["pm_pid_path"] = path.resolve(DEFAULT_PID_PATH, [app.name, '.pid'].join(''));
app.pidFile = app["pm_pid_path"];
}
fs.existsSync(app.pm_out_log_path);
fs.existsSync(app.pm_err_log_path);
return app;
}
const VERSION = '0.0.1';
const SUCCESS_EXIT = 0;
const ERROR_EXIT = 1;
// var logStream = process.stdout;
@ -18,109 +259,3 @@ const ERROR_EXIT = 1;
// ["debug", "info", "warning", "error"].forEach(function(level) {
// log[level] = log.bind(null, level);
// });
commander.version(VERSION)
.option('-v --verbose', 'Display all data')
.option('-f --force', 'Force actions')
.usage('[cmd] app');
commander.command('start <part>')
.description('start specific part')
.action(function(cmd) {
Satan.executeRemote('prepare', {
script : './examples/child.js',
fileError : 'logs/errLog.log',
fileOutput : 'logs/outLog.log',
pidFile : 'pids/child',
max : 10,
instances : 'max'
}, function() {
console.log('All process launched');
process.exit(1);
});
//
});
commander.command('stop')
.description('stop all processes')
.action(function(opts, cmd) {
console.log('Stopping all processes');
Satan.executeRemote('stop', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
dispAsTable(list);
process.exit(SUCCESS_EXIT);
});
});
commander.command('list')
.description('list all processes')
.action(function(opts, cmd) {
Satan.executeRemote('list', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
dispAsTable(list);
process.exit(SUCCESS_EXIT);
});
//
});
// Logs
commander.command('monit')
.description('list all processes')
.action(function(opts, cmd) {
Satan.executeRemote('list', {}, function(err, list) {
if (err) process.exit(ERROR_EXIT);
Monit.init(list);
function refresh(cb) {
Satan.executeRemote('list', {}, function(err, list) {
setTimeout(function() {
Monit.refresh(list);
refresh();
}, 500);
});
}
refresh();
});
});
commander.command('launch')
.description('manual launch of God')
.action(function(cmd) {
process.exit(1);
});
//
// Wait Satan is connected to God to launch parsing
//
process.on('satan:ready', function() {
commander.parse(process.argv);
});
function dispAsTable(list) {
var table = new Table({ head: ["Script", "id", "PID","status", "memory"] });
list.forEach(function(l) {
var u = l.opts.script;
var obj = {};
obj[l.opts.script] = [
l.pm_id,
l.pid,
l.status,
l.monit ? l.monit.memory : ''
];
table.push(obj);
});
console.log(table.toString());
}

View File

@ -1 +0,0 @@

View File

@ -5,13 +5,13 @@
(function child_wrapper() {
var fs = require('fs');
var worker = require('cluster').worker;
var outFile = process.env.pm_outFile;
var errFile = process.env.pm_errFile;
var pmId = process.env.pm_id;
var pidFile = [process.env.pm_pidFile, pmId, '.pid'].join('');
var pidFile = [process.env.pm_pidFile, pmId].join('');
var script = process.env.pm_script;
fs.writeFileSync(pidFile, process.pid);
var stdout = fs.createWriteStream(outFile, { flags : 'a' });
@ -19,13 +19,13 @@
process.stderr.write = (function(write) {
return function(string, encoding, fd) {
stderr.write(string);
stdout.write(JSON.stringify({msg :string, date : (new Date()).toISOString()}));
};
})(process.stderr.write);
process.stdout.write = (function(write) {
return function(string, encoding, fd) {
stdout.write(string);
stderr.write(string);
};
})(process.stdout.write);
@ -35,16 +35,16 @@
});
require(script);
// var domain = require('domain').create();
// domain.run(function() {
// require(script);
// domain.run(function() {
// require(script);
// });
// domain.on('error', function(e) {
// stderr.write(e);
// });
})();

View File

@ -1,19 +1,8 @@
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
//var worker = require('cluster').worker;
console.log(process.env.NODE_UNIQUE_ID, cluster.isWorker);
var i = 0;
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n" + i++);
res.writeHead(200);
res.end("hello world\n" + i++);
}).listen(8000);
// setTimeout(function() {
// process.exit(1);
// }, 2000);

View File

@ -1,9 +1,4 @@
setInterval(function() {
console.log('ok');
console.error('merde');
}, 500);
setTimeout(function() {
throw new Error('eh merde');
}, 3000);
console.log('ok');
}, 800);

9
examples/echokill.js Normal file
View File

@ -0,0 +1,9 @@
setInterval(function() {
console.log('ok');
}, 800);
setTimeout(function() {
process.exit(-1);
}, 3000);

353
god.js
View File

@ -8,218 +8,205 @@ var async = require('async');
var path = require('path');
cluster.setupMaster({
exec : path.resolve('child_wrapper.js')
exec : path.resolve('child_wrapper.js')
});
var God = module.exports = {
next_id : 0,
clusters_db : {}
next_id : 0,
clusters_db : {}
};
//
// Init
//
(function initEngine() {
cluster.on('online', function(clu) {
console.log("%s - id%d worker online",
clu.opts.script,
clu.pm_id);
God.clusters_db[clu.pm_id].status = 'online';
});
cluster.on('exit', function(clu, code, signal) {
console.log('Script %s %d exited code %d',
clu.opts.script,
clu.pm_id,
code);
cluster.on('online', function(clu) {
console.log("%s - id%d worker online",
clu.opts.script,
clu.pm_id);
God.clusters_db[clu.pm_id].status = 'online';
});
cluster.on('exit', function(clu, code, signal) {
console.log('Script %s %d exited code %d',
clu.opts.script,
clu.pm_id,
code);
God.clusters_db[clu.pm_id].status = 'starting';
if (clu.opts.max !== undefined) {
if (clu.opts.max <= 0) {
God.clusters_db[clu.pm_id].status = 'stopped';
delete God.clusters_db[clu.pm_id];
return ;
}
else clu.opts.max -= 1;
}
delete God.clusters_db[clu.pm_id];
execute(clu.opts);
});
God.clusters_db[clu.pm_id].status = 'starting';
//delete God.clusters_db[clu.pm_id];
if (clu.opts.max !== undefined) {
if (clu.opts.max <= 0) {
God.clusters_db[clu.pm_id].status = 'stopped';
return ;
}
else clu.opts.max -= 1;
}
execute(clu.opts);
});
})();
God.stopAll = function(cb) {
var pros = God.getFormatedProcesses();
var l = pros.length;
(function ex(processes, i) {
if (i <= -1) return cb(null, God.getFormatedProcesses());
if (processes[i].state == 'stopped') return ex(processes, i - 1);
return God.stopProcess(processes[i], function() {
ex(processes, i - 1);
});
})(pros, l - 1);
};
God.getProcesses = function() {
return God.clusters_db;
};
God.getMonitorData = function(cb) {
var processes = God.getFormatedProcesses();
var arr = [];
function ex(i) {
if (i <= -1) return cb(null, arr);
var pro = processes[i];
usage.lookup(pro.pid, { keepHistory : true }, function(err, res) {
pro['monit'] = res;
arr.push(pro);
return ex(i - 1);
});
};
ex(processes.length - 1);
};
God.getFormatedProcesses = function() {
var db = God.clusters_db;
var arr = [];
for (var key in db) {
if (db[key])
arr.push({
pid : db[key].process.pid,
opts : db[key].opts,
pm_id : db[key].pm_id,
status : db[key].status
});
}
return arr;
};
God.findByScript = function(script) {
var db = God.clusters_db;
for (var key in db) {
if (db[key].opts.script == script) {
return db[key].opts;
}
}
return null;
};
God.checkProcess = function(pid) {
if (!pid) return false;
try {
// Sending 0 signal do not kill the process
process.kill(pid, 0);
return true;
}
catch (err) {
return false;
}
};
God.startProcess = function(clu, cb) {
God.clusters_db[clu.pm_id].opts.max = 99;
execute(God.clusters_db[clu.pm_id].opts, cb);
};
God.startProcessId = function(id, cb) {
if (God.clusters_db[id] === undefined)
return cb({ msg : "PM ID unknown"}, {});
if (God.clusters_db[id].status == "online")
return cb({ msg : "Process already online"}, {});
God.clusters_db[id].opts.max = 99;
return execute(God.clusters_db[id].opts, cb);
};
God.stopProcess = function(clu, cb) {
God.clusters_db[clu.pm_id].opts.max = 0;
process.kill(God.clusters_db[clu.pm_id].process.pid);
God.clusters_db[clu.pm_id].process.pid = 0;
setTimeout(cb, 200);
};
God.stopProcessId = function(id, cb) {
God.clusters_db[id].opts.max = 0;
process.kill(God.clusters_db[id].process.pid);
God.clusters_db[id].process.pid = 0;
setTimeout(cb, 200);
};
//
// Public method
//
God.prepare = function(opts, cb) {
if (opts.instances) {
// instances "max" have been setted
var arr = [];
(function ex(i) {
if (i <= 0) {
if (cb != null) return cb(null, arr);
return true;
}
return execute(JSON.parse(JSON.stringify(opts)), function(err, clu) { // deep copy
arr.push(clu);
ex(i - 1);
});
})(numCPUs);
}
else return execute(opts, cb);
};
God.stopAll = function(cb) {
var pros = God.getFormatedProcesses();
var l = pros.length;
(function ex(processes, i) {
if (i <= -1) return cb(null, God.getFormatedProcesses());
if (processes[i].state == 'stopped')
return ex(processes, i - 1);
return God.stopProcess(processes[i], function() {
ex(processes, i - 1);
});
})(pros, l - 1);
};
God.getProcesses = function() {
return God.clusters_db;
};
God.getMonitorData = function(cb) {
var processes = God.getFormatedProcesses();
if (opts.instances) {
// instances "max" have been setted
// multi fork depending on number of cpus
var arr = [];
function ex(i) {
if (i <= -1) return cb(null, arr);
var pro = processes[i];
usage.lookup(pro.pid, { keepHistory : true }, function(err, res) {
pro['monit'] = res;
arr.push(pro);
return ex(i - 1);
});
};
ex(processes.length - 1);
};
God.getFormatedProcesses = function() {
var db = God.clusters_db;
var arr = [];
for (var key in db) {
if (db[key])
arr.push({
pid : db[key].process.pid,
opts : db[key].opts,
pm_id : db[key].pm_id,
status : db[key].status
});
}
return arr;
};
God.checkProcess = function(pid) {
if (!pid) return false;
try {
// Sending 0 signal do not kill the process
process.kill(pid, 0);
(function ex(i) {
if (i <= 0) {
if (cb != null) return cb(null, arr);
return true;
}
catch (err) {
return false;
}
};
God.startProcess = function(clu, cb) {
God.clusters_db[clu.pm_id].opts.max = 99;
execute(God.clusters_db[clu.pm_id].opts);
};
God.stopProcess = function(clu, cb) {
God.clusters_db[clu.pm_id].opts.max = 0;
process.kill(God.clusters_db[clu.pm_id].process.pid);
God.clusters_db[clu.pm_id].process.pid = 0;
setTimeout(cb, 200);
}
return execute(JSON.parse(JSON.stringify(opts)), function(err, clu) { // deep copy
arr.push(clu);
ex(i - 1);
});
})(numCPUs);
}
else return execute(opts, cb);
};
//
// Private methods
//
function execute(opts, cb) {
var id;
var id, exec_path, out_log_path, err_log_path, pid_path;
if (opts.pm_id)
id = opts.pm_id;
else {
id = God.next_id;
God.next_id += 1;
}
var clu = cluster.fork({
pm_script : opts.script,
pm_errFile : opts.fileError,
pm_outFile : opts.fileOutput,
pm_pidFile : opts.pidFile,
pm_id : id
});
opts['pm_id'] = id;
clu['pm_id'] = id;
clu['opts'] = opts;
clu['status'] = 'launching';
id = God.next_id;
God.next_id += 1;
God.clusters_db[clu.pm_id] = clu;
var clu = cluster.fork({
pm_script : opts["pm_exec_path"],
pm_errFile : opts["pm_err_log_path"],
pm_outFile : opts["pm_out_log_path"],
pm_pidFile : opts["pm_pid_path"],
pm_id : id
});
clu.once('online', function() {
God.clusters_db[clu.pm_id].status = 'online';
if (cb) return cb(null, clu);
return true;
});
opts['pm_id'] = id;
clu['pm_id'] = id;
clu['opts'] = opts;
clu['status'] = 'launching';
// Should fix it
// DONT use it for now !!
clu.start = function(cb) {
var self = this;
God.clusters_db[id] = clu;
execute(this.opts, function(err, clu) {
cb(err, clu);
});
};
clu.stop = function(cb) {
clu.once('online', function() {
God.clusters_db[id].status = 'online';
if (cb) return cb(null, clu);
return true;
});
};
return clu;
return clu;
}
// (function exec() {
// God.prepare({
// script : './examples/child.js',
// fileError : 'logs/errLog.log',
// fileOutput : 'logs/outLog.log',
// pidFile : 'pids/child',
// max : 2,
// instances : 'max'
// });
// God.prepare({
// script : './examples/echo.js',
// fileError : 'logs/echoErr.log',
// fileOutput : 'logs/echoLog.log',
// pidFile : 'pids/child'
// });
// setInterval(function() {
// //console.log(God.clusters_db);
// God.getMonitorData(function(dt) {
// console.log(dt);
// });
// }, 1000);
// })();

85
lib/Log.js Normal file
View File

@ -0,0 +1,85 @@
//
// Display a file in streaming
//
var fs = require('fs');
var colors = [
'\x1B[34m',
'\x1B[36m',
'\x1B[32m',
'\x1B[35m',
'\x1B[31m',
'\x1B[30m',
'\x1B[90m',
'\x1B[33m',
'\x1B[34m',
'\x1B[36m',
'\x1B[32m',
'\x1B[35m',
'\x1B[31m',
'\x1B[30m',
'\x1B[90m',
'\x1B[33m'
];
var gl_idx = 0;
var db = [];
var Log = module.exports = {};
Log.stream = function(path, title) {
if (title === undefined)
title = gl_idx;
try {
var currSize = 0;
if (fs.statSync(path).size > 1000)
currSize = fs.statSync(path).size - 500;
} catch(e) {
if (e.code == 'ENOENT')
console.log('%s with %s file not found', title, path);
return false;
}
var odb = db[title] = {color : colors[gl_idx++], l : 0};
fs.watch(path, function(ev, filename) {
if (ev == 'rename')
return console.error('Renaming file ?');
fs.stat(path, function(err, stat) {
var prevSize = stat.size;
if (currSize > prevSize) return true;
var rstream = fs.createReadStream(path, {
encoding : 'utf8',
start : currSize,
end : prevSize
});
rstream.on('data', function(data) {
print_data(odb, title, data);
});
currSize = stat.size;
return true;
});
return true;
});
};
//
// Privates
//
function print_data(odb, title, data) {
var lines = data.split('\n');
lines.forEach(function(l) {
if (l)
console.log(odb.color + '[%s (l%d)]\x1B[39m %s',
title,
odb.l++,
l);
});
};

26
lib/cli-ux.js Normal file
View File

@ -0,0 +1,26 @@
var Table = require('cli-table');
var UX = module.exports = {};
UX.dispAsTable = function(list) {
var table = new Table({ head: ["Script", "id", "PID","status", "memory", "out logs", "err logs", "full path"] });
list.forEach(function(l) {
var u = l.opts.script;
var obj = {};
obj[l.opts.script] = [
l.pm_id,
l.pid,
l.status,
l.monit ? l.monit.memory : '',
l.opts.fileOutput,
l.opts.fileError,
l.opts.pm_exec_path
];
table.push(obj);
});
console.log(table.toString());
}

72
lib/logger.js Normal file
View File

@ -0,0 +1,72 @@
//
// Display a file in streaming
//
var fs = require('fs');
var colors = [
'\x1B[34m',
'\x1B[36m',
'\x1B[32m',
'\x1B[35m',
'\x1B[31m',
'\x1B[30m',
'\x1B[90m',
'\x1B[33m'
];
var gl_idx = 0;
var db = [];
var Log = module.exports = {};
Log.stream_log = function(title, path) {
try {
var currSize = 0; //fs.statSync(path).size;
} catch(e) {
if (e.code == 'ENOENT')
console.log('%s with %s file not found', title, path);
return false;
}
var odb = db[title] = {color : colors[gl_idx++], l : 0};
fs.watch(path, function(ev, filename) {
if (ev == 'rename')
return console.error('Renaming file ?');
fs.stat(path, function(err, stat) {
var prevSize = stat.size;
if (currSize > prevSize) return true;
var rstream = fs.createReadStream(path, {
encoding : 'utf8',
start : currSize,
end : prevSize
});
rstream.on('data', function(data) {
print_data(odb, title, data);
});
currSize = stat.size;
return true;
});
return true;
});
}
//
// Privates
//
function print_data(odb, title, data) {
var lines = data.split('\n');
lines.forEach(function(l) {
if (l)
console.log(odb.color + '[%s (l%d)]\x1B[39m %s',
title,
odb.l++,
l);
});
};

View File

@ -3,92 +3,89 @@ var multimeter = require('multimeter');
var bars = {};
var Monit = module.exports = {
init : init,
refresh : refresh
};
var Monit = module.exports = {};
function init(processes) {
Monit.multi = multimeter(process);
Monit.init = function(processes) {
if (processes === undefined) throw new Error('No processes passed to init');
Monit.multi = multimeter(process);
Monit.multi.on('^C', process.exit);
Monit.multi.charm.reset();
Monit.multi.on('^C', process.exit);
Monit.multi.charm.reset();
Monit.multi.write('PM2 monitoring :\n\n');
Monit.multi.write('PM2 monitoring :\n\n');
processes.forEach(function(proc, i) {
if (proc.status == 'stopped') return ;
Monit.multi.write(proc.opts.script + ' [' + proc.pid + '] ' + ' \n\n');
processes.forEach(function(proc, i) {
if (proc.status == 'stopped') return ;
Monit.multi.write(proc.opts.script + ' [' + proc.pid + '] ' + ' \n\n');
var bar_cpu = Monit.multi(40, (i * 2) + 3 + i, {
width: 30,
solid: {
text: '|',
foreground: 'white',
background: 'blue'
},
empty: {
text: ' '
}
});
var bar_memory = Monit.multi(40, (i * 2) + 4 + i, {
width: 30,
solid: {
text: '|',
foreground: 'white',
background: 'red'
},
empty: {
text: ' '
}
});
bar_cpu.percent(proc.monit.cpu);
bar_memory.ratio(proc.monit.memory,
200000000,
bytesToSize(proc.monit.memory, 3));
bars[proc.pid] = {};
bars[proc.pid].memory = bar_memory;
bars[proc.pid].cpu = bar_cpu;
Monit.multi.write('\n');
var bar_cpu = Monit.multi(40, (i * 2) + 3 + i, {
width: 30,
solid: {
text: '|',
foreground: 'white',
background: 'blue'
},
empty: {
text: ' '
}
});
var bar_memory = Monit.multi(40, (i * 2) + 4 + i, {
width: 30,
solid: {
text: '|',
foreground: 'white',
background: 'red'
},
empty: {
text: ' '
}
});
bar_cpu.percent(proc.monit.cpu);
bar_memory.ratio(proc.monit.memory,
200000000,
bytesToSize(proc.monit.memory, 3));
bars[proc.pid] = {};
bars[proc.pid].memory = bar_memory;
bars[proc.pid].cpu = bar_cpu;
Monit.multi.write('\n');
});
}
function refresh(dt) {
if (Object.keys(bars).length == 0) {
Monit.multi.write('No online process to monitor\n');
process.exit(1);
Monit.refresh = function(dt) {
if (Object.keys(bars).length == 0) {
Monit.multi.write('No online process to monitor\n');
process.exit(1);
}
dt.forEach(function(proc, i) {
if (proc && proc.monit && bars[proc.pid]) {
bars[proc.pid].cpu.percent(proc.monit.cpu);
bars[proc.pid].memory.ratio(proc.monit.memory,
200000000,
bytesToSize(proc.monit.memory, 3));
}
dt.forEach(function(proc, i) {
if (proc && proc.monit && bars[proc.pid]) {
bars[proc.pid].cpu.percent(proc.monit.cpu);
bars[proc.pid].memory.ratio(proc.monit.memory,
200000000,
bytesToSize(proc.monit.memory, 3));
}
});
});
}
function bytesToSize(bytes, precision) {
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + ' B';
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
return (bytes / kilobyte).toFixed(precision) + ' KB';
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
return (bytes / megabyte).toFixed(precision) + ' MB';
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
return (bytes / gigabyte).toFixed(precision) + ' GB';
} else if (bytes >= terabyte) {
return (bytes / terabyte).toFixed(precision) + ' TB';
} else {
return bytes + ' B';
}
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + ' B';
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
return (bytes / kilobyte).toFixed(precision) + ' KB';
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
return (bytes / megabyte).toFixed(precision) + ' MB';
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
return (bytes / gigabyte).toFixed(precision) + ' GB';
} else if (bytes >= terabyte) {
return (bytes / terabyte).toFixed(precision) + ' TB';
} else {
return bytes + ' B';
}
}

7
lib/sample.json Normal file
View File

@ -0,0 +1,7 @@
{
"path" : "VARIABLE.js",
"fileError" : "out-VARIABLE.log",
"fileOutput" : "err-VARIABLE.log",
"pidFile" : "exec-VARIABLE.pid",
"options": [""]
}

48
lib/web-interface.js Normal file
View File

@ -0,0 +1,48 @@
try {
var express = require('express');
}
catch (e) {
console.error('[GOD] In order to use the web interface, Install express');
process.exit(1);
}
var http = require('http');
var os = require('os');
var Satan = require('../satan.js');
var app = express();
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.errorHandler());
app.use(express.logger('dev'));
app.get('/', function(req, res) {
var json = [];
Satan.executeRemote('list', {}, function(err, data_proc) {
// Computer API point
var data = {
processes: data_proc,
system_info: { hostname: os.hostname(),
uptime: os.uptime()
},
monit: { loadavg: os.loadavg(),
total_mem: os.totalmem(),
free_mem: os.freemem(),
cpu: os.cpus(),
interfaces: os.networkInterfaces()
}
};
return res.send(data);
});
});
var server = http.createServer(app);
server.listen(4000, function() {
console.log("Web server enabled on port 4000");
});

View File

@ -1,53 +0,0 @@
merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
Error: eh merde
at Object._onTimeout (/home/tknew/WiredCraft/api.devo.ps/manager/experimental/examples/echo.js:8:11)
at Timer.list.ontimeout (timers.js:101:19)merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
Error: eh merde
at Object._onTimeout (/home/tknew/WiredCraft/api.devo.ps/manager/experimental/examples/echo.js:8:11)
at Timer.list.ontimeout (timers.js:101:19)merde
Error: eh merde
at Object._onTimeout (/home/tknew/WiredCraft/api.devo.ps/manager/experimental/examples/echo.js:8:11)
at Timer.list.ontimeout (timers.js:101:19)merde
merde
merde
merde
merde
merde
merde
merde
merde
merde
Error: eh merde
at Object._onTimeout (/home/tknew/WiredCraft/api.devo.ps/manager/experimental/examples/echo.js:8:11)
at Timer.list.ontimeout (timers.js:101:19)merde
merde
merde
merde
merde
merde
Error: eh merde
at Object._onTimeout (/home/tknew/WiredCraft/api.devo.ps/manager/experimental/examples/echo.js:8:11)
at Timer.list.ontimeout (timers.js:101:19)

View File

@ -1,42 +0,0 @@
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -258,4 +258,4 @@ function findScript(script, cb) {
forever.getAllProcesses(function(dt) {
cb(forever.findByScript(script, dt));
});
};
};

View File

@ -1,46 +1,57 @@
{
"name": "god",
"preferGlobal": "true",
"version": "0.4.0",
"description": "Manage processes with JSON files, reload, monitor, show logs and manage process programaticaly",
"main": "index.js",
"scripts": {
"test": "NODE_ENV=test ./node_modules/mocha/bin/mocha test ; bash test/cli-test.sh",
"froze": "npm shrinkwrap"
},
"keywords": [
"cli",
"fault tolerant",
"sysadmin",
"tools",
"monitoring",
"process manager",
"forever",
"process configuration"],
"bin": {
"pm2": "./bin/cli"
},
"dependencies": {
"commander": "*",
"cli-table" : "*",
"multimeter": "*",
"usage" : "*",
"watch": "*",
"axon-rpc" : "*",
"axon" : "*"
},
"devDependencies": {
"mocha": "1.x",
"should": "1.x"
},
"repository": "",
"author": {
"name": "AS"
},
"contributors": [{
"name": "Strzelewicz Alexandre",
"email": "strzelewicz.alexandre@gmail.com",
"website": "http://apps.hemca.com"
}],
"license": "MIT"
"name": "god",
"preferGlobal": "true",
"version": "0.4.0",
"os" : [ "!win32" ],
"engines" : {
"node" : ">=0.8"
},
"homepage" : "http://unitech.io/",
"description": "Manage apps with a declarative approach (JSON). Clusterize network apps in 0 lines of code.",
"main": "index.js",
"scripts": {
"test": "NODE_ENV=test ./node_modules/mocha/bin/mocha test",
"testc": "NODE_ENV=test ./node_modules/mocha/bin/mocha test ; bash ./test/cli.sh",
"froze": "npm shrinkwrap"
},
"keywords": [
"cli",
"fault tolerant",
"sysadmin",
"tools",
"monitoring",
"process manager",
"forever",
"process configuration",
"clustering",
"cluster cli",
"cluster"
],
"bin": {
"pm2": "./bin/god"
},
"dependencies": {
"commander": "*",
"cli-table" : "*",
"multimeter": "git://github.com/Alexandre-Strzelewicz/node-multimeter.git",
"usage" : "*",
"watch": "*",
"axon-rpc" : "*",
"axon" : "*"
},
"devDependencies": {
"mocha": "1.x",
"should": "1.x",
"better-assert" : "*"
},
"repository": "",
"author": {
"name": "AS"
},
"contributors": [{
"name": "Strzelewicz Alexandre",
"email": "strzelewicz.alexandre@gmail.com",
"url": "http://apps.hemca.com"
}],
"license": "MIT"
}

View File

@ -1 +0,0 @@
7554

View File

@ -1 +0,0 @@
7556

View File

@ -1 +0,0 @@
18217

View File

@ -1 +0,0 @@
18220

View File

@ -1 +0,0 @@
15718

View File

@ -1 +0,0 @@
7560

View File

@ -1 +0,0 @@
7563

View File

@ -1 +0,0 @@
18178

View File

@ -1 +0,0 @@
18180

View File

@ -1 +0,0 @@
18184

View File

@ -1 +0,0 @@
18187

View File

@ -1 +0,0 @@
18212

View File

@ -1 +0,0 @@
18214

257
satan.js
View File

@ -4,149 +4,184 @@ var axon = require('axon');
var rep = axon.socket('rep');
var req = axon.socket('req');
var debug = require('debug')('god:satan');
var God = require('./god.js');
var events = require("events");
var util = require("util");
const SATAN_PORT = 66666;
var Satan = module.exports = {
var Satan = module.exports = {};
//
// Code switcher
//
Satan.onReady = function() {
(function init() {
if (process.env.DAEMON) {
Satan.remoteWrapper();
}
else {
Satan.pingDaemon(function(ab) {
if (ab == false)
return Satan.launchDaemon(Satan.launchRPC);
return Satan.launchRPC();
});
}
})();
};
//
// The code that will be executed on the next process
// Here it exposes God methods
//
Satan.remoteWrapper = function() {
// Send ready message to Satan Client
process.send({
online : true, success : true, pid : process.pid
});
var server = new rpc.Server(rep);
rep.bind(66666);
server.expose({
prepare : function(opts, fn) {
God.prepare(opts, function(err, clu) {
fn(null, stringifyOnce(clu, undefined, 0));
});
},
list : function(opts, fn) {
God.getMonitorData(fn);
},
stop : function(opts, fn) {
God.stopAll(fn);
}
});
};
// Only require here because God init himself
var God = require('./god.js');
Satan.onReady = function() {
(function init() {
if (process.env.DAEMON) {
Satan.remoteWrapper();
}
else {
isDaemonReachable(function(ab) {
if (ab == false)
return Satan.launchDaemon(Satan.launchRPC);
return Satan.launchRPC();
});
}
})();
// Send ready message to Satan Client
process.send({
online : true, success : true, pid : process.pid
});
var server = new rpc.Server(rep);
rep.bind(SATAN_PORT);
server.expose({
prepare : function(opts, fn) {
God.prepare(opts, function(err, clu) {
fn(null, stringifyOnce(clu, undefined, 0));
});
},
list : function(opts, fn) {
God.getMonitorData(fn);
},
startId : function(opts, fn) {
God.startProcessId(opts.id, function(err, clu) {
fn(err, stringifyOnce(clu, undefined, 0));
});
},
stop : function(opts, fn) {
God.stopAll(fn);
},
killMe : function(fn) {
console.log('Killing daemon');
fn(null, {});
process.exit(0);
},
findByScript : function(opts, fn) {
fn(null, God.findByScript(opts.script));
},
daemonData: function(fn) {
fn(null, {
pid : process.pid
});
}
});
};
Satan.launchRPC = function() {
debug('Launching RPC client');
Satan.client = new rpc.Client(req);
this.ev = req.connect(66666);
this.ev.on('connect', function() {
process.emit('satan:ready');
});
debug('Launching RPC client');
Satan.client = new rpc.Client(req);
Satan.ev = req.connect(SATAN_PORT);
Satan.ev.on('connect', function() {
process.emit('satan:client:ready');
});
};
Satan.getExposedMethods = function(cb) {
Satan.client.methods(function(err, methods) {
cb(err, methods);
});
};
//
// Interface to connect to the client
//
Satan.executeRemote = function(method, opts, fn) {
Satan.client.call(method, opts, function(err, res) {
fn(err, res);
});
Satan.client.call(method, opts, function(err, res) {
fn(err, res);
});
};
Satan.killDaemon = function(fn) {
Satan.client.call('killMe', function(err, res) {
fn(err, res);
});
};
Satan.launchDaemon = function(cb) {
debug('Launching daemon');
var path = require('path');
var child = require("child_process").fork(path.resolve("./satan.js"), [], {
silent : false,
detached: true,
cwd: process.cwd(),
env : {
"DAEMON" : true
},
stdio: "ignore"
}, function(err, stdout, stderr) {
debug(arguments);
//console.log(stdout);
});
child.unref();
child.once('message', function(msg) {
console.log(msg);
return cb(child);
});
debug('Launching daemon');
var path = require('path');
// Todo : Redirect daemon logs
var child = require("child_process").fork(path.resolve("./satan.js"), [], {
silent : false,
detached: true,
cwd: process.cwd(),
env : {
"DAEMON" : true
},
stdio: "ignore"
}, function(err, stdout, stderr) {
debug(arguments);
});
child.unref();
child.once('message', function(msg) {
process.emit('satan:daemon:ready');
console.log(msg);
return setTimeout(function() {cb(child)}, 100); // Put a little time out
});
};
// TODO : do it better
Satan.pingDaemon = function(cb) {
var req = axon.socket('req');
var client = new rpc.Client(req);
debug('Trying to connect to server');
client.sock.once('reconnect attempt', function() {
client.sock.close();
debug('Daemon not launched');
cb(false);
});
client.sock.once('connect', function() {
client.sock.close();
debug('Daemon alive');
cb(true);
});
req.connect(SATAN_PORT);
};
// Change Circular dependies to null
function stringifyOnce(obj, replacer, indent){
var printedObjects = [];
var printedObjectKeys = [];
var printedObjects = [];
var printedObjectKeys = [];
function printOnceReplacer(key, value){
var printedObjIndex = false;
printedObjects.forEach(function(obj, index){
if(obj===value){
printedObjIndex = index;
}
});
function printOnceReplacer(key, value){
var printedObjIndex = false;
printedObjects.forEach(function(obj, index){
if(obj===value){
printedObjIndex = index;
}
});
if(printedObjIndex && typeof(value)=="object"){
return "null";
}else{
var qualifiedKey = key || "(empty key)";
printedObjects.push(value);
printedObjectKeys.push(qualifiedKey);
if(replacer){
return replacer(key, value);
}else{
return value;
}
}
if(printedObjIndex && typeof(value)=="object"){
return "null";
}else{
var qualifiedKey = key || "(empty key)";
printedObjects.push(value);
printedObjectKeys.push(qualifiedKey);
if(replacer){
return replacer(key, value);
}else{
return value;
}
}
return JSON.stringify(obj, printOnceReplacer, indent);
}
// TODO : do it better
function isDaemonReachable(cb) {
var req = axon.socket('req');
var client = new rpc.Client(req);
debug('Trying to connect to server');
client.sock.once('reconnect attempt', function() {
client.sock.close();
debug('Daemon not launched');
cb(false);
});
client.sock.once('connect', function() {
client.sock.close();
debug('Daemon alive');
cb(true);
});
req.connect(66666);
}
return JSON.stringify(obj, printOnceReplacer, indent);
}
Satan.onReady();

View File

@ -10,8 +10,9 @@ console.log(process.env.NODE_UNIQUE_ID, cluster.isWorker);
var i = 0;
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n" + i++);
res.writeHead(200);
console.log('rcv', process.pid);
res.end("hello world\n" + i++);
}).listen(8000);
// setTimeout(function() {

View File

@ -1,9 +1,8 @@
setInterval(function() {
console.log('ok');
console.error('merde');
}, 500);
setTimeout(function() {
throw new Error('eh merde');
}, 3000);
// setTimeout(function() {
// throw new Error('eh merde');
// }, 3000);

View File

@ -1,73 +1,76 @@
var God = require('..');
var numCPUs = require('os').cpus().length;
describe('God', function() {
it('should have right properties', function() {
God.should.have.property('prepare');
God.should.have.property('getProcesses');
God.should.have.property('getMonitorData');
God.should.have.property('getFormatedProcesses');
God.should.have.property('checkProcess');
God.should.have.property('stopAll');
it('should have right properties', function() {
God.should.have.property('prepare');
God.should.have.property('getProcesses');
God.should.have.property('getMonitorData');
God.should.have.property('getFormatedProcesses');
God.should.have.property('checkProcess');
God.should.have.property('stopAll');
God.should.have.property('stopProcessId');
});
describe('One process', function() {
var proc;
after(function(done) {
God.stopAll(done);
});
describe('One process', function() {
var proc;
after(function(done) {
God.stopAll(done);
});
it('should fork one process', function(done) {
God.prepare({
script : './test/fixtures/echo.js',
fileError : 'logs/echoErr.log',
fileOutput : 'logs/echoLog.log',
pidFile : 'pids/child'
}, function(err, proce) {
proc = proce;
proc.status.should.be.equal('online');
God.getFormatedProcesses().length.should.equal(1);
done();
});
});
it('should stop process and no more present', function(done) {
proc.status.should.be.equal('online');
God.checkProcess(proc.process.pid).should.be.true;
proc.stop(function() {
God.getFormatedProcesses().length.should.equal(1);
God.checkProcess(proc.process.pid).should.be.false;
proc.status.should.be.equal('stopped');
done()
});
});
it.skip('should start the process', function(done) {
proc.start(function(err, proc) {
God.checkProcess(proc.process.pid).should.be.true;
proc.status.should.be.equal('online');
console.log(God.getFormatedProcesses());
God.getFormatedProcesses().length.should.equal(1);
done();
});
});
it('should fork one process', function(done) {
God.prepare({
script : './test/fixtures/echo.js',
fileError : 'logs/echoErr.log',
fileOutput : 'logs/echoLog.log',
pidFile : 'pids/child'
}, function(err, proce) {
proc = proce;
proc.status.should.be.equal('online');
God.getFormatedProcesses().length.should.equal(1);
done();
});
});
describe('Multi launching', function() {
it('should launch multiple processes', function(done) {
God.prepare({
script : './test/fixtures/child.js',
fileError : 'logs/errLog.log',
fileOutput : 'logs/outLog.log',
pidFile : 'pids/child',
instances : 'max'
}, function(err, procs) {
God.getFormatedProcesses().length.should.equal(5);
done();
});
});
it('should stop process and no more present', function(done) {
proc.status.should.be.equal('online');
God.checkProcess(proc.process.pid).should.be.true;
God.stopProcess(proc, function() {
God.getFormatedProcesses().length.should.equal(0);
God.checkProcess(proc.process.pid).should.be.false;
proc.status.should.be.equal('stopped');
done()
});
});
// Process stopped are not anymore cached in db
it.skip('should start the process', function(done) {
God.startProcess(proc, function(err, proc) {
God.checkProcess(proc.process.pid).should.be.true;
proc.status.should.be.equal('online');
God.getFormatedProcesses().length.should.equal(1);
done();
});
});
});
describe('Multi launching', function() {
it('should launch multiple processes depending on CPUs available', function(done) {
God.prepare({
script : './test/fixtures/child.js',
fileError : 'logs/errLog.log',
fileOutput : 'logs/outLog.log',
pidFile : 'pids/child',
instances : 'max'
}, function(err, procs) {
God.getFormatedProcesses().length.should.equal(numCPUs);
procs.length.should.equal(numCPUs);
done();
});
});
});
});

11
test/monit.mocha.js Normal file
View File

@ -0,0 +1,11 @@
var Monit = require('../lib/monit.js');
var should = require('should');
var assert = require('better-assert');
describe('Monit', function() {
it('should have right properties', function() {
Monit.should.have.property('init');
Monit.should.have.property('refresh');
});
});

88
test/satan.mocha.js Normal file
View File

@ -0,0 +1,88 @@
var Satan;
var should = require('should');
var assert = require('better-assert');
describe('Satan', function() {
after(function(done) {
Satan.killDaemon(function() {
done();
});
});
it('should auto instancy itself, fire event and kill daemon', function(done) {
Satan = require('../satan.js');
process.once('satan:client:ready', function() {
console.log('Client ready');
Satan.killDaemon(function() {
done();
})
});
});
it('should start daemon', function(done) {
Satan.launchDaemon(function(child) {
assert(typeof child.pid == 'number');
Satan.pingDaemon(function(online) {
console.log(online);
assert(online == true);
done();
});
});
});
it('should have right properties', function() {
Satan.should.have.property('remoteWrapper');
Satan.should.have.property('onReady');
Satan.should.have.property('launchRPC');
Satan.should.have.property('executeRemote');
Satan.should.have.property('launchDaemon');
Satan.should.have.property('getExposedMethods');
Satan.should.have.property('pingDaemon');
Satan.should.have.property('killDaemon');
});
describe('DAEMON', function() {
it('should have the right exposed methods via RPC', function(done) {
Satan.getExposedMethods(function(err, methods) {
assert(err == null);
methods.should.have.property('prepare');
methods.should.have.property('list');
methods.should.have.property('stop');
methods.should.have.property('killMe');
methods.should.have.property('daemonData');
done();
});
});
it('should get an empty process list', function(done) {
Satan.executeRemote('list', {}, function(err, res) {
assert(res.length === 0);
done();
});
});
it('should launch a process', function(done) {
Satan.executeRemote('prepare', {
script : './test/fixtures/child.js',
fileError : 'logs/errLog.log',
fileOutput : 'logs/outLog.log',
pidFile : 'pids/child',
instances : 'max'
}, function(err, procs) {
assert(err == null);
assert(JSON.parse(procs).length == 4);
done();
});
});
it('should list 4 processes', function(done) {
Satan.executeRemote('list', {}, function(err, res) {
assert(res.length === 4);
done();
});
});
});
});