From 5692a13d04a6c115d6c1a5d6796a64a4dbb23274 Mon Sep 17 00:00:00 2001 From: Jonathan Lee Date: Tue, 8 Feb 2011 10:00:35 -0500 Subject: [PATCH] Break reporting module into multiple files Add comment to release notes about jmxstat & spawnAndMonitor example/nodeload.ex.js uses persistent connections --- Makefile | 2 +- RELEASE-NOTES.md | 1 + examples/nodeload.ex.js | 6 +- lib/reporting/index.js | 214 +-------------------------------- lib/reporting/report.js | 176 +++++++++++++++++++++++++++ lib/reporting/reportmanager.js | 33 +++++ nodeload.js | 7 +- 7 files changed, 224 insertions(+), 215 deletions(-) create mode 100644 lib/reporting/report.js create mode 100644 lib/reporting/reportmanager.js diff --git a/Makefile b/Makefile index 1568a62..e0d119c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: clean templates compile PROCESS_TPL = scripts/process_tpl.js -SOURCES = lib/header.js lib/config.js lib/util.js lib/stats.js lib/loop/loop.js lib/loop/multiloop.js lib/monitoring/collectors.js lib/monitoring/statslogger.js lib/monitoring/monitor.js lib/monitoring/monitorgroup.js lib/http.js lib/reporting/*.tpl.js lib/reporting/template.js lib/reporting/index.js lib/reporting/process.js lib/loadtesting.js lib/remote/endpoint.js lib/remote/endpointclient.js lib/remote/slave.js lib/remote/slaves.js lib/remote/slavenode.js lib/remote/cluster.js lib/remote/httphandler.js lib/remote/remotetesting.js +SOURCES = lib/header.js lib/config.js lib/util.js lib/stats.js lib/loop/loop.js lib/loop/multiloop.js lib/monitoring/collectors.js lib/monitoring/statslogger.js lib/monitoring/monitor.js lib/monitoring/monitorgroup.js lib/http.js lib/reporting/*.tpl.js lib/reporting/template.js lib/reporting/report.js lib/reporting/reportmanager.js lib/reporting/process.js lib/loadtesting.js lib/remote/endpoint.js lib/remote/endpointclient.js lib/remote/slave.js lib/remote/slaves.js lib/remote/slavenode.js lib/remote/cluster.js lib/remote/httphandler.js lib/remote/remotetesting.js all: compile diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 41606dc..832acfc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -3,6 +3,7 @@ Features: * Add /console/console.html, a jQuery based UI for connecting to multiple nodeload instances simultaneously +* jmxstat/jmxstat.jar allows command line polling of JMX attributes. Combined with reporting.spawnAndMonitor(), Java processes can be monitored during load tests. Bug Fixes: diff --git a/examples/nodeload.ex.js b/examples/nodeload.ex.js index fbb54c3..442df62 100755 --- a/examples/nodeload.ex.js +++ b/examples/nodeload.ex.js @@ -22,7 +22,7 @@ var i = 0, userProfile: [[0,0], [20, 10]], stats: ['result-codes', {name: 'latency', percentiles: [0.95, 0.999]}, 'concurrency', 'uniques', 'request-bytes', 'response-bytes'], requestGenerator: function(client) { - return client.request('GET', "/" + Math.floor(Math.random()*8000), { 'host': 'localhost' }); + return client.request('GET', "/" + Math.floor(Math.random()*8000), { 'host': 'localhost', 'connection': 'keep-alive' }); } }, writetest = { @@ -34,7 +34,7 @@ var i = 0, targetRps: 20, stats: ['result-codes', 'latency', 'uniques'], requestGenerator: function(client) { - var request = client.request('PUT', "/" + Math.floor(Math.random()*8000), { 'host': 'localhost' }); + var request = client.request('PUT', "/" + Math.floor(Math.random()*8000), { 'host': 'localhost', 'connection': 'keep-alive' }); request.end('foo'); return request; } @@ -47,7 +47,7 @@ var i = 0, numRequests: 8001, stats: ['result-codes'], requestGenerator: function(client) { - return client.request('DELETE', "/" + i++, { 'host': 'localhost' }); + return client.request('DELETE', "/" + i++, { 'host': 'localhost', 'connection': 'keep-alive' }); } }, loadtest = nl.run(readtest, writetest); diff --git a/lib/reporting/index.js b/lib/reporting/index.js index b37a1b2..a5d5373 100644 --- a/lib/reporting/index.js +++ b/lib/reporting/index.js @@ -1,208 +1,6 @@ -// ------------------------------------ -// Progress Reporting -// ------------------------------------ -// -// This file defines Report, Chart, and REPORT_MANAGER -// -// A Report contains a summary and a number of charts. Reports added to the global REPORT_MANAGER are -// served by the global HTTP_SERVER instance (defaults to http://localhost:8000/) and written to disk -// at regular intervals. -// -var BUILD_AS_SINGLE_FILE; -if (!BUILD_AS_SINGLE_FILE) { -var util = require('../util'); -var querystring = require('querystring'); -var LogFile = require('../stats').LogFile; -var template = require('./template'); -var config = require('../config'); - -var REPORT_SUMMARY_TEMPLATE = require('./summary.tpl.js').REPORT_SUMMARY_TEMPLATE; -var NODELOAD_CONFIG = config.NODELOAD_CONFIG; -var START = NODELOAD_CONFIG.START; -var DYGRAPH_SOURCE = require('./dygraph.tpl.js').DYGRAPH_SOURCE; -var HTTP_SERVER = require('../http').HTTP_SERVER; -} - -var Chart, timeFromStart; - -/** A Report contains a summary object and set of charts. It can be easily updated using the stats from -a monitor.js#Monitor or monitor.js#MonitorGroup using updateFromMonitor()/updateFromMonitorGroup(). - -@param name A name for the report. Generally corresponds to the test name. -@param updater A function(report) that should update the summary and chart data. */ -var Report = exports.Report = function(name) { - this.name = name; - this.uid = util.uid(); - this.summary = {}; - this.charts = {}; -}; -Report.prototype = { - getChart: function(name) { - if (!this.charts[name]) { - this.charts[name] = new Chart(name); - } - return this.charts[name]; - }, - /** Update this report automatically each time the Monitor emits an 'update' event */ - updateFromMonitor: function(monitor) { - monitor.on('update', this.doUpdateFromMonitor_.bind(this, monitor, '')); - return this; - }, - /** Update this report automatically each time the MonitorGroup emits an 'update' event */ - updateFromMonitorGroup: function(monitorGroup) { - var self = this; - monitorGroup.on('update', function() { - util.forEach(monitorGroup.monitors, function(monitorname, monitor) { - self.doUpdateFromMonitor_(monitor, monitorname); - }); - }); - return self; - }, - doUpdateFromMonitor_: function(monitor, monitorname) { - var self = this; - monitorname = monitorname ? monitorname + ' ' : ''; - util.forEach(monitor.stats, function(statname, stat) { - util.forEach(stat.summary(), function(name, val) { - self.summary[self.name + ' ' + monitorname + statname + ' ' + name] = val; - }); - if (monitor.interval[statname]) { - self.getChart(monitorname + statname) - .put(monitor.interval[statname].summary()); - } - }); - } -}; - -/** A Chart represents a collection of lines over time represented as: - - columns: ["x values", "line 1", "line 2", "line 3", ...] - rows: [[timestamp1, line1[0], line2[0], line3[0], ...], - [timestamp2, line1[1], line2[1], line3[1], ...], - [timestamp3, line1[2], line2[2], line3[2], ...], - ... - ] - -@param name A name for the chart */ -var Chart = exports.Chart = function(name) { - this.name = name; - this.uid = util.uid(); - this.columns = ["time"]; - this.rows = [[timeFromStart()]]; -}; -Chart.prototype = { - /** Put a row of data into the chart. The current time will be used as the x-value. The lines in the - chart are extracted from the "data". New lines can be added to the chart at any time by including it - in data. - - @param data An object representing one row of data: { - "line name 1": value1 - "line name 2": value2 - ... - } - */ - put: function(data) { - var self = this, row = [timeFromStart()]; - util.forEach(data, function(column, val) { - var col = self.columns.indexOf(column); - if (col < 0) { - col = self.columns.length; - self.columns.push(column); - self.rows[0].push(0); - } - row[col] = val; - }); - self.rows.push(row); - }, - /** Update chart using data from event emitter each time it emits an event. 'eventEmitter' should - emit the given 'event' (defaults to 'data') with a single object. 'fields' are read from the object - and added to the chart. For example, a chart can track the output form a child process output using - - chart.updateFromEventEmitter(spawnAndMonitor('cmd', ['args'], /val: (.*)/, ['val']), ['val']) - - */ - updateFromEventEmitter: function(eventEmitter, fields, event) { - eventEmitter.on(event || 'data', function(data) { - var row = {}; - fields.forEach(function(i) { - if (data[i] !== undefined) { row[i] = data[i]; } - }); - this.put(row); - }); - } -}; - -var ReportGroup = exports.ReportGroup = function() { - this.reports = []; - this.logNameOrObject = 'results-' + START.getTime() + '.html'; -}; -ReportGroup.prototype = { - addReport: function(report) { - report = (typeof report === 'string') ? new Report(report) : report; - this.reports.push(report); - return report; - }, - setLogFile: function(logNameOrObject) { - this.logNameOrObject = logNameOrObject; - }, - setLoggingEnabled: function(enabled) { - clearTimeout(this.loggingTimeoutId); - if (enabled) { - this.logger = this.logger || (typeof this.logNameOrObject === 'string') ? new LogFile(this.logNameOrObject) : this.logNameOrObject; - this.loggingTimeoutId = setTimeout(this.writeToLog_.bind(this), this.refreshIntervalMs); - } else if (this.logger) { - this.logger.close(); - this.logger = null; - } - return this; - }, - reset: function() { - this.reports = {}; - }, - getHtml: function() { - var self = this, - t = template.create(REPORT_SUMMARY_TEMPLATE); - return t({ - DYGRAPH_SOURCE: DYGRAPH_SOURCE, - querystring: querystring, - refreshPeriodMs: self.refreshIntervalMs, - reports: self.reports - }); - }, - writeToLog_: function() { - this.loggingTimeoutId = setTimeout(this.writeToLog_.bind(this), this.refreshIntervalMs); - this.logger.clear(this.getHtml()); - } -}; - -// ================= -// Singletons -// ================= - -/** A global report manager used by nodeload to keep the summary webpage up to date during a load test */ -var REPORT_MANAGER = exports.REPORT_MANAGER = new ReportGroup(); -NODELOAD_CONFIG.on('apply', function() { - REPORT_MANAGER.refreshIntervalMs = REPORT_MANAGER.refreshIntervalMs || NODELOAD_CONFIG.AJAX_REFRESH_INTERVAL_MS; - REPORT_MANAGER.setLoggingEnabled(NODELOAD_CONFIG.LOGS_ENABLED); -}); - -HTTP_SERVER.addRoute('^/$', function(url, req, res) { - var html = REPORT_MANAGER.getHtml(); - res.writeHead(200, {"Content-Type": "text/html", "Content-Length": html.length}); - res.write(html); - res.end(); -}); -HTTP_SERVER.addRoute('^/reports$', function(url, req, res) { - var json = JSON.stringify(REPORT_MANAGER.reports); - res.writeHead(200, {"Content-Type": "application/json", "Content-Length": json.length}); - res.write(json); - res.end(); -}); - -// ================= -// Private methods -// ================= - -/** current time from start of nodeload process in 100ths of a minute */ -function timeFromStart() { - return (Math.floor((new Date().getTime() - START) / 600) / 100); -} +var report = require('./report'); +exports.Report = report.Report; +exports.Chart = report.Chart; +exports.ReportGroup = report.ReportGroup; +exports.REPORT_MANAGER= require('./reportmanager').REPORT_MANAGER; +exports.spawnAndMonitor = require('./process').spawnAndMonitor; \ No newline at end of file diff --git a/lib/reporting/report.js b/lib/reporting/report.js new file mode 100644 index 0000000..3585a2c --- /dev/null +++ b/lib/reporting/report.js @@ -0,0 +1,176 @@ +// This file defines Report, Chart, and ReportGroup +// +// A Report contains a summary and a number of charts. +// +var BUILD_AS_SINGLE_FILE; +if (!BUILD_AS_SINGLE_FILE) { +var util = require('../util'); +var querystring = require('querystring'); +var LogFile = require('../stats').LogFile; +var template = require('./template'); +var config = require('../config'); + +var REPORT_SUMMARY_TEMPLATE = require('./summary.tpl.js').REPORT_SUMMARY_TEMPLATE; +var NODELOAD_CONFIG = config.NODELOAD_CONFIG; +var START = NODELOAD_CONFIG.START; +var DYGRAPH_SOURCE = require('./dygraph.tpl.js').DYGRAPH_SOURCE; +} +var Chart, timeFromStart; + +/** A Report contains a summary object and set of charts. It can be easily updated using the stats from +a monitor.js#Monitor or monitor.js#MonitorGroup using updateFromMonitor()/updateFromMonitorGroup(). + +@param name A name for the report. Generally corresponds to the test name. +@param updater A function(report) that should update the summary and chart data. */ +var Report = exports.Report = function(name) { + this.name = name; + this.uid = util.uid(); + this.summary = {}; + this.charts = {}; +}; +Report.prototype = { + getChart: function(name) { + if (!this.charts[name]) { + this.charts[name] = new Chart(name); + } + return this.charts[name]; + }, + /** Update this report automatically each time the Monitor emits an 'update' event */ + updateFromMonitor: function(monitor) { + monitor.on('update', this.doUpdateFromMonitor_.bind(this, monitor, '')); + return this; + }, + /** Update this report automatically each time the MonitorGroup emits an 'update' event */ + updateFromMonitorGroup: function(monitorGroup) { + var self = this; + monitorGroup.on('update', function() { + util.forEach(monitorGroup.monitors, function(monitorname, monitor) { + self.doUpdateFromMonitor_(monitor, monitorname); + }); + }); + return self; + }, + doUpdateFromMonitor_: function(monitor, monitorname) { + var self = this; + monitorname = monitorname ? monitorname + ' ' : ''; + util.forEach(monitor.stats, function(statname, stat) { + util.forEach(stat.summary(), function(name, val) { + self.summary[self.name + ' ' + monitorname + statname + ' ' + name] = val; + }); + if (monitor.interval[statname]) { + self.getChart(monitorname + statname) + .put(monitor.interval[statname].summary()); + } + }); + } +}; + +/** A Chart represents a collection of lines over time represented as: + + columns: ["x values", "line 1", "line 2", "line 3", ...] + rows: [[timestamp1, line1[0], line2[0], line3[0], ...], + [timestamp2, line1[1], line2[1], line3[1], ...], + [timestamp3, line1[2], line2[2], line3[2], ...], + ... + ] + +@param name A name for the chart */ +var Chart = exports.Chart = function(name) { + this.name = name; + this.uid = util.uid(); + this.columns = ["time"]; + this.rows = [[timeFromStart()]]; +}; +Chart.prototype = { + /** Put a row of data into the chart. The current time will be used as the x-value. The lines in the + chart are extracted from the "data". New lines can be added to the chart at any time by including it + in data. + + @param data An object representing one row of data: { + "line name 1": value1 + "line name 2": value2 + ... + } + */ + put: function(data) { + var self = this, row = [timeFromStart()]; + util.forEach(data, function(column, val) { + var col = self.columns.indexOf(column); + if (col < 0) { + col = self.columns.length; + self.columns.push(column); + self.rows[0].push(0); + } + row[col] = val; + }); + self.rows.push(row); + }, + /** Update chart using data from event emitter each time it emits an event. 'eventEmitter' should + emit the given 'event' (defaults to 'data') with a single object. 'fields' are read from the object + and added to the chart. For example, a chart can track the output form a child process output using + + chart.updateFromEventEmitter(spawnAndMonitor('cmd', ['args'], /val: (.*)/, ['val']), ['val']) + + */ + updateFromEventEmitter: function(eventEmitter, fields, event) { + eventEmitter.on(event || 'data', function(data) { + var row = {}; + fields.forEach(function(i) { + if (data[i] !== undefined) { row[i] = data[i]; } + }); + this.put(row); + }); + } +}; + +var ReportGroup = exports.ReportGroup = function() { + this.reports = []; + this.logNameOrObject = 'results-' + START.getTime() + '.html'; +}; +ReportGroup.prototype = { + addReport: function(report) { + report = (typeof report === 'string') ? new Report(report) : report; + this.reports.push(report); + return report; + }, + setLogFile: function(logNameOrObject) { + this.logNameOrObject = logNameOrObject; + }, + setLoggingEnabled: function(enabled) { + clearTimeout(this.loggingTimeoutId); + if (enabled) { + this.logger = this.logger || (typeof this.logNameOrObject === 'string') ? new LogFile(this.logNameOrObject) : this.logNameOrObject; + this.loggingTimeoutId = setTimeout(this.writeToLog_.bind(this), this.refreshIntervalMs); + } else if (this.logger) { + this.logger.close(); + this.logger = null; + } + return this; + }, + reset: function() { + this.reports = {}; + }, + getHtml: function() { + var self = this, + t = template.create(REPORT_SUMMARY_TEMPLATE); + return t({ + DYGRAPH_SOURCE: DYGRAPH_SOURCE, + querystring: querystring, + refreshPeriodMs: self.refreshIntervalMs, + reports: self.reports + }); + }, + writeToLog_: function() { + this.loggingTimeoutId = setTimeout(this.writeToLog_.bind(this), this.refreshIntervalMs); + this.logger.clear(this.getHtml()); + } +}; + +// ================= +// Private methods +// ================= + +/** current time from start of nodeload process in 100ths of a minute */ +function timeFromStart() { + return (Math.floor((new Date().getTime() - START) / 600) / 100); +} diff --git a/lib/reporting/reportmanager.js b/lib/reporting/reportmanager.js new file mode 100644 index 0000000..4838add --- /dev/null +++ b/lib/reporting/reportmanager.js @@ -0,0 +1,33 @@ +// This file defines REPORT_MANAGER +// +// Reports added to the global REPORT_MANAGER are served by the global HTTP_SERVER instance (defaults to +// http://localhost:8000/) and written to disk at regular intervals. + +var BUILD_AS_SINGLE_FILE; +if (!BUILD_AS_SINGLE_FILE) { +var ReportGroup = require('./report').ReportGroup; +var config = require('../config'); + +var NODELOAD_CONFIG = config.NODELOAD_CONFIG; +var HTTP_SERVER = require('../http').HTTP_SERVER; +} + +/** A global report manager used by nodeload to keep the summary webpage up to date during a load test */ +var REPORT_MANAGER = exports.REPORT_MANAGER = new ReportGroup(); +NODELOAD_CONFIG.on('apply', function() { + REPORT_MANAGER.refreshIntervalMs = REPORT_MANAGER.refreshIntervalMs || NODELOAD_CONFIG.AJAX_REFRESH_INTERVAL_MS; + REPORT_MANAGER.setLoggingEnabled(NODELOAD_CONFIG.LOGS_ENABLED); +}); + +HTTP_SERVER.addRoute('^/$', function(url, req, res) { + var html = REPORT_MANAGER.getHtml(); + res.writeHead(200, {"Content-Type": "text/html", "Content-Length": html.length}); + res.write(html); + res.end(); +}); +HTTP_SERVER.addRoute('^/reports$', function(url, req, res) { + var json = JSON.stringify(REPORT_MANAGER.reports); + res.writeHead(200, {"Content-Type": "application/json", "Content-Length": json.length}); + res.write(json); + res.end(); +}); \ No newline at end of file diff --git a/nodeload.js b/nodeload.js index 34484fc..f8e64fe 100755 --- a/nodeload.js +++ b/nodeload.js @@ -94,12 +94,13 @@ var template={cache_:{},create:function(str,data,callback){var fn;if(!/[\t\r\n% this.create(buffer.toString('utf8'),data,callback);});return;}}else{if(this.cache_[str]){fn=this.cache_[str];}else{fn=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};"+"obj=obj||{};"+"with(obj){p.push('"+ str.split("'").join("\\'").split("\n").join("\\n").replace(/<%([\s\S]*?)%>/mg,function(m,t){return'<%'+t.split("\\'").join("'").split("\\n").join("\n")+'%>';}).replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");this.cache_[str]=fn;}} if(callback){callback(data?fn(data):fn);} -else{return data?fn(data):fn;}}};exports.create=template.create.bind(template);var BUILD_AS_SINGLE_FILE;if(!BUILD_AS_SINGLE_FILE){var util=require('../util');var querystring=require('querystring');var LogFile=require('../stats').LogFile;var template=require('./template');var config=require('../config');var REPORT_SUMMARY_TEMPLATE=require('./summary.tpl.js').REPORT_SUMMARY_TEMPLATE;var NODELOAD_CONFIG=config.NODELOAD_CONFIG;var START=NODELOAD_CONFIG.START;var DYGRAPH_SOURCE=require('./dygraph.tpl.js').DYGRAPH_SOURCE;var HTTP_SERVER=require('../http').HTTP_SERVER;} +else{return data?fn(data):fn;}}};exports.create=template.create.bind(template);var BUILD_AS_SINGLE_FILE;if(!BUILD_AS_SINGLE_FILE){var util=require('../util');var querystring=require('querystring');var LogFile=require('../stats').LogFile;var template=require('./template');var config=require('../config');var REPORT_SUMMARY_TEMPLATE=require('./summary.tpl.js').REPORT_SUMMARY_TEMPLATE;var NODELOAD_CONFIG=config.NODELOAD_CONFIG;var START=NODELOAD_CONFIG.START;var DYGRAPH_SOURCE=require('./dygraph.tpl.js').DYGRAPH_SOURCE;} var Chart,timeFromStart;var Report=exports.Report=function(name){this.name=name;this.uid=util.uid();this.summary={};this.charts={};};Report.prototype={getChart:function(name){if(!this.charts[name]){this.charts[name]=new Chart(name);} return this.charts[name];},updateFromMonitor:function(monitor){monitor.on('update',this.doUpdateFromMonitor_.bind(this,monitor,''));return this;},updateFromMonitorGroup:function(monitorGroup){var self=this;monitorGroup.on('update',function(){util.forEach(monitorGroup.monitors,function(monitorname,monitor){self.doUpdateFromMonitor_(monitor,monitorname);});});return self;},doUpdateFromMonitor_:function(monitor,monitorname){var self=this;monitorname=monitorname?monitorname+' ':'';util.forEach(monitor.stats,function(statname,stat){util.forEach(stat.summary(),function(name,val){self.summary[self.name+' '+monitorname+statname+' '+name]=val;});if(monitor.interval[statname]){self.getChart(monitorname+statname).put(monitor.interval[statname].summary());}});}};var Chart=exports.Chart=function(name){this.name=name;this.uid=util.uid();this.columns=["time"];this.rows=[[timeFromStart()]];};Chart.prototype={put:function(data){var self=this,row=[timeFromStart()];util.forEach(data,function(column,val){var col=self.columns.indexOf(column);if(col<0){col=self.columns.length;self.columns.push(column);self.rows[0].push(0);} row[col]=val;});self.rows.push(row);},updateFromEventEmitter:function(eventEmitter,fields,event){eventEmitter.on(event||'data',function(data){var row={};fields.forEach(function(i){if(data[i]!==undefined){row[i]=data[i];}});this.put(row);});}};var ReportGroup=exports.ReportGroup=function(){this.reports=[];this.logNameOrObject='results-'+START.getTime()+'.html';};ReportGroup.prototype={addReport:function(report){report=(typeof report==='string')?new Report(report):report;this.reports.push(report);return report;},setLogFile:function(logNameOrObject){this.logNameOrObject=logNameOrObject;},setLoggingEnabled:function(enabled){clearTimeout(this.loggingTimeoutId);if(enabled){this.logger=this.logger||(typeof this.logNameOrObject==='string')?new LogFile(this.logNameOrObject):this.logNameOrObject;this.loggingTimeoutId=setTimeout(this.writeToLog_.bind(this),this.refreshIntervalMs);}else if(this.logger){this.logger.close();this.logger=null;} -return this;},reset:function(){this.reports={};},getHtml:function(){var self=this,t=template.create(REPORT_SUMMARY_TEMPLATE);return t({DYGRAPH_SOURCE:DYGRAPH_SOURCE,querystring:querystring,refreshPeriodMs:self.refreshIntervalMs,reports:self.reports});},writeToLog_:function(){this.loggingTimeoutId=setTimeout(this.writeToLog_.bind(this),this.refreshIntervalMs);this.logger.clear(this.getHtml());}};var REPORT_MANAGER=exports.REPORT_MANAGER=new ReportGroup();NODELOAD_CONFIG.on('apply',function(){REPORT_MANAGER.refreshIntervalMs=REPORT_MANAGER.refreshIntervalMs||NODELOAD_CONFIG.AJAX_REFRESH_INTERVAL_MS;REPORT_MANAGER.setLoggingEnabled(NODELOAD_CONFIG.LOGS_ENABLED);});HTTP_SERVER.addRoute('^/$',function(url,req,res){var html=REPORT_MANAGER.getHtml();res.writeHead(200,{"Content-Type":"text/html","Content-Length":html.length});res.write(html);res.end();});HTTP_SERVER.addRoute('^/reports$',function(url,req,res){var json=JSON.stringify(REPORT_MANAGER.reports);res.writeHead(200,{"Content-Type":"application/json","Content-Length":json.length});res.write(json);res.end();});function timeFromStart(){return(Math.floor((new Date().getTime()-START)/600)/100);} -var BUILD_AS_SINGLE_FILE;if(!BUILD_AS_SINGLE_FILE){var child_process=require('child_process');} +return this;},reset:function(){this.reports={};},getHtml:function(){var self=this,t=template.create(REPORT_SUMMARY_TEMPLATE);return t({DYGRAPH_SOURCE:DYGRAPH_SOURCE,querystring:querystring,refreshPeriodMs:self.refreshIntervalMs,reports:self.reports});},writeToLog_:function(){this.loggingTimeoutId=setTimeout(this.writeToLog_.bind(this),this.refreshIntervalMs);this.logger.clear(this.getHtml());}};function timeFromStart(){return(Math.floor((new Date().getTime()-START)/600)/100);} +var BUILD_AS_SINGLE_FILE;if(!BUILD_AS_SINGLE_FILE){var ReportGroup=require('./report').ReportGroup;var config=require('../config');var NODELOAD_CONFIG=config.NODELOAD_CONFIG;var HTTP_SERVER=require('../http').HTTP_SERVER;} +var REPORT_MANAGER=exports.REPORT_MANAGER=new ReportGroup();NODELOAD_CONFIG.on('apply',function(){REPORT_MANAGER.refreshIntervalMs=REPORT_MANAGER.refreshIntervalMs||NODELOAD_CONFIG.AJAX_REFRESH_INTERVAL_MS;REPORT_MANAGER.setLoggingEnabled(NODELOAD_CONFIG.LOGS_ENABLED);});HTTP_SERVER.addRoute('^/$',function(url,req,res){var html=REPORT_MANAGER.getHtml();res.writeHead(200,{"Content-Type":"text/html","Content-Length":html.length});res.write(html);res.end();});HTTP_SERVER.addRoute('^/reports$',function(url,req,res){var json=JSON.stringify(REPORT_MANAGER.reports);res.writeHead(200,{"Content-Type":"application/json","Content-Length":json.length});res.write(json);res.end();});var BUILD_AS_SINGLE_FILE;if(!BUILD_AS_SINGLE_FILE){var child_process=require('child_process');} var spawnAndMonitor=exports.spawnAndMonitor=function(regex,fields,spawnArguments){var buf='',proc=child_process.spawn.apply(child_process,Array.prototype.slice.call(arguments,2));proc.stdout.on('data',function(data){buf+=data.toString();var lines=buf.split('\n');buf=lines.pop();lines.forEach(function(line){var vals=line.match(regex);if(vals){if(fields){var obj={};for(var i=1;i