/*! * Cluster - stats * Copyright (c) 2011 LearnBoost * 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); };