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