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