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