stats.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*!
  2. * Cluster - stats
  3. * Copyright (c) 2011 LearnBoost <dev@learnboost.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var fs = require('fs')
  10. , Log = require('log')
  11. , repl = require('./repl')
  12. , utils = require('../utils')
  13. , os = require('os');
  14. /**
  15. * Enable stat tracking with the given `options`.
  16. *
  17. * Options:
  18. *
  19. * - `connections` enable connection statistics
  20. * - `requests` enable request statistics
  21. * - `lightRequests` enable light-weight request statistics
  22. *
  23. * Real-time applications should utilize `lightRequests` for reporting
  24. * when possible, although less data is available.
  25. *
  26. * TODO: UDP
  27. *
  28. * @param {Object} options
  29. * @return {Function}
  30. * @api public
  31. */
  32. module.exports = function(options){
  33. options = options || {};
  34. stats.enableInWorker = options.connections || options.requests;
  35. function stats(master){
  36. var server = master.server;
  37. master.connectionStats = options.connections;
  38. master.requestStats = options.requests;
  39. master.lightRequestStats = options.lightRequests;
  40. // worker stats
  41. if (master.isWorker) {
  42. var id = 0;
  43. // connections
  44. if (options.connections) {
  45. server.on('connection', function(sock){
  46. var data = { remoteAddress: sock.remoteAddress };
  47. master.call('reportStats', 'connection', data);
  48. sock.on('close', function(){
  49. master.call('reportStats', 'disconnection', data);
  50. });
  51. });
  52. }
  53. // light-weight requests
  54. if (options.lightRequests) {
  55. utils.unshiftListener(server, 'request', function(req, res){
  56. master.call('reportStats', 'light request', res.id = ++id);
  57. var end = res.end;
  58. res.end = function(str, encoding){
  59. res.end = end;
  60. res.end(str, encoding);
  61. master.call('reportStats', 'light request complete', res.id);
  62. };
  63. });
  64. }
  65. // requests
  66. if (options.requests) {
  67. utils.unshiftListener(server, 'request', function(req, res){
  68. var data = {
  69. remoteAddress: req.socket.remoteAddress
  70. , headers: req.headers
  71. , httpVersion: req.httpVersion
  72. , method: req.method
  73. , url: req.url
  74. , id: ++id
  75. };
  76. master.call('reportStats', 'request', data);
  77. var end = res.end;
  78. res.end = function(str, encoding){
  79. res.end = end;
  80. res.end(str, encoding);
  81. master.call('reportStats', 'request complete', data);
  82. };
  83. });
  84. }
  85. // master stats
  86. } else {
  87. master.stats = {
  88. start: new Date
  89. , restarts: 0
  90. , workersSpawned: 0
  91. , workersKilled: 0
  92. };
  93. // 0.4.x
  94. if (os) {
  95. master.stats.totalmem = os.totalmem();
  96. master.stats.freemem = os.freemem();
  97. }
  98. // worker stats
  99. master.reportStats = function(worker, type, data){
  100. master.emit('client ' + type, worker, data);
  101. switch (type) {
  102. case 'connection':
  103. worker.stats.connectionsTotal++;
  104. worker.stats.connectionsActive++;
  105. break;
  106. case 'disconnection':
  107. worker.stats.connectionsActive--;
  108. break;
  109. case 'light request':
  110. case 'request':
  111. worker.stats.requestsTotal++;
  112. }
  113. };
  114. // total workers spawned
  115. master.on('worker', function(worker){
  116. ++master.stats.workersSpawned;
  117. worker.stats = {
  118. start: new Date
  119. , connectionsTotal: 0
  120. , connectionsActive: 0
  121. , requestsTotal: 0
  122. };
  123. });
  124. // total worker deaths
  125. master.on('worker killed', function(worker){
  126. ++master.stats.workersKilled;
  127. });
  128. // restarting
  129. master.on('restarting', function(data){
  130. ++master.stats.restarts;
  131. data.stats = master.stats;
  132. });
  133. // restart
  134. master.on('restart', function(data){
  135. master.stats = data.stats;
  136. master.stats.start = new Date(master.stats.start);
  137. });
  138. }
  139. }
  140. return stats;
  141. };
  142. /**
  143. * REPL statistics command.
  144. */
  145. repl.define('stats', function(master, sock){
  146. var active = master.children.length
  147. , total = master.stats.workersSpawned
  148. , deaths = master.stats.workersKilled
  149. , restarts = master.stats.restarts;
  150. // master stats
  151. sock.title('Master');
  152. if (os) sock.row('os', os.type() + ' ' + os.release());
  153. sock.row('state', master.state);
  154. sock.row('started', master.stats.start.toUTCString());
  155. sock.row('uptime', utils.formatDateRange(new Date, master.stats.start));
  156. sock.row('restarts', restarts);
  157. sock.row('workers', active);
  158. sock.row('deaths', deaths);
  159. // resources
  160. if (os) {
  161. sock.title('Resources');
  162. sock.row('load average', os.loadavg().map(function(n){ return n.toFixed(2); }).join(' '));
  163. sock.row('cores utilized', active + ' / ' + os.cpus().length);
  164. var free = utils.formatBytes(master.stats.freemem);
  165. var total = utils.formatBytes(master.stats.totalmem);
  166. sock.row('memory at boot (free / total)', free + ' / ' + total);
  167. var free = utils.formatBytes(os.freemem());
  168. var total = utils.formatBytes(os.totalmem());
  169. sock.row('memory now (free / total)', free + ' / ' + total);
  170. }
  171. // worker stats
  172. sock.title('Workers');
  173. // connections
  174. if (master.connectionStats) {
  175. sock.row('connections total', sum(master.children, 'connectionsTotal'));
  176. sock.row('connections active', sum(master.children, 'connectionsActive'));
  177. }
  178. // requests
  179. if (master.requestStats) {
  180. sock.row('requests total', sum(master.children, 'requestsTotal'));
  181. }
  182. master.children.forEach(function(worker){
  183. var stats = ''
  184. , piped = [];
  185. // uptime
  186. stats += utils.formatDateRange(new Date, worker.stats.start);
  187. // connections
  188. if (master.connectionStats) {
  189. piped.push(worker.stats.connectionsActive);
  190. piped.push(worker.stats.connectionsTotal);
  191. }
  192. // requests
  193. if (master.requestStats) {
  194. piped.push(worker.stats.requestsTotal);
  195. }
  196. if (piped.length) {
  197. stats += ' ' + piped.join('\033[90m|\033[0m');
  198. }
  199. sock.row(worker.id, stats);
  200. });
  201. sock.write('\n');
  202. }, 'Display server statistics');
  203. /**
  204. * Return sum of each `prop` in `arr`.
  205. *
  206. * @param {Array} arr
  207. * @param {String} prop
  208. * @return {Number}
  209. * @api private
  210. */
  211. function sum(arr, prop){
  212. return arr.reduce(function(sum, obj){
  213. return sum + obj.stats[prop];
  214. }, 0);
  215. };