]> arthur.barton.de Git - netdata.git/blob - node.d/node_modules/netdata.js
added backends debugging; #2017
[netdata.git] / node.d / node_modules / netdata.js
1 'use strict';
2
3 // netdata
4 // real-time performance and health monitoring, done right!
5 // (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
6 // GPL v3+
7
8 var url = require('url');
9 var http = require('http');
10 var util = require('util');
11
12 /*
13 var netdata = require('netdata');
14
15 var example_chart = {
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
25     dimensions: {
26         'dim1': {
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)
33         },
34         'dim2': {
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)
41         }
42         // add as many dimensions as needed
43     }
44 };
45 */
46
47 var netdata = {
48     options: {
49         filename: __filename,
50         DEBUG: false,
51         update_every: 1
52     },
53
54     chartAlgorithms: {
55         incremental: 'incremental',
56         absolute: 'absolute',
57         percentage_of_absolute_row: 'percentage-of-absolute-row',
58         percentage_of_incremental_row: 'percentage-of-incremental-row'
59     },
60
61     chartTypes: {
62         line: 'line',
63         area: 'area',
64         stacked: 'stacked'
65     },
66
67     services: new Array(),
68     modules_configuring: 0,
69     charts: {},
70
71
72     processors: {
73         http: {
74             name: 'http',
75
76             process: function(service, callback) {
77                 var __DEBUG = netdata.options.DEBUG;
78
79                 if(__DEBUG === true)
80                     netdata.debug(service.module.name + ': ' + service.name + ': making ' + this.name + ' request: ' + netdata.stringify(service.request));
81
82                 var req = http.request(service.request, function(response) {
83                     if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got server response...');
84
85                     var end = false;
86                     var data = '';
87                     response.setEncoding('utf8');
88
89                     if(response.statusCode !== 200) {
90                         if(end === false) {
91                             service.error('Got HTTP code ' + response.statusCode + ', failed to get data.');
92                             end = true;
93                             return callback(null);
94                         }
95                     }
96
97                     response.on('data', function(chunk) {
98                         if(end === false) data += chunk;
99                     });
100
101                     response.on('error', function() {
102                         if(end === false) {
103                             service.error(': Read error, failed to get data.');
104                             end = true;
105                             return callback(null);
106                         }
107                     });
108
109                     response.on('end', function() {
110                         if(end === false) {
111                             if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.');
112                             end = true;
113                             return callback(data);
114                         }
115                     });
116                 });
117
118                 req.on('error', function(e) {
119                     if(__DEBUG === true) netdata.debug('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
120                     service.error('Failed to make request, message: ' + e.message);
121                     return callback(null);
122                 });
123
124                 // write data to request body
125                 if(typeof service.postData !== 'undefined' && service.request.method === 'POST') {
126                     if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': posting data: ' + service.postData);
127                     req.write(service.postData);
128                 }
129
130                 req.end();
131             }
132         }
133     },
134
135     stringify: function(obj) {
136         return util.inspect(obj, {depth: 10});
137     },
138
139     zeropad2: function(s) {
140         return ("00" + s).slice(-2);
141     },
142
143     logdate: function(d) {
144         if(typeof d === 'undefined') d = new Date();
145         return d.getFullYear().toString() + '-' + this.zeropad2(d.getMonth() + 1) + '-' + this.zeropad2(d.getDate())
146             + ' ' + this.zeropad2(d.getHours()) + ':' + this.zeropad2(d.getMinutes()) + ':' + this.zeropad2(d.getSeconds());
147     },
148
149     // show debug info, if debug is enabled
150     debug: function(msg) {
151         if(this.options.DEBUG === true) {
152             console.error(this.logdate() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
153         }
154     },
155
156     // log an error
157     error: function(msg) {
158         console.error(this.logdate() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
159     },
160
161     // send data to netdata
162     send: function(msg) {
163         console.log(msg.toString());
164     },
165
166     service: function(service) {
167         if(typeof service === 'undefined')
168             service = {};
169
170         var now = Date.now();
171
172         service._current_chart = null;  // the current chart we work on
173         service._queue = '';            // data to be sent to netdata
174
175         service.error_reported = false; // error log flood control
176
177         service.added = false;          // added to netdata.services
178         service.enabled = true;
179         service.updates = 0;
180         service.running = false;
181         service.started = 0;
182         service.ended = 0;
183
184         if(typeof service.module === 'undefined') {
185             service.module = { name: 'not-defined-module' };
186             service.error('Attempted to create service without a module.');
187             service.enabled = false;
188         }
189
190         if(typeof service.name === 'undefined') {
191             service.name = 'unnamed@' + service.module.name + '/' + now;
192         }
193
194         if(typeof service.processor === 'undefined')
195             service.processor = netdata.processors.http;
196
197         if(typeof service.update_every === 'undefined')
198             service.update_every = service.module.update_every;
199
200         if(typeof service.update_every === 'undefined')
201             service.update_every = netdata.options.update_every;
202
203         if(service.update_every < netdata.options.update_every)
204             service.update_every = netdata.options.update_every;
205
206         // align the runs
207         service.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000);
208
209         service.commit = function() {
210             if(this.added !== true) {
211                 this.added = true;
212                 
213                 var now = Date.now();
214                 this.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000);
215
216                 netdata.services.push(this);
217                 if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': service committed.');
218             }
219         };
220
221         service.execute = function(responseProcessor) {
222             var __DEBUG = netdata.options.DEBUG;
223
224             if(service.enabled === false)
225                 return responseProcessor(null);
226
227             this.module.active++;
228             this.running = true;
229             this.started = Date.now();
230             this.updates++;
231
232             if(__DEBUG === true)
233                 netdata.debug(this.module.name + ': ' + this.name + ': making ' + this.processor.name + ' request: ' + netdata.stringify(this));
234
235             this.processor.process(this, function(response) {
236                 service.ended = Date.now();
237                 service.duration = service.ended - service.started;
238
239                 if(typeof response === 'undefined')
240                     response = null;
241
242                 if(response !== null)
243                     service.errorClear();
244
245                 if(__DEBUG === true)
246                     netdata.debug(service.module.name + ': ' + service.name + ': processing ' + service.processor.name + ' response (received in ' + (service.ended - service.started).toString() + ' ms)');
247
248                 responseProcessor(service, response);
249
250                 service.running = false;
251                 service.module.active--;
252                 if(service.module.active < 0) {
253                     service.module.active = 0;
254                     if(__DEBUG === true)
255                         netdata.debug(service.module.name + ': active module counter below zero.');
256                 }
257
258                 if(service.module.active === 0) {
259                     // check if we run under configure
260                     if(service.module.configure_callback !== null) {
261                         if(__DEBUG === true)
262                             netdata.debug(service.module.name + ': configuration finish callback called from processResponse().');
263
264                         var configure_callback = service.module.configure_callback;
265                         service.module.configure_callback = null;
266                         configure_callback();
267                     }
268                 }
269             });
270         };
271
272         service.update = function() {
273             if(netdata.options.DEBUG === true)
274                 netdata.debug(this.module.name + ': ' + this.name + ': starting data collection...');
275
276             this.module.update(this, function() {
277                 if(netdata.options.DEBUG === true)
278                     netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.');
279             });
280         };
281
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;
286             }
287             else if(netdata.options.DEBUG === true)
288                 netdata.debug(this.module.name + ': ' + this.name + ': ' + message);
289         };
290
291         service.errorClear = function() {
292             this.error_reported = false;
293         };
294
295         service.queue = function(txt) {
296             this._queue += txt + '\n';
297         };
298
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());
302
303             if(typeof(chart.dimensions) !== 'undefined') {
304                 var dims = Object.keys(chart.dimensions);
305                 var len = dims.length;
306                 while(len--) {
307                     var d = chart.dimensions[dims[len]];
308
309                     this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true) ? 'hidden' : '').toString());
310                     d._created = true;
311                     d._updated = false;
312                 }
313             }
314
315             chart._created = true;
316             chart._updated = false;
317         };
318
319         // begin data collection for a chart
320         service.begin = function(chart) {
321             if(this._current_chart !== null && this._current_chart !== chart) {
322                 this.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.');
323                 this.end();
324             }
325
326             if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] !== chart) {
327                 this.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.');
328                 return false;
329             }
330
331             if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id);
332             this._current_chart = chart;
333             this._current_chart._began = true;
334
335             if(this._current_chart._dimensions_count !== 0) {
336                 if(this._current_chart._created === false || this._current_chart._updated === true)
337                     this._send_chart_to_netdata(this._current_chart);
338
339                 var now = this.ended;
340                 this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString());
341             }
342             // else this.error('Called begin() for chart ' + chart.id + ' which is empty.');
343
344             this._current_chart._last_updated = now;
345             this._current_chart._began = true;
346             this._current_chart._counter++;
347
348             return true;
349         };
350
351         // set a collected value for a chart
352         // we do most things on the first value we attempt to set
353         service.set = function(dimension, value) {
354             if(this._current_chart === null) {
355                 this.error('Called set(' + dimension + ', ' + value + ') without an open chart.');
356                 return false;
357             }
358
359             if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') {
360                 this.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".');
361                 return false;
362             }
363
364             if(typeof value === 'undefined' || value === null)
365                 return false;
366
367             if(this._current_chart._dimensions_count !== 0) {
368                 if (value instanceof Buffer)
369                     this.queue('SET ' + dimension + ' = 0x' + value.toString('hex'));
370                 else
371                     this.queue('SET ' + dimension + ' = ' + value.toString());
372             }
373
374             return true;
375         };
376
377         // end data collection for the current chart - after calling begin()
378         service.end = function() {
379             if(this._current_chart !== null && this._current_chart._began === false) {
380                 this.error('Called end() without an open chart.');
381                 return false;
382             }
383
384             if(this._current_chart._dimensions_count !== 0) {
385                 this.queue('END');
386                 netdata.send(this._queue);
387             }
388
389             this._queue = '';
390             this._current_chart._began = false;
391             if(netdata.options.DEBUG === true) netdata.debug('sent chart ' + this._current_chart.id);
392             this._current_chart = null;
393             return true;
394         };
395
396         // discard the collected values for the current chart - after calling begin()
397         service.flush = function() {
398             if(this._current_chart === null || this._current_chart._began === false) {
399                 this.error('Called flush() without an open chart.');
400                 return false;
401             }
402
403             this._queue = '';
404             this._current_chart._began = false;
405             this._current_chart = null;
406             return true;
407         };
408
409         // create a netdata chart
410         service.chart = function(id, chart) {
411             var __DEBUG = netdata.options.DEBUG;
412
413             if(typeof(netdata.charts[id]) === 'undefined') {
414                 netdata.charts[id] = {
415                     _created: false,
416                     _updated: true,
417                     _began: false,
418                     _counter: 0,
419                     _last_updated: 0,
420                     _dimensions_count: 0,
421                     id: id,
422                     name: id,
423                     title: 'untitled chart',
424                     units: 'a unit',
425                     family: '',
426                     context: '',
427                     type: netdata.chartTypes.line,
428                     priority: 50000,
429                     update_every: netdata.options.update_every,
430                     dimensions: {}
431                 };
432             }
433
434             var c = netdata.charts[id];
435
436             if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) {
437                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its name');
438                 c.name = chart.name;
439                 c._updated = true;
440             }
441
442             if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) {
443                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its title');
444                 c.title = chart.title;
445                 c._updated = true;
446             }
447
448             if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) {
449                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its units');
450                 c.units = chart.units;
451                 c._updated = true;
452             }
453
454             if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) {
455                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its family');
456                 c.family = chart.family;
457                 c._updated = true;
458             }
459
460             if(typeof(chart.context) !== 'undefined' && chart.context !== c.context) {
461                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its context');
462                 c.context = chart.context;
463                 c._updated = true;
464             }
465
466             if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) {
467                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its type');
468                 c.type = chart.type;
469                 c._updated = true;
470             }
471
472             if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) {
473                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its priority');
474                 c.priority = chart.priority;
475                 c._updated = true;
476             }
477
478             if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) {
479                 if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every from ' + c.update_every + ' to ' + chart.update_every);
480                 c.update_every = chart.update_every;
481                 c._updated = true;
482             }
483
484             if(typeof(chart.dimensions) !== 'undefined') {
485                 var dims = Object.keys(chart.dimensions);
486                 var len = dims.length;
487                 while(len--) {
488                     var x = dims[len];
489
490                     if(typeof(c.dimensions[x]) === 'undefined') {
491                         c._dimensions_count++;
492
493                         c.dimensions[x] = {
494                             _created: false,
495                             _updated: false,
496                             id: x,                  // the unique id of the dimension
497                             name: x,                // the name of the dimension
498                             algorithm: netdata.chartAlgorithms.absolute,    // the id of the netdata algorithm
499                             multiplier: 1,          // the multiplier
500                             divisor: 1,             // the divisor
501                             hidden: false           // is hidden (boolean)
502                         };
503
504                         if(__DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x);
505                         c._updated = true;
506                     }
507
508                     var dim = chart.dimensions[x];
509                     var d = c.dimensions[x];
510
511                     if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) {
512                         if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name');
513                         d.name = dim.name;
514                         d._updated = true;
515                     }
516
517                     if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) {
518                         if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm);
519                         d.algorithm = dim.algorithm;
520                         d._updated = true;
521                     }
522
523                     if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) {
524                         if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier');
525                         d.multiplier = dim.multiplier;
526                         d._updated = true;
527                     }
528
529                     if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) {
530                         if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor');
531                         d.divisor = dim.divisor;
532                         d._updated = true;
533                     }
534
535                     if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) {
536                         if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status');
537                         d.hidden = dim.hidden;
538                         d._updated = true;
539                     }
540
541                     if(d._updated) c._updated = true;
542                 }
543             }
544
545             //if(netdata.options.DEBUG === true) netdata.debug(netdata.charts);
546             return netdata.charts[id];
547         };
548
549         return service;
550     },
551
552     runAllServices: function() {
553         if(netdata.options.DEBUG === true) netdata.debug('runAllServices()');
554
555         var now = Date.now();
556         var len = netdata.services.length;
557         while(len--) {
558             var service = netdata.services[len];
559
560             if(service.enabled === false || service.running === true) continue;
561             if(now <= service.next_run) continue;
562
563             service.update();
564
565             now = Date.now();
566             service.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000);
567         }
568
569         // 1/10th of update_every in pause
570         setTimeout(netdata.runAllServices, netdata.options.update_every * 100);
571     },
572
573     start: function() {
574         if(netdata.options.DEBUG === true) this.debug('started, services: ' + netdata.stringify(this.services));
575
576         if(this.services.length === 0) {
577             this.disableNodePlugin();
578
579             // eslint suggested way to exit
580             var exit = process.exit;
581             exit(1);
582         }
583         else this.runAllServices();
584     },
585
586     // disable the whole node.js plugin
587     disableNodePlugin: function() {
588         this.send('DISABLE');
589
590         // eslint suggested way to exit
591         var exit = process.exit;
592         exit(1);
593     },
594
595     requestFromParams: function(protocol, hostname, port, path, method) {
596         return {
597             protocol: protocol,
598             hostname: hostname,
599             port: port,
600             path: path,
601             //family: 4,
602             method: method,
603             headers: {
604                 'Content-Type': 'application/x-www-form-urlencoded',
605                 'Connection': 'keep-alive'
606             },
607             agent: new http.Agent({
608                 keepAlive: true,
609                 keepAliveMsecs: netdata.options.update_every * 1000,
610                 maxSockets: 2, // it must be 2 to work
611                 maxFreeSockets: 1
612             })
613         };
614     },
615
616     requestFromURL: function(a_url) {
617         var u = url.parse(a_url);
618         return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET');
619     },
620
621     configure: function(module, config, callback) {
622         if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...');
623
624         module.active = 0;
625         module.update_every = this.options.update_every;
626
627         if(typeof config.update_every !== 'undefined')
628             module.update_every = config.update_every;
629
630         module.enable_autodetect = (config.enable_autodetect)?true:false;
631
632         if(typeof(callback) === 'function')
633             module.configure_callback = callback;
634         else
635             module.configure_callback = null;
636
637         var added = module.configure(config);
638
639         if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.');
640
641         if(module.configure_callback !== null && added === 0) {
642             if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().');
643             var configure_callback = module.configure_callback;
644             module.configure_callback = null;
645             configure_callback();
646         }
647
648         return added;
649     }
650 };
651
652 if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from:', __filename);
653 module.exports = netdata;