mirror of
https://github.com/netdata/netdata.git
synced 2025-04-12 08:48:12 +00:00
654 lines
25 KiB
JavaScript
654 lines
25 KiB
JavaScript
'use strict';
|
|
|
|
// netdata
|
|
// real-time performance and health monitoring, done right!
|
|
// (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
var url = require('url');
|
|
var http = require('http');
|
|
var util = require('util');
|
|
|
|
/*
|
|
var netdata = require('netdata');
|
|
|
|
var example_chart = {
|
|
id: 'id', // the unique id of the chart
|
|
name: 'name', // the name of the chart
|
|
title: 'title', // the title of the chart
|
|
units: 'units', // the units of the chart dimensions
|
|
family: 'family', // the family of the chart
|
|
context: 'context', // the context of the chart
|
|
type: netdata.chartTypes.line, // the type of the chart
|
|
priority: 0, // the priority relative to others in the same family
|
|
update_every: 1, // the expected update frequency of the chart
|
|
dimensions: {
|
|
'dim1': {
|
|
id: 'dim1', // the unique id of the dimension
|
|
name: 'name', // the name of the dimension
|
|
algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm
|
|
multiplier: 1, // the multiplier
|
|
divisor: 1, // the divisor
|
|
hidden: false, // is hidden (boolean)
|
|
},
|
|
'dim2': {
|
|
id: 'dim2', // the unique id of the dimension
|
|
name: 'name', // the name of the dimension
|
|
algorithm: 'absolute', // the id of the netdata algorithm
|
|
multiplier: 1, // the multiplier
|
|
divisor: 1, // the divisor
|
|
hidden: false, // is hidden (boolean)
|
|
}
|
|
// add as many dimensions as needed
|
|
}
|
|
};
|
|
*/
|
|
|
|
var netdata = {
|
|
options: {
|
|
filename: __filename,
|
|
DEBUG: false,
|
|
update_every: 1
|
|
},
|
|
|
|
chartAlgorithms: {
|
|
incremental: 'incremental',
|
|
absolute: 'absolute',
|
|
percentage_of_absolute_row: 'percentage-of-absolute-row',
|
|
percentage_of_incremental_row: 'percentage-of-incremental-row'
|
|
},
|
|
|
|
chartTypes: {
|
|
line: 'line',
|
|
area: 'area',
|
|
stacked: 'stacked'
|
|
},
|
|
|
|
services: new Array(),
|
|
modules_configuring: 0,
|
|
charts: {},
|
|
|
|
processors: {
|
|
http: {
|
|
name: 'http',
|
|
|
|
process: function(service, callback) {
|
|
var __DEBUG = netdata.options.DEBUG;
|
|
|
|
if(__DEBUG === true)
|
|
netdata.debug(service.module.name + ': ' + service.name + ': making ' + this.name + ' request: ' + netdata.stringify(service.request));
|
|
|
|
var req = http.request(service.request, function(response) {
|
|
if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got server response...');
|
|
|
|
var end = false;
|
|
var data = '';
|
|
response.setEncoding('utf8');
|
|
|
|
if(response.statusCode !== 200) {
|
|
if(end === false) {
|
|
service.error('Got HTTP code ' + response.statusCode + ', failed to get data.');
|
|
end = true;
|
|
return callback(null);
|
|
}
|
|
}
|
|
|
|
response.on('data', function(chunk) {
|
|
if(end === false) data += chunk;
|
|
});
|
|
|
|
response.on('error', function() {
|
|
if(end === false) {
|
|
service.error(': Read error, failed to get data.');
|
|
end = true;
|
|
return callback(null);
|
|
}
|
|
});
|
|
|
|
response.on('end', function() {
|
|
if(end === false) {
|
|
if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.');
|
|
end = true;
|
|
return callback(data);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', function(e) {
|
|
if(__DEBUG === true) netdata.debug('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
|
|
service.error('Failed to make request, message: ' + e.message);
|
|
return callback(null);
|
|
});
|
|
|
|
// write data to request body
|
|
if(typeof service.postData !== 'undefined' && service.request.method === 'POST') {
|
|
if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': posting data: ' + service.postData);
|
|
req.write(service.postData);
|
|
}
|
|
|
|
req.end();
|
|
}
|
|
}
|
|
},
|
|
|
|
stringify: function(obj) {
|
|
return util.inspect(obj, {depth: 10});
|
|
},
|
|
|
|
zeropad2: function(s) {
|
|
return ("00" + s).slice(-2);
|
|
},
|
|
|
|
logdate: function(d) {
|
|
if(typeof d === 'undefined') d = new Date();
|
|
return d.getFullYear().toString() + '-' + this.zeropad2(d.getMonth() + 1) + '-' + this.zeropad2(d.getDate())
|
|
+ ' ' + this.zeropad2(d.getHours()) + ':' + this.zeropad2(d.getMinutes()) + ':' + this.zeropad2(d.getSeconds());
|
|
},
|
|
|
|
// show debug info, if debug is enabled
|
|
debug: function(msg) {
|
|
if(this.options.DEBUG === true) {
|
|
console.error(this.logdate() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
|
|
}
|
|
},
|
|
|
|
// log an error
|
|
error: function(msg) {
|
|
console.error(this.logdate() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
|
|
},
|
|
|
|
// send data to netdata
|
|
send: function(msg) {
|
|
console.log(msg.toString());
|
|
},
|
|
|
|
service: function(service) {
|
|
if(typeof service === 'undefined')
|
|
service = {};
|
|
|
|
var now = Date.now();
|
|
|
|
service._current_chart = null; // the current chart we work on
|
|
service._queue = ''; // data to be sent to netdata
|
|
|
|
service.error_reported = false; // error log flood control
|
|
|
|
service.added = false; // added to netdata.services
|
|
service.enabled = true;
|
|
service.updates = 0;
|
|
service.running = false;
|
|
service.started = 0;
|
|
service.ended = 0;
|
|
|
|
if(typeof service.module === 'undefined') {
|
|
service.module = { name: 'not-defined-module' };
|
|
service.error('Attempted to create service without a module.');
|
|
service.enabled = false;
|
|
}
|
|
|
|
if(typeof service.name === 'undefined') {
|
|
service.name = 'unnamed@' + service.module.name + '/' + now;
|
|
}
|
|
|
|
if(typeof service.processor === 'undefined')
|
|
service.processor = netdata.processors.http;
|
|
|
|
if(typeof service.update_every === 'undefined')
|
|
service.update_every = service.module.update_every;
|
|
|
|
if(typeof service.update_every === 'undefined')
|
|
service.update_every = netdata.options.update_every;
|
|
|
|
if(service.update_every < netdata.options.update_every)
|
|
service.update_every = netdata.options.update_every;
|
|
|
|
// align the runs
|
|
service.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000);
|
|
|
|
service.commit = function() {
|
|
if(this.added !== true) {
|
|
this.added = true;
|
|
|
|
var now = Date.now();
|
|
this.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000);
|
|
|
|
netdata.services.push(this);
|
|
if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': service committed.');
|
|
}
|
|
};
|
|
|
|
service.execute = function(responseProcessor) {
|
|
var __DEBUG = netdata.options.DEBUG;
|
|
|
|
if(service.enabled === false)
|
|
return responseProcessor(null);
|
|
|
|
this.module.active++;
|
|
this.running = true;
|
|
this.started = Date.now();
|
|
this.updates++;
|
|
|
|
if(__DEBUG === true)
|
|
netdata.debug(this.module.name + ': ' + this.name + ': making ' + this.processor.name + ' request: ' + netdata.stringify(this));
|
|
|
|
this.processor.process(this, function(response) {
|
|
service.ended = Date.now();
|
|
service.duration = service.ended - service.started;
|
|
|
|
if(typeof response === 'undefined')
|
|
response = null;
|
|
|
|
if(response !== null)
|
|
service.errorClear();
|
|
|
|
if(__DEBUG === true)
|
|
netdata.debug(service.module.name + ': ' + service.name + ': processing ' + service.processor.name + ' response (received in ' + (service.ended - service.started).toString() + ' ms)');
|
|
|
|
try {
|
|
responseProcessor(service, response);
|
|
}
|
|
catch(e) {
|
|
netdata.error(e);
|
|
service.error("responseProcessor failed process response data.");
|
|
}
|
|
|
|
service.running = false;
|
|
service.module.active--;
|
|
if(service.module.active < 0) {
|
|
service.module.active = 0;
|
|
if(__DEBUG === true)
|
|
netdata.debug(service.module.name + ': active module counter below zero.');
|
|
}
|
|
|
|
if(service.module.active === 0) {
|
|
// check if we run under configure
|
|
if(service.module.configure_callback !== null) {
|
|
if(__DEBUG === true)
|
|
netdata.debug(service.module.name + ': configuration finish callback called from processResponse().');
|
|
|
|
var configure_callback = service.module.configure_callback;
|
|
service.module.configure_callback = null;
|
|
configure_callback();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
service.update = function() {
|
|
if(netdata.options.DEBUG === true)
|
|
netdata.debug(this.module.name + ': ' + this.name + ': starting data collection...');
|
|
|
|
this.module.update(this, function() {
|
|
if(netdata.options.DEBUG === true)
|
|
netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.');
|
|
});
|
|
};
|
|
|
|
service.error = function(message) {
|
|
if(this.error_reported === false) {
|
|
netdata.error(this.module.name + ': ' + this.name + ': ' + message);
|
|
this.error_reported = true;
|
|
}
|
|
else if(netdata.options.DEBUG === true)
|
|
netdata.debug(this.module.name + ': ' + this.name + ': ' + message);
|
|
};
|
|
|
|
service.errorClear = function() {
|
|
this.error_reported = false;
|
|
};
|
|
|
|
service.queue = function(txt) {
|
|
this._queue += txt + '\n';
|
|
};
|
|
|
|
service._send_chart_to_netdata = function(chart) {
|
|
// internal function to send a chart to netdata
|
|
this.queue('CHART "' + chart.id + '" "' + chart.name + '" "' + chart.title + '" "' + chart.units + '" "' + chart.family + '" "' + chart.context + '" "' + chart.type + '" ' + chart.priority.toString() + ' ' + chart.update_every.toString());
|
|
|
|
if(typeof(chart.dimensions) !== 'undefined') {
|
|
var dims = Object.keys(chart.dimensions);
|
|
var len = dims.length;
|
|
while(len--) {
|
|
var d = chart.dimensions[dims[len]];
|
|
|
|
this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true) ? 'hidden' : '').toString());
|
|
d._created = true;
|
|
d._updated = false;
|
|
}
|
|
}
|
|
|
|
chart._created = true;
|
|
chart._updated = false;
|
|
};
|
|
|
|
// begin data collection for a chart
|
|
service.begin = function(chart) {
|
|
if(this._current_chart !== null && this._current_chart !== chart) {
|
|
this.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.');
|
|
this.end();
|
|
}
|
|
|
|
if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] !== chart) {
|
|
this.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.');
|
|
return false;
|
|
}
|
|
|
|
if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id);
|
|
this._current_chart = chart;
|
|
this._current_chart._began = true;
|
|
|
|
if(this._current_chart._dimensions_count !== 0) {
|
|
if(this._current_chart._created === false || this._current_chart._updated === true)
|
|
this._send_chart_to_netdata(this._current_chart);
|
|
|
|
var now = this.ended;
|
|
this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString());
|
|
}
|
|
// else this.error('Called begin() for chart ' + chart.id + ' which is empty.');
|
|
|
|
this._current_chart._last_updated = now;
|
|
this._current_chart._began = true;
|
|
this._current_chart._counter++;
|
|
|
|
return true;
|
|
};
|
|
|
|
// set a collected value for a chart
|
|
// we do most things on the first value we attempt to set
|
|
service.set = function(dimension, value) {
|
|
if(this._current_chart === null) {
|
|
this.error('Called set(' + dimension + ', ' + value + ') without an open chart.');
|
|
return false;
|
|
}
|
|
|
|
if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') {
|
|
this.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".');
|
|
return false;
|
|
}
|
|
|
|
if(typeof value === 'undefined' || value === null)
|
|
return false;
|
|
|
|
if(this._current_chart._dimensions_count !== 0)
|
|
this.queue('SET ' + dimension + ' = ' + value.toString());
|
|
|
|
return true;
|
|
};
|
|
|
|
// end data collection for the current chart - after calling begin()
|
|
service.end = function() {
|
|
if(this._current_chart !== null && this._current_chart._began === false) {
|
|
this.error('Called end() without an open chart.');
|
|
return false;
|
|
}
|
|
|
|
if(this._current_chart._dimensions_count !== 0) {
|
|
this.queue('END');
|
|
netdata.send(this._queue);
|
|
}
|
|
|
|
this._queue = '';
|
|
this._current_chart._began = false;
|
|
if(netdata.options.DEBUG === true) netdata.debug('sent chart ' + this._current_chart.id);
|
|
this._current_chart = null;
|
|
return true;
|
|
};
|
|
|
|
// discard the collected values for the current chart - after calling begin()
|
|
service.flush = function() {
|
|
if(this._current_chart === null || this._current_chart._began === false) {
|
|
this.error('Called flush() without an open chart.');
|
|
return false;
|
|
}
|
|
|
|
this._queue = '';
|
|
this._current_chart._began = false;
|
|
this._current_chart = null;
|
|
return true;
|
|
};
|
|
|
|
// create a netdata chart
|
|
service.chart = function(id, chart) {
|
|
var __DEBUG = netdata.options.DEBUG;
|
|
|
|
if(typeof(netdata.charts[id]) === 'undefined') {
|
|
netdata.charts[id] = {
|
|
_created: false,
|
|
_updated: true,
|
|
_began: false,
|
|
_counter: 0,
|
|
_last_updated: 0,
|
|
_dimensions_count: 0,
|
|
id: id,
|
|
name: id,
|
|
title: 'untitled chart',
|
|
units: 'a unit',
|
|
family: '',
|
|
context: '',
|
|
type: netdata.chartTypes.line,
|
|
priority: 50000,
|
|
update_every: netdata.options.update_every,
|
|
dimensions: {}
|
|
};
|
|
}
|
|
|
|
var c = netdata.charts[id];
|
|
|
|
if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its name');
|
|
c.name = chart.name;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its title');
|
|
c.title = chart.title;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its units');
|
|
c.units = chart.units;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its family');
|
|
c.family = chart.family;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.context) !== 'undefined' && chart.context !== c.context) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its context');
|
|
c.context = chart.context;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its type');
|
|
c.type = chart.type;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its priority');
|
|
c.priority = chart.priority;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every from ' + c.update_every + ' to ' + chart.update_every);
|
|
c.update_every = chart.update_every;
|
|
c._updated = true;
|
|
}
|
|
|
|
if(typeof(chart.dimensions) !== 'undefined') {
|
|
var dims = Object.keys(chart.dimensions);
|
|
var len = dims.length;
|
|
while(len--) {
|
|
var x = dims[len];
|
|
|
|
if(typeof(c.dimensions[x]) === 'undefined') {
|
|
c._dimensions_count++;
|
|
|
|
c.dimensions[x] = {
|
|
_created: false,
|
|
_updated: false,
|
|
id: x, // the unique id of the dimension
|
|
name: x, // the name of the dimension
|
|
algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm
|
|
multiplier: 1, // the multiplier
|
|
divisor: 1, // the divisor
|
|
hidden: false // is hidden (boolean)
|
|
};
|
|
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x);
|
|
c._updated = true;
|
|
}
|
|
|
|
var dim = chart.dimensions[x];
|
|
var d = c.dimensions[x];
|
|
|
|
if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name');
|
|
d.name = dim.name;
|
|
d._updated = true;
|
|
}
|
|
|
|
if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm);
|
|
d.algorithm = dim.algorithm;
|
|
d._updated = true;
|
|
}
|
|
|
|
if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier');
|
|
d.multiplier = dim.multiplier;
|
|
d._updated = true;
|
|
}
|
|
|
|
if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor');
|
|
d.divisor = dim.divisor;
|
|
d._updated = true;
|
|
}
|
|
|
|
if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) {
|
|
if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status');
|
|
d.hidden = dim.hidden;
|
|
d._updated = true;
|
|
}
|
|
|
|
if(d._updated) c._updated = true;
|
|
}
|
|
}
|
|
|
|
//if(netdata.options.DEBUG === true) netdata.debug(netdata.charts);
|
|
return netdata.charts[id];
|
|
};
|
|
|
|
return service;
|
|
},
|
|
|
|
runAllServices: function() {
|
|
if(netdata.options.DEBUG === true) netdata.debug('runAllServices()');
|
|
|
|
var now = Date.now();
|
|
var len = netdata.services.length;
|
|
while(len--) {
|
|
var service = netdata.services[len];
|
|
|
|
if(service.enabled === false || service.running === true) continue;
|
|
if(now <= service.next_run) continue;
|
|
|
|
service.update();
|
|
|
|
now = Date.now();
|
|
service.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000);
|
|
}
|
|
|
|
// 1/10th of update_every in pause
|
|
setTimeout(netdata.runAllServices, netdata.options.update_every * 100);
|
|
},
|
|
|
|
start: function() {
|
|
if(netdata.options.DEBUG === true) this.debug('started, services: ' + netdata.stringify(this.services));
|
|
|
|
if(this.services.length === 0) {
|
|
this.disableNodePlugin();
|
|
|
|
// eslint suggested way to exit
|
|
var exit = process.exit;
|
|
exit(1);
|
|
}
|
|
else this.runAllServices();
|
|
},
|
|
|
|
// disable the whole node.js plugin
|
|
disableNodePlugin: function() {
|
|
this.send('DISABLE');
|
|
|
|
// eslint suggested way to exit
|
|
var exit = process.exit;
|
|
exit(1);
|
|
},
|
|
|
|
requestFromParams: function(protocol, hostname, port, path, method) {
|
|
return {
|
|
protocol: protocol,
|
|
hostname: hostname,
|
|
port: port,
|
|
path: path,
|
|
//family: 4,
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Connection': 'keep-alive'
|
|
},
|
|
agent: new http.Agent({
|
|
keepAlive: true,
|
|
keepAliveMsecs: netdata.options.update_every * 1000,
|
|
maxSockets: 2, // it must be 2 to work
|
|
maxFreeSockets: 1
|
|
})
|
|
};
|
|
},
|
|
|
|
requestFromURL: function(a_url) {
|
|
var u = url.parse(a_url);
|
|
return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET');
|
|
},
|
|
|
|
configure: function(module, config, callback) {
|
|
if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...');
|
|
|
|
module.active = 0;
|
|
module.update_every = this.options.update_every;
|
|
|
|
if(typeof config.update_every !== 'undefined')
|
|
module.update_every = config.update_every;
|
|
|
|
module.enable_autodetect = (config.enable_autodetect)?true:false;
|
|
|
|
if(typeof(callback) === 'function')
|
|
module.configure_callback = callback;
|
|
else
|
|
module.configure_callback = null;
|
|
|
|
var added = module.configure(config);
|
|
|
|
if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.');
|
|
|
|
if(module.configure_callback !== null && added === 0) {
|
|
if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().');
|
|
var configure_callback = module.configure_callback;
|
|
module.configure_callback = null;
|
|
configure_callback();
|
|
}
|
|
|
|
return added;
|
|
}
|
|
};
|
|
|
|
if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from:', __filename);
|
|
module.exports = netdata;
|