4 // real-time performance and health monitoring, done right!
5 // (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
8 var url = require('url');
9 var http = require('http');
10 var util = require('util');
13 var netdata = require('netdata');
16 id: 'id', // the unique id of the chart
17 name: 'name', // the name of the chart
18 title: 'title', // the title of the chart
19 units: 'units', // the units of the chart dimensions
20 family: 'family', // the family of the chart
21 context: 'context', // the context of the chart
22 type: netdata.chartTypes.line, // the type of the chart
23 priority: 0, // the priority relative to others in the same family
24 update_every: 1, // the expected update frequency of the chart
27 id: 'dim1', // the unique id of the dimension
28 name: 'name', // the name of the dimension
29 algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm
30 multiplier: 1, // the multiplier
31 divisor: 1, // the divisor
32 hidden: false, // is hidden (boolean)
35 id: 'dim2', // the unique id of the dimension
36 name: 'name', // the name of the dimension
37 algorithm: 'absolute', // the id of the netdata algorithm
38 multiplier: 1, // the multiplier
39 divisor: 1, // the divisor
40 hidden: false, // is hidden (boolean)
42 // add as many dimensions as needed
55 incremental: 'incremental',
57 percentage_of_absolute_row: 'percentage-of-absolute-row',
58 percentage_of_incremental_row: 'percentage-of-incremental-row'
67 services: new Array(),
68 modules_configuring: 0,
76 process: function(service, callback) {
77 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': making ' + this.name + ' request: ' + netdata.stringify(service.request));
79 var req = http.request(service.request, function(response) {
80 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got server response...');
84 response.setEncoding('utf8');
86 if(response.statusCode !== 200) {
88 service.error('Got HTTP code ' + response.statusCode + ', failed to get data.');
94 response.on('data', function(chunk) {
95 if(end === false) data += chunk;
98 response.on('error', function() {
100 service.error(': Read error, failed to get data.');
106 response.on('end', function() {
108 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.');
115 req.on('error', function(e) {
116 if(netdata.options.DEBUG === true) netdata.debug('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
117 service.error('Failed to make request, message: ' + e.message);
121 // write data to request body
122 if(typeof service.postData !== 'undefined' && service.request.method === 'POST') {
123 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': posting data: ' + service.postData);
124 req.write(service.postData);
132 stringify: function(obj) {
133 return util.inspect(obj, {depth: 10});
136 zeropad2: function(s) {
137 if(typeof s !== 'string')
142 case 1: return '0' + s;
147 logdate: function(d) {
148 if(typeof d === 'undefined') d = new Date();
149 return this.zeropad2(d.getFullYear()) + '-' + this.zeropad2(d.getMonth()) + '-' + this.zeropad2(d.getDay())
150 + ' ' + this.zeropad2(d.getHours()) + ':' + this.zeropad2(d.getMinutes()) + ':' + this.zeropad2(d.getSeconds());
153 // show debug info, if debug is enabled
154 debug: function(msg) {
155 if(this.options.DEBUG === true) {
156 console.error(this.logdate() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
161 error: function(msg) {
162 console.error(this.logdate() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
165 // send data to netdata
166 send: function(msg) {
167 console.log(msg.toString());
170 service: function(service) {
171 if(typeof service === 'undefined')
174 var now = new Date().getTime();
176 service._current_chart = null; // the current chart we work on
177 service._queue = ''; // data to be sent to netdata
179 service.error_reported = false; // error log flood control
181 service.added = false; // added to netdata.services
182 service.enabled = true;
184 service.running = false;
188 if(typeof service.module === 'undefined') {
189 service.module = { name: 'not-defined-module' };
190 service.error('Attempted to create service without a module.');
191 service.enabled = false;
194 if(typeof service.name === 'undefined') {
195 service.name = 'unnamed@' + service.module.name + '/' + now;
198 if(typeof service.processor === 'undefined')
199 service.processor = netdata.processors.http;
201 if(typeof service.update_every === 'undefined')
202 service.update_every = service.module.update_every;
204 if(typeof service.update_every === 'undefined')
205 service.update_every = netdata.options.update_every;
207 if(service.update_every < netdata.options.update_every)
208 service.update_every = netdata.options.update_every;
211 service.next_run = now - (now % (service.update_every * 1000));
213 service.commit = function() {
214 if(this.added !== true) {
217 var now = new Date().getTime();
218 while( this.next_run < now )
219 this.next_run += (this.update_every * 1000);
221 netdata.services.push(this);
222 if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': service committed.');
226 service.execute = function(callback) {
227 if(service.enabled === false) {
232 this.module.active++;
234 this.started = new Date().getTime();
237 if(netdata.options.DEBUG === true)
238 netdata.debug(this.module.name + ': ' + this.name + ': making ' + this.processor.name + ' request: ' + netdata.stringify(this));
240 this.processor.process(this, function(response) {
241 service.ended = new Date().getTime();
242 service.duration = service.ended - service.started;
244 if(typeof response === 'undefined')
247 if(response !== null)
248 service.errorClear();
250 if(netdata.options.DEBUG === true)
251 netdata.debug(service.module.name + ': ' + service.name + ': processing ' + service.processor.name + ' response (received in ' + (service.ended - service.started).toString() + ' ms)');
253 callback(service, response);
255 service.running = false;
256 service.module.active--;
257 if(service.module.active < 0) {
258 service.module.active = 0;
259 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': active module counter below zero.');
262 if(service.module.active === 0) {
263 // check if we run under configure
264 if(service.module.configure_callback !== null) {
265 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': configuration finish callback called from processResponse().');
266 var ccallback = service.module.configure_callback;
267 service.module.configure_callback = null;
274 service.update = function() {
275 if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': starting data collection...');
277 this.module.update(this, function() {
278 if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.');
282 service.error = function(message) {
283 if(this.error_reported === false) {
284 netdata.error(this.module.name + ': ' + this.name + ': ' + message);
285 this.error_reported = true;
287 else if(netdata.options.DEBUG === true)
288 netdata.debug(this.module.name + ': ' + this.name + ': ' + message);
291 service.errorClear = function() {
292 this.error_reported = false;
295 service.queue = function(txt) {
296 this._queue += txt + '\n';
299 service._send_chart_to_netdata = function(chart) {
300 // internal function to send a chart to netdata
301 this.queue('CHART "' + chart.id + '" "' + chart.name + '" "' + chart.title + '" "' + chart.units + '" "' + chart.family + '" "' + chart.context + '" "' + chart.type + '" ' + chart.priority.toString() + ' ' + chart.update_every.toString());
303 for(var dim in chart.dimensions) {
304 var d = chart.dimensions[dim];
306 this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true)?'hidden':'').toString());
311 chart._created = true;
312 chart._updated = false;
315 // begin data collection for a chart
316 service.begin = function(chart) {
317 if(this._current_chart !== null && this._current_chart !== chart) {
318 this.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.');
322 if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] != chart) {
323 this.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.');
327 if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id);
328 this._current_chart = chart;
329 this._current_chart._began = true;
331 if(this._current_chart._dimensions_count !== 0) {
332 if(this._current_chart._created === false || this._current_chart._updated === true)
333 this._send_chart_to_netdata(this._current_chart);
335 var now = this.ended;
336 this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString());
338 // else this.error('Called begin() for chart ' + chart.id + ' which is empty.');
340 this._current_chart._last_updated = now;
341 this._current_chart._began = true;
342 this._current_chart._counter++;
347 // set a collected value for a chart
348 // we do most things on the first value we attempt to set
349 service.set = function(dimension, value) {
350 if(this._current_chart === null) {
351 this.error('Called set(' + dimension + ', ' + value + ') without an open chart.');
355 if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') {
356 this.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".');
360 if(typeof value === 'undefined' || value === null)
363 if(this._current_chart._dimensions_count !== 0) {
364 if (value instanceof Buffer)
365 this.queue('SET ' + dimension + ' = 0x' + value.toString('hex'));
367 this.queue('SET ' + dimension + ' = ' + value.toString());
373 // end data collection for the current chart - after calling begin()
374 service.end = function() {
375 if(this._current_chart !== null && this._current_chart._began === false) {
376 this.error('Called end() without an open chart.');
380 if(this._current_chart._dimensions_count !== 0) {
382 netdata.send(this._queue);
386 this._current_chart._began = false;
387 if(netdata.options.DEBUG === true) netdata.debug('sent chart ' + this._current_chart.id);
388 this._current_chart = null;
392 // discard the collected values for the current chart - after calling begin()
393 service.flush = function() {
394 if(this._current_chart === null || this._current_chart._began === false) {
395 this.error('Called flush() without an open chart.');
400 this._current_chart._began = false;
401 this._current_chart = null;
405 // create a netdata chart
406 service.chart = function(id, chart) {
407 if(typeof(netdata.charts[id]) === 'undefined') {
408 netdata.charts[id] = {
414 _dimensions_count: 0,
417 title: 'untitled chart',
421 type: netdata.chartTypes.line,
423 update_every: netdata.options.update_every,
428 var c = netdata.charts[id];
430 if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) {
431 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its name');
436 if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) {
437 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its title');
438 c.title = chart.title;
442 if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) {
443 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its units');
444 c.units = chart.units;
448 if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) {
449 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its family');
450 c.family = chart.family;
454 if(typeof(chart.context) !== 'undefined' && chart.context !== c.context) {
455 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its context');
456 c.context = chart.context;
460 if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) {
461 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its type');
466 if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) {
467 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its priority');
468 c.priority = chart.priority;
472 if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) {
473 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every from ' + c.update_every + ' to ' + chart.update_every);
474 c.update_every = chart.update_every;
478 if(typeof(chart.dimensions) !== 'undefined') {
479 for(var x in chart.dimensions) {
480 if(typeof(c.dimensions[x]) === 'undefined') {
481 c._dimensions_count++;
486 id: x, // the unique id of the dimension
487 name: x, // the name of the dimension
488 algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm
489 multiplier: 1, // the multiplier
490 divisor: 1, // the divisor
491 hidden: false, // is hidden (boolean)
494 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x);
498 var dim = chart.dimensions[x];
499 var d = c.dimensions[x];
501 if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) {
502 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name');
507 if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) {
508 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm);
509 d.algorithm = dim.algorithm;
513 if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) {
514 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier');
515 d.multiplier = dim.multiplier;
519 if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) {
520 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor');
521 d.divisor = dim.divisor;
525 if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) {
526 if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status');
527 d.hidden = dim.hidden;
531 if(d._updated) c._updated = true;
535 //if(netdata.options.DEBUG === true) netdata.debug(netdata.charts);
536 return netdata.charts[id];
542 runAllServices: function() {
543 if(netdata.options.DEBUG === true) netdata.debug('runAllServices()');
545 var now = new Date().getTime();
546 var len = netdata.services.length;
548 var service = netdata.services[len];
550 if(service.enabled === false || service.running === true) continue;
551 if(now <= service.next_run) continue;
555 now = new Date().getTime();
556 while(service.next_run < now)
557 service.next_run += (service.update_every * 1000);
560 // 1/10th of update_every in pause
561 setTimeout(netdata.runAllServices, netdata.options.update_every * 100);
565 if(netdata.options.DEBUG === true) this.debug('started, services: ' + netdata.stringify(this.services));
567 if(this.services.length === 0) {
568 this.disableNodePlugin();
571 else this.runAllServices();
574 // disable the whole node.js plugin
575 disableNodePlugin: function() {
576 this.send('DISABLE');
580 requestFromParams: function(protocol, hostname, port, path, method) {
589 'Content-Type': 'application/x-www-form-urlencoded',
590 'Connection': 'keep-alive'
592 agent: new http.Agent({
594 keepAliveMsecs: netdata.options.update_every * 1000,
595 maxSockets: 2, // it must be 2 to work
601 requestFromURL: function(a_url) {
602 var u = url.parse(a_url);
603 return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET');
606 configure: function(module, config, callback) {
607 if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...');
610 module.update_every = this.options.update_every;
612 if(typeof config.update_every !== 'undefined')
613 module.update_every = config.update_every;
615 module.enable_autodetect = (config.enable_autodetect)?true:false;
617 if(typeof(callback) === 'function')
618 module.configure_callback = callback;
620 module.configure_callback = null;
622 var added = module.configure(config);
624 if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.');
626 if(module.configure_callback !== null && added === 0) {
627 if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().');
628 module.configure_callback = null;
636 if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from: ' + __filename);
637 module.exports = netdata;