├── .gitignore ├── README.md ├── app.js ├── ben ├── chart.js ├── index.html ├── install.sh ├── package.json └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodben 2 | Benchmark tool for any running process - Written in NodeJS 3 | 4 | ![](screenshot.png) 5 | 6 | # Installation 7 | Install dependencies with the following command: 8 | 9 | ``` 10 | npm install 11 | ``` 12 | 13 | Add current folder to your PATH to run `ben` command everywhere. Or just run `install.sh` 14 | 15 | # How to use 16 | First, you need to find the `Process ID` (pid) of the process you want to monitor: 17 | 18 | ``` 19 | ps -ax | grep 20 | ``` 21 | 22 | For example: 23 | 24 | ``` 25 | ps -ax | grep node app.js 26 | ``` 27 | 28 | Now, run `nodben`: 29 | 30 | ``` 31 | ./ben 32 | ``` 33 | 34 | The monitoring page will be available at [http://localhost:3030](http://localhost:3030) 35 | 36 | # To Do 37 | Know issues/todo: 38 | - Convert M, G, T units to Megabytes to display properly on chart 39 | - Linux support (fix the `top` command to make it work on Linux) 40 | - Multiple screen support (currently looks crappy on small screens) 41 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | var spawn = require('child_process').spawn; 4 | var express = require('express')(); 5 | var http = require('http'); 6 | var io = require('socket.io'); 7 | 8 | function Benchmark() { 9 | this.httpServer = http.Server(express); 10 | this.httpServer.listen(3030, function() { }); 11 | 12 | express.get('/', function(req, res) { 13 | res.sendFile(__dirname + '/index.html'); 14 | }); 15 | 16 | express.get('/chart.js', function(req, res) { 17 | res.sendFile(__dirname + '/chart.js'); 18 | }); 19 | 20 | this.server = io(this.httpServer); 21 | this.server.on('connection', function(socket){ 22 | socket.broadcast.emit('hi!'); 23 | }); 24 | } 25 | 26 | console.clearAll = function () { 27 | return process.stdout.write('\033c'); 28 | } 29 | 30 | Benchmark.prototype.start = function(pid) { 31 | var self = this; 32 | var topp = spawn('top', ['-pid', pid, '-l', 0, '-stats', 'pid,cpu,th,time,mem']); 33 | topp.stdout.on('data', function(stream) { 34 | var lines = stream.toString().split('\n'); 35 | 36 | var totalCPUline = lines[3]; 37 | var cpu_user_pattern = /\d+.\d+% user/g; 38 | var user_pattern = / user/g; 39 | var cpu_sys_pattern = /\d+.\d+% sys/g; 40 | var sys_pattern = / sys/g; 41 | var cpu_idle_pattern = /\d+.\d+% idle/g; 42 | var idle_pattern = / idle/g; 43 | 44 | var diskUsageLine = lines[9]; 45 | var disk_read_pattern = /\d+\/\d+[BKMGT] read/g; 46 | var disk_write_pattern = /\d+\/\d+[BKMGT] written/g; 47 | var read_pattern = / read/g; 48 | var write_pattern = / written/g; 49 | console.log(diskUsageLine.match(disk_write_pattern)); 50 | var read_text = diskUsageLine.match(disk_read_pattern)[0].replace(read_pattern, '').split('/'); 51 | var write_text = diskUsageLine.match(disk_write_pattern)[0].replace(write_pattern, '').split('/'); 52 | 53 | var netUsageLine = lines[8]; 54 | var net_in_pattern = /\d+\/\d+[BKMGT] in/g; 55 | var net_out_pattern = /\d+\/\d+[BKMGT] out/g; 56 | var in_pattern = / in/g; 57 | var out_pattern = / out/g; 58 | var net_in_text = netUsageLine.match(net_in_pattern)[0].replace(in_pattern, '').split('/'); 59 | var net_out_text = netUsageLine.match(net_out_pattern)[0].replace(out_pattern, '').split('/'); 60 | 61 | var processData = lines[lines.length -2].split(' ').filter(Boolean); 62 | var processInfo = { 63 | pid: processData[0], 64 | cpu: processData[1], 65 | threads: processData[2], 66 | time: processData[3], 67 | mem: processData[4] 68 | }; 69 | 70 | var totalDisk = { 71 | read: read_text[0], 72 | read_size: read_text[1], 73 | write: write_text[0], 74 | write_size: write_text[1] 75 | }; 76 | 77 | var totalNetworks = { 78 | in: net_in_text[0], 79 | in_size: net_in_text[1], 80 | out: net_out_text[0], 81 | out_size: net_out_text[1] 82 | }; 83 | 84 | var totalCPU = { 85 | user: totalCPUline.match(cpu_user_pattern)[0].replace(user_pattern, ''), 86 | sys: totalCPUline.match(cpu_sys_pattern)[0].replace(sys_pattern, ''), 87 | idle: totalCPUline.match(cpu_idle_pattern)[0].replace(idle_pattern, '') 88 | }; 89 | 90 | var result = { 91 | process: processInfo, 92 | computer: { 93 | cpu: totalCPU, 94 | disk: totalDisk, 95 | networks: totalNetworks 96 | } 97 | }; 98 | 99 | console.clearAll(); 100 | 101 | if (self.server != undefined) { 102 | self.server.emit('benchmark', result); 103 | } 104 | 105 | console.log(stream.toString()); 106 | console.log(); 107 | console.log('Benchmark server is running on http://localhost:3030/'); 108 | }); 109 | } 110 | 111 | module.exports = Benchmark; 112 | })(); 113 | -------------------------------------------------------------------------------- /ben: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | 3 | // Usage: ben 4 | // or : ./ben 5 | 6 | var Benchmark = require('./app'); 7 | var params = process.argv; 8 | var pid = params[2]; 9 | console.log(pid); 10 | var ben = new Benchmark(); 11 | ben.start(pid); 12 | -------------------------------------------------------------------------------- /chart.js: -------------------------------------------------------------------------------- 1 | var chartRT = function (parent) { 2 | var _self = this; 3 | 4 | function s4() { 5 | return Math.floor((1 + Math.random()) * 0x10000) 6 | .toString(16) 7 | .substring(1); 8 | }; 9 | 10 | function guid() { 11 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 12 | s4() + '-' + s4() + s4() + s4(); 13 | } 14 | 15 | _self.guid = guid(); 16 | _self.DataSeries = []; 17 | _self.Ticks = 20; 18 | _self.TickDuration = 1000; //1 Sec 19 | _self.MaxValue = 100; 20 | _self.w = d3.select(parent).node().getBoundingClientRect().width; 21 | _self.h = 280; 22 | _self.margin = { top: 50, right: 40, bottom: 50, left: 80 }; 23 | _self.width = _self.w - _self.margin.left - _self.margin.right; 24 | _self.height = _self.h - _self.margin.top - _self.margin.bottom; 25 | _self.xText = ''; 26 | _self.yText = ''; 27 | _self.titleText = ''; 28 | _self.chartSeries = {}; 29 | 30 | _self.Init = function () { 31 | d3.select('#chart-' + _self.guid).remove(); 32 | 33 | _self.svg = d3.select(parent).append("svg") 34 | .attr("id", 'chart-' + _self.guid) 35 | .attr("width", _self.w) 36 | .attr("height", _self.h) 37 | .append("g") 38 | .attr("transform", "translate(" + _self.margin.left + "," + _self.margin.top + ")"); 39 | // 40 | // Use Clipping to hide chart mechanics 41 | // 42 | _self.svg.append("defs").append("clipPath") 43 | .attr("id", "clip-" + _self.guid) 44 | .append("rect") 45 | .attr("width", _self.width) 46 | .attr("height", _self.height); 47 | // 48 | // Generate colors from DataSeries Names 49 | // 50 | 51 | _self.color = d3.scale.category10(); 52 | _self.color.domain(_self.DataSeries.map(function (d) { return d.Name; })); 53 | // 54 | // X,Y Scale 55 | // 56 | _self.xscale = d3.scale.linear().domain([0, _self.Ticks]).range([0, _self.width]); 57 | _self.yscale = d3.scale.linear().domain([0, _self.MaxValue]).range([_self.height, 0]); 58 | // 59 | // X,Y Axis 60 | // 61 | _self.xAxis = d3.svg.axis() 62 | .scale(d3.scale.linear().domain([0, _self.Ticks]).range([_self.width, 0])) 63 | .orient("bottom"); 64 | _self.yAxis = d3.svg.axis() 65 | .scale(_self.yscale) 66 | .orient("left"); 67 | // 68 | // Line/Area Chart 69 | // 70 | _self.line = d3.svg.line() 71 | .interpolate("basis") 72 | .x(function (d, i) { return _self.xscale(i-1); }) 73 | .y(function (d) { return _self.yscale(d.Value); }); 74 | // 75 | _self.area = d3.svg.area() 76 | .interpolate("basis") 77 | .x(function (d, i) { return _self.xscale(i-1); }) 78 | .y0(_self.height) 79 | .y1(function (d) { return _self.yscale(d.Value); }); 80 | // 81 | // Title 82 | // 83 | _self.Title = _self.svg.append("text") 84 | .attr("id", "title-" + _self.guid) 85 | .attr("class", "chart-title") 86 | .style("text-anchor", "middle") 87 | .text(_self.titleText) 88 | .attr("transform", function (d, i) { return "translate(" + _self.width / 2 + "," + -10 + ")"; }); 89 | // 90 | // X axis text 91 | // 92 | _self.svg.append("g") 93 | .attr("class", "x axis") 94 | .attr("transform", "translate(0," + _self.yscale(0) + ")") 95 | .call(_self.xAxis) 96 | .append("text") 97 | .attr("id", "xName-" + _self.guid) 98 | .attr("x", _self.width / 2) 99 | .attr("dy", "3em") 100 | .style("text-anchor", "middle") 101 | .text(_self.xText); 102 | // 103 | // Y axis text 104 | // 105 | _self.svg.append("g") 106 | .attr("class", "y axis") 107 | .call(_self.yAxis) 108 | .append("text") 109 | .attr("id", "yName-" + _self.guid) 110 | .attr("transform", "rotate(-90)") 111 | .attr("y", 0) 112 | .attr("x", -_self.height / 2) 113 | .attr("dy", "-3em") 114 | .style("text-anchor", "middle") 115 | .text(_self.yText); 116 | // 117 | // Vertical grid lines 118 | // 119 | _self.svg.selectAll(".vline").data(d3.range(_self.Ticks)).enter() 120 | .append("line") 121 | .attr("x1", function (d) { return d * (_self.width / _self.Ticks); }) 122 | .attr("x2", function (d) { return d * (_self.width / _self.Ticks); }) 123 | .attr("y1", function (d) { return 0; }) 124 | .attr("y2", function (d) { return _self.height; }) 125 | .style("stroke", "#282C34") 126 | .style("opacity", .5) 127 | .attr("clip-path", "url(#clip-" + _self.guid + ")") 128 | .attr("transform", "translate(" + (_self.width / _self.Ticks) + "," + 0 + ")"); 129 | // 130 | // Horizontal grid lines 131 | // 132 | _self.svg.selectAll(".hline").data(d3.range(_self.Ticks)).enter() 133 | .append("line") 134 | .attr("x1", function (d) { return 0; }) 135 | .attr("x2", function (d) { return _self.width; }) 136 | .attr("y1", function (d) { return d * (_self.height / (_self.MaxValue / 10)); }) 137 | .attr("y2", function (d) { return d * (_self.height / (_self.MaxValue / 10)); }) 138 | .style("stroke", "#282C34") 139 | .style("opacity", .5) 140 | .attr("clip-path", "url(#clip-" + _self.guid + ")") 141 | .attr("transform", "translate(" + 0 + "," + 0 + ")"); 142 | // 143 | // Bind DataSeries to chart 144 | // 145 | _self.Series = _self.svg.selectAll(".Series") 146 | .data(_self.DataSeries) 147 | .enter().append("g") 148 | .attr("clip-path", "url(#clip-" + _self.guid + ")") 149 | .attr("class", "Series"); 150 | // 151 | // Draw path from Series Data Points 152 | // 153 | _self.path = _self.Series.append("path") 154 | .attr("class", "area") 155 | .attr("d", function (d) { return _self.area(d.Data); }) 156 | .style("fill", function (d) { return _self.color(d.Name); }) 157 | .style("fill-opacity", .25) 158 | .style("stroke", function (d) { return _self.color(d.Name); }); 159 | // 160 | // Legend 161 | // 162 | _self.Legend = _self.svg.selectAll(".Legend") 163 | .data(_self.DataSeries) 164 | .enter().append("g") 165 | .attr("class", "Legend"); 166 | _self.Legend.append("circle") 167 | .attr("r", 4) 168 | .style("fill", function (d) { return _self.color(d.Name); }) 169 | .style("fill-opacity", .5) 170 | .style("stroke", function (d) { return _self.color(d.Name); }) 171 | .attr("transform", function (d, i) { return "translate(" + (i * 60) + "," + (_self.height + 36) + ")"; }); 172 | _self.Legend.append("text") 173 | .text(function (d) { return d.Name; }) 174 | .attr("dx", "0.5em") 175 | .attr("dy", "0.25em") 176 | .style("text-anchor", "start") 177 | .attr("transform", function (d, i) { return "translate(" + (i * 60 + 2) + "," + (_self.height + 37) + ")"; }); 178 | 179 | _self.tick = function (id) { 180 | _self.thisTick = new Date(); 181 | var elapsed = parseInt(_self.thisTick - _self.lastTick); 182 | var elapsedTotal = parseInt(_self.lastTick - _self.firstTick); 183 | if (elapsed < 900 && elapsedTotal > 0) { 184 | _self.lastTick = _self.thisTick; 185 | return; 186 | } 187 | if (id < _self.DataSeries.length - 1 && elapsedTotal > 0) { 188 | return; 189 | } 190 | _self.lastTick = _self.thisTick; 191 | //console.log(_self.guid, id, _self.DataSeries[id]); 192 | //var DataUpdate = [{ Value: (elapsed - 1000) }, { Value: Math.random() * 10 }, { Value: Math.random() * 10 }, { Value: Math.random() * 10}]; 193 | 194 | 195 | 196 | //Add new values 197 | for (i in _self.DataSeries) { 198 | _self.DataSeries[i].Data.push({ Value: _self.chartSeries[_self.DataSeries[i].Name] }); 199 | //Backfill missing values 200 | while (_self.DataSeries[i].Data.length -1<_self.Ticks+3 ) { 201 | _self.DataSeries[i].Data.unshift({ Value: 0 }) 202 | } 203 | } 204 | 205 | d3.select("#yName-" + _self.guid).text(_self.yText); 206 | d3.select("#xName-" + _self.guid).text(_self.xText); 207 | d3.select("#title-" + _self.guid).text(_self.titleText); 208 | 209 | _self.path 210 | .attr("d", function (d) { return _self.area(d.Data); }) 211 | .attr("transform", null) 212 | .transition() 213 | .duration(_self.TickDuration) 214 | .ease("linear") 215 | .attr("transform", "translate(" + _self.xscale(-1) + ",0)") 216 | .each("end", function (d, i) { _self.tick(i); }); 217 | 218 | //Remove oldest values 219 | for (i in _self.DataSeries) { 220 | _self.DataSeries[i].Data.shift(); 221 | } 222 | 223 | } 224 | _self.firstTick = new Date(); 225 | _self.lastTick = new Date(); 226 | _self.start = function () { 227 | _self.firstTick = new Date(); 228 | _self.lastTick = new Date(); 229 | _self.tick(0); 230 | 231 | } 232 | _self.start(); 233 | } 234 | 235 | _self.addSeries = function (SeriesName) { 236 | _self.chartSeries[SeriesName] = 0; 237 | _self.DataSeries.push({ Name: SeriesName, Data: [{ Value: 0}] }); 238 | _self.Init(); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Node Benchmark 4 | 5 | 6 | 7 | 8 | 9 | 75 | 76 |

NODBEN v1.0

77 |

Application Stats Up time: 00:00.51CPU: 0.5%MEM: 110MPID: 000

78 |
79 |
80 |
81 |
82 |

Computer Stats CPU IDLE: 0CPU USER: 0CPU SYS: 0NET IN: 0NET OUT: 0DISK READ: 0DISK WRITE: 0

83 |
84 |
85 |
86 |
87 |
88 | 89 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | npm install 2 | set CURRENT_DIR = $(pwd) 3 | echo "export PATH=\$PATH:$(pwd)" >> ~/.bash_profile 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-benchmark", 3 | "version": "1.0.0", 4 | "description": "NodeJS Benchmark and Profiling tool for any running process", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Henry Tr.", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "express": "^4.13.3", 13 | "socket.io": "^1.3.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/nodben/85f488be56026a2db8eeb850effd95414c699020/screenshot.png --------------------------------------------------------------------------------