UNPKG

damage

Version:

A simple way to calculate the 'damage' of running a task in Node.JS.

319 lines (279 loc) 7.19 kB
// 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;