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