251 lines
6.4 KiB
JavaScript
251 lines
6.4 KiB
JavaScript
|
|
/*!
|
|
* Cluster - stats
|
|
* Copyright (c) 2011 LearnBoost <dev@learnboost.com>
|
|
* MIT Licensed
|
|
*/
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var fs = require('fs')
|
|
, Log = require('log')
|
|
, repl = require('./repl')
|
|
, utils = require('../utils')
|
|
, os = require('os');
|
|
|
|
/**
|
|
* Enable stat tracking with the given `options`.
|
|
*
|
|
* Options:
|
|
*
|
|
* - `connections` enable connection statistics
|
|
* - `requests` enable request statistics
|
|
* - `lightRequests` enable light-weight request statistics
|
|
*
|
|
* Real-time applications should utilize `lightRequests` for reporting
|
|
* when possible, although less data is available.
|
|
*
|
|
* TODO: UDP
|
|
*
|
|
* @param {Object} options
|
|
* @return {Function}
|
|
* @api public
|
|
*/
|
|
|
|
module.exports = function(options){
|
|
options = options || {};
|
|
stats.enableInWorker = options.connections || options.requests;
|
|
|
|
function stats(master){
|
|
var server = master.server;
|
|
master.connectionStats = options.connections;
|
|
master.requestStats = options.requests;
|
|
master.lightRequestStats = options.lightRequests;
|
|
|
|
// worker stats
|
|
if (master.isWorker) {
|
|
var id = 0;
|
|
|
|
// connections
|
|
if (options.connections) {
|
|
server.on('connection', function(sock){
|
|
var data = { remoteAddress: sock.remoteAddress };
|
|
master.call('reportStats', 'connection', data);
|
|
sock.on('close', function(){
|
|
master.call('reportStats', 'disconnection', data);
|
|
});
|
|
});
|
|
}
|
|
|
|
// light-weight requests
|
|
if (options.lightRequests) {
|
|
utils.unshiftListener(server, 'request', function(req, res){
|
|
master.call('reportStats', 'light request', res.id = ++id);
|
|
var end = res.end;
|
|
res.end = function(str, encoding){
|
|
res.end = end;
|
|
res.end(str, encoding);
|
|
master.call('reportStats', 'light request complete', res.id);
|
|
};
|
|
});
|
|
}
|
|
|
|
// requests
|
|
if (options.requests) {
|
|
utils.unshiftListener(server, 'request', function(req, res){
|
|
var data = {
|
|
remoteAddress: req.socket.remoteAddress
|
|
, headers: req.headers
|
|
, httpVersion: req.httpVersion
|
|
, method: req.method
|
|
, url: req.url
|
|
, id: ++id
|
|
};
|
|
|
|
master.call('reportStats', 'request', data);
|
|
|
|
var end = res.end;
|
|
res.end = function(str, encoding){
|
|
res.end = end;
|
|
res.end(str, encoding);
|
|
master.call('reportStats', 'request complete', data);
|
|
};
|
|
});
|
|
}
|
|
// master stats
|
|
} else {
|
|
master.stats = {
|
|
start: new Date
|
|
, restarts: 0
|
|
, workersSpawned: 0
|
|
, workersKilled: 0
|
|
};
|
|
|
|
// 0.4.x
|
|
if (os) {
|
|
master.stats.totalmem = os.totalmem();
|
|
master.stats.freemem = os.freemem();
|
|
}
|
|
|
|
// worker stats
|
|
master.reportStats = function(worker, type, data){
|
|
master.emit('client ' + type, worker, data);
|
|
switch (type) {
|
|
case 'connection':
|
|
worker.stats.connectionsTotal++;
|
|
worker.stats.connectionsActive++;
|
|
break;
|
|
case 'disconnection':
|
|
worker.stats.connectionsActive--;
|
|
break;
|
|
case 'light request':
|
|
case 'request':
|
|
worker.stats.requestsTotal++;
|
|
}
|
|
};
|
|
|
|
// total workers spawned
|
|
master.on('worker', function(worker){
|
|
++master.stats.workersSpawned;
|
|
worker.stats = {
|
|
start: new Date
|
|
, connectionsTotal: 0
|
|
, connectionsActive: 0
|
|
, requestsTotal: 0
|
|
};
|
|
});
|
|
|
|
// total worker deaths
|
|
master.on('worker killed', function(worker){
|
|
++master.stats.workersKilled;
|
|
});
|
|
|
|
// restarting
|
|
master.on('restarting', function(data){
|
|
++master.stats.restarts;
|
|
data.stats = master.stats;
|
|
});
|
|
|
|
// restart
|
|
master.on('restart', function(data){
|
|
master.stats = data.stats;
|
|
master.stats.start = new Date(master.stats.start);
|
|
});
|
|
}
|
|
}
|
|
|
|
return stats;
|
|
};
|
|
|
|
/**
|
|
* REPL statistics command.
|
|
*/
|
|
|
|
repl.define('stats', function(master, sock){
|
|
var active = master.children.length
|
|
, total = master.stats.workersSpawned
|
|
, deaths = master.stats.workersKilled
|
|
, restarts = master.stats.restarts;
|
|
|
|
// master stats
|
|
sock.title('Master');
|
|
if (os) sock.row('os', os.type() + ' ' + os.release());
|
|
sock.row('state', master.state);
|
|
sock.row('started', master.stats.start.toUTCString());
|
|
sock.row('uptime', utils.formatDateRange(new Date, master.stats.start));
|
|
sock.row('restarts', restarts);
|
|
sock.row('workers', active);
|
|
sock.row('deaths', deaths);
|
|
|
|
// resources
|
|
if (os) {
|
|
sock.title('Resources');
|
|
sock.row('load average', os.loadavg().map(function(n){ return n.toFixed(2); }).join(' '));
|
|
sock.row('cores utilized', active + ' / ' + os.cpus().length);
|
|
var free = utils.formatBytes(master.stats.freemem);
|
|
var total = utils.formatBytes(master.stats.totalmem);
|
|
sock.row('memory at boot (free / total)', free + ' / ' + total);
|
|
var free = utils.formatBytes(os.freemem());
|
|
var total = utils.formatBytes(os.totalmem());
|
|
sock.row('memory now (free / total)', free + ' / ' + total);
|
|
}
|
|
|
|
// worker stats
|
|
sock.title('Workers');
|
|
|
|
// connections
|
|
if (master.connectionStats) {
|
|
sock.row('connections total', sum(master.children, 'connectionsTotal'));
|
|
sock.row('connections active', sum(master.children, 'connectionsActive'));
|
|
}
|
|
|
|
// requests
|
|
if (master.requestStats) {
|
|
sock.row('requests total', sum(master.children, 'requestsTotal'));
|
|
}
|
|
|
|
master.children.forEach(function(worker){
|
|
var stats = ''
|
|
, piped = [];
|
|
|
|
// uptime
|
|
stats += utils.formatDateRange(new Date, worker.stats.start);
|
|
|
|
// connections
|
|
if (master.connectionStats) {
|
|
piped.push(worker.stats.connectionsActive);
|
|
piped.push(worker.stats.connectionsTotal);
|
|
}
|
|
|
|
// requests
|
|
if (master.requestStats) {
|
|
piped.push(worker.stats.requestsTotal);
|
|
}
|
|
|
|
if (piped.length) {
|
|
stats += ' ' + piped.join('\033[90m|\033[0m');
|
|
}
|
|
|
|
sock.row(worker.id, stats);
|
|
});
|
|
sock.write('\n');
|
|
}, 'Display server statistics');
|
|
|
|
|
|
/**
|
|
* Return sum of each `prop` in `arr`.
|
|
*
|
|
* @param {Array} arr
|
|
* @param {String} prop
|
|
* @return {Number}
|
|
* @api private
|
|
*/
|
|
|
|
function sum(arr, prop){
|
|
return arr.reduce(function(sum, obj){
|
|
return sum + obj.stats[prop];
|
|
}, 0);
|
|
};
|