damage
Version:
A simple way to calculate the 'damage' of running a task in Node.JS.
319 lines (279 loc) • 7.19 kB
JavaScript
// Requirements
var _ = require('lodash');
var q = require('q');
var util = require('util');
var os = require('os');
var child = require('child_process');
var fork = child.fork;
var colors = require('colors');
var path = require('path');
var fs = require('fs');
// States
var queue = [];
var running = false;
var initialData;
var resultListener;
var entryMsg=false;
var _config = {};
var _env = {};
var _prepare = function () {};
var self;
function Damage (config,env,prepare)
{
self = this;
_config = config;
_env = env;
_prepare = prepare;
}
/**
* Create the damage calculation promise.
*/
Damage.prototype.exec = function (desc, fn, repeats)
{
var task = self.create(desc,fn,repeats);
var defer = task[3];
defer.promise.defaultMessage=true;
defer.promise.is = self.listener;
defer.promise.compare
= defer.promise.compared
= defer.promise.comparedTo
= self.compare;
defer.promise.task = task;
self.add(task);
if (!running)
{
running = true;
if (!entryMsg)
{
console.log('\n How much is the '.grey+'DAMAGE'.bold.red+' of...'.grey)
entryMsg = true;
}
process.nextTick(function () {
self.next();
});
}
return defer.promise;
}
/**
* Create a new task.
*/
Damage.prototype.create = function (desc,fn,repeats)
{
if (!_.isString(desc)||!_.isFunction(fn))
return false;
var defer = q.defer();
repeats = repeats || 1;
return [desc,fn,repeats,defer,[]];
}
/**
* Add a task to the queue.
*/
Damage.prototype.add = function (task)
{
if (!_.isArray(task))
return false;
queue.push(task);
}
/**
* Run the damage calculation function.
*/
Damage.prototype.run = function (desc,fn,repeats,defer,compare)
{
console.log('');
console.log(' '+'»'.bold.red+' '+desc+' ('+(repeats+'x').cyan+')');
var cwd = path.resolve(__dirname,'../tmp');
if (!fs.existsSync(cwd))
fs.mkdirSync(cwd);
var scriptTestFile = '/damage-script.'+Math.floor(Math.random()*89999+10000)+'.tmp.js';
fs.readFile(__dirname+'/env-script.js',function (err,script) {
script=(script+'').replace(/\/\/TASK\/\//gim,'var desc="'+desc+'",repeats='+repeats+';');
script=script.replace(/\/\/PREPARE\/\//gim,_prepare.toString().replace(/^function \(\) \{/g,'').replace(/\}$/g,''));
script=script.replace(/\/\/TEST\/\//gim,'('+fn.toString()+')(done);');
fs.writeFile(cwd+scriptTestFile,script,function (err) {
_env._config = _config;
var task = fork(cwd+scriptTestFile);
task.send(_env);
task.on('error',function (err) { console.log(err); })
task.on('message',function (result) {
if (result.error)
console.log(result.error);
_env = result.env || {};
fs.unlink(cwd+scriptTestFile);
process.nextTick(function () {
self.handleDone(desc,fn,repeats,defer,result);
});
});
});
});
}
/**
* Setup a listener to the result.
*/
Damage.prototype.listener = function (fn)
{
this.defaultMessage=false;
resultListener=fn;
}
/**
* Setup a damage comparison.
*/
Damage.prototype.compare = function (desc,fn,repeats)
{
var task = self.create(desc,fn,repeats);
if (task)
this.task[4].push(task);
return this;
}
/**
* Get process state before the damage test.
*/
Damage.prototype.getInitialData = function ()
{
initialData={};
initialData.time = +new Date;
initialData.rss = process.memoryUsage().rss;
initialData.heapTotal = process.memoryUsage().heapTotal;
initialData.heapUsed = process.memoryUsage().heapUsed;
}
/**
* Calculate the result of the damage.
*/
Damage.prototype.process = function (result)
{
var processed={};
processed.time = 0;
processed.rss = 0;
processed.heapTotal = 0;
processed.heapUsed = 0;
var maxCPU=0, totalCPU=0, cpuCount=0;
for (var i=0;i<result.startData.length;i++)
{
if (!result.finalData[i])
continue;
processed.time += Math.max(result.finalData[i].time-result.startData[i].time,0);
processed.rss += Math.max(result.finalData[i].rss-result.startData[i].rss,0);
processed.heapTotal += Math.max(result.finalData[i].heapTotal-result.startData[i].heapTotal,0);
processed.heapUsed += Math.max(result.finalData[i].heapUsed-result.startData[i].heapUsed,0);
if (result.finalData[i].processUsage.length>0)
{
var max = (Math.max.apply(Math,result.finalData[i].processUsage));
if (max>0)
{
totalCPU+=max;
if (max>maxCPU)
maxCPU=max;
cpuCount++;
}
}
}
processed.time = (processed.time).toFixed(2);
processed.rss = (processed.rss/1024/1024).toFixed(2);
processed.heapTotal = (processed.heapTotal/1024/1024).toFixed(2);
processed.heapUsed = (processed.heapUsed/1024/1024).toFixed(2);
processed.maxProcess = maxCPU.toFixed(2);
processed.avgProcess = Number(0).toFixed(2);
if (cpuCount>0)
processed.avgProcess=(totalCPU/cpuCount).toFixed(2)
return processed;
}
/**
* Colorize damage
*/
Damage.prototype.colorizeDamage = function (name,damage)
{
return damage;
}
/**
* Info sufixes.
*/
Damage.prototype.sufixes = {
"time": "ms",
"rss": "kb",
"heapTotal": "kb",
"heapUsed": "kb",
"maxProcess": "%",
"avgProcess": "%"
}
Damage.prototype.prefixes = {
"time": "Δ".green,
"rss": "Δ".green,
"heapTotal": "Δ".green,
"heapUsed": "Δ".green,
"maxProcess": "↑".bold.red,
"avgProcess": "Ø".yellow,
}
/**
* Return the sufix of the info.
*/
Damage.prototype.infoSufix = function (name)
{
return self.sufixes[name] || "";
}
/**
* Return the prefix of the info.
*/
Damage.prototype.infoPrefix = function (name)
{
return self.prefixes[name] || "";
}
/**
* Format the result
*/
Damage.prototype.formatResult = function (result)
{
var nameWidth = 0;
var dataWidth = 0;
for (r in result)
{
var dataStr = String(result[r]).valueOf();
if (r.length > nameWidth)
nameWidth = r.length;
if (dataStr.length > dataWidth)
dataWidth = dataStr.length;
result[r]=dataStr;
}
var strResult = '';
for (r in result)
{
var space = new Array(nameWidth-r.length+1).join(' ');
var spaceData = new Array(dataWidth-(result[r].length)+1).join(' ');
strResult+='\n'+' '+self.infoPrefix(r)+' '.green;
strResult+=(r+space+' = ').grey+spaceData+self.colorizeDamage(r,result[r]) +' ('+ self.infoSufix(r).grey+')';
}
return strResult;
}
/**
* Handles the DONE signal sent from the task.
*/
Damage.prototype.handleDone = function (desc,fn,repeats,defer,result)
{
if (defer.promise.defaultMessage)
{
var formatedResult = self.formatResult(self.process(result));
console.log(formatedResult)
}
else
{
resultListener&&resultListener(self.process(result),result);
}
running = false;
if (queue.length>0)
{
running = true;
process.nextTick(function () {
self.next();
});
}
}
/**
* Change to the next task on the queue.
*/
Damage.prototype.next = function ()
{
if (queue.length==0)
return false;
var task = queue.shift();
self.getInitialData();
self.run(task[0],task[1],task[2],task[3],task[4]);
}
module.exports = Damage;