SUBDIRS = \
charts.d \
+ node.d \
conf.d \
plugins.d \
src \
AC_SUBST([cachedir], ["\$(localstatedir)/cache/netdata"])
AC_SUBST([chartsdir], ["\$(libexecdir)/netdata/charts.d"])
+AC_SUBST([nodedir], ["\$(libexecdir)/netdata/node.d"])
AC_SUBST([configdir], ["\$(sysconfdir)/netdata"])
AC_SUBST([logdir], ["\$(localstatedir)/log/netdata"])
AC_SUBST([pluginsdir], ["\$(libexecdir)/netdata/plugins.d"])
AC_CONFIG_FILES([
Makefile
charts.d/Makefile
+ node.d/Makefile
conf.d/Makefile
netdata.spec
plugins.d/Makefile
--- /dev/null
+MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
+
+dist_node_SCRIPTS = \
+ README.md \
+ named.node.js \
+ $(NULL)
+
+nodemodulesdir=$(nodedir)/node_modules
+dist_nodemodules_DATA = \
+ node_modules/netdata.js \
+ node_modules/extend.js \
+ $(NULL)
--- /dev/null
+'use strict';\r
+\r
+// collect statistics from bind (named) v9.10+\r
+//\r
+// bind statistics documentation at:\r
+// https://ftp.isc.org/isc/bind/9.10.3/doc/arm/Bv9ARM.ch06.html#statistics\r
+\r
+// example configuration in /etc/netdata/named.conf\r
+// the module supports auto-detection if bind is running in localhost\r
+\r
+/*\r
+{\r
+ "enable_autodetect": true,\r
+ "update_every": 5,\r
+ "servers": [\r
+ {\r
+ "name": "bind1",\r
+ "url": "http://127.0.0.1:8888/json/v1",\r
+ "update_every": 1\r
+ },\r
+ {\r
+ "name": "bind2",\r
+ "url": "http://10.1.2.3:8888/json/v1",\r
+ "update_every": 2\r
+ }\r
+ ]\r
+}\r
+*/\r
+\r
+// the following is the bind named.conf configuration required\r
+\r
+/*\r
+statistics-channels {\r
+ inet 127.0.0.1 port 8888 allow { 127.0.0.1; };\r
+};\r
+*/\r
+\r
+var url = require('url');\r
+var http = require('http');\r
+var netdata = require('netdata');\r
+\r
+if(netdata.options.DEBUG === true) netdata.debug('loaded ' + __filename + ' plugin');\r
+\r
+var named = {\r
+ name: __filename,\r
+ enable_autodetect: true,\r
+ update_every: 1000,\r
+\r
+ charts: {},\r
+\r
+ chartFromMembersCreate: function(service, obj, id, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor) {\r
+ var chart = {\r
+ id: id, // the unique id of the chart\r
+ name: '', // the unique name of the chart\r
+ title: service.name + ' ' + title_suffix, // the title of the chart\r
+ units: units, // the units of the chart dimensions\r
+ family: family_prefix + '_' + service.name, // the family of the chart\r
+ category: category_prefix + '_' + service.name, // the category of the chart\r
+ type: type, // the type of the chart\r
+ priority: priority, // the priority relative to others in the same family and category\r
+ update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart\r
+ dimensions: {}\r
+ }\r
+\r
+ var found = 0;\r
+ for(var x in obj) {\r
+ if(typeof(obj[x]) !== 'undefined' && obj[x] !== 0) {\r
+ found++;\r
+ chart.dimensions[x] = {\r
+ id: x, // the unique id of the dimension\r
+ name: x, // the name of the dimension\r
+ algorithm: algorithm, // the id of the netdata algorithm\r
+ multiplier: multiplier, // the multiplier\r
+ divisor: divisor, // the divisor\r
+ hidden: false // is hidden (boolean)\r
+ }\r
+ }\r
+ }\r
+\r
+ if(found === false)\r
+ return null;\r
+\r
+ chart = service.chart(id, chart);\r
+ this.charts[id] = chart;\r
+ return chart;\r
+ },\r
+\r
+ chartFromMembers: function(service, obj, id_suffix, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor) {\r
+ var id = 'named_' + service.name + '.' + id_suffix;\r
+ var chart = this.charts[id];\r
+\r
+ if(typeof chart === 'undefined') {\r
+ chart = this.chartFromMembersCreate(service, obj, id, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor);\r
+ if(chart === null) return false;\r
+ }\r
+ else {\r
+ // check if we need to re-generate the chart\r
+ for(var x in obj) {\r
+ if(typeof(chart.dimensions[x]) === 'undefined') {\r
+ chart = this.chartFromMembersCreate(service, obj, id, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor);\r
+ if(chart === null) return false;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ var found = 0;\r
+ service.begin(chart);\r
+ for(var x in obj) {\r
+ if(typeof(chart.dimensions[x]) !== 'undefined') {\r
+ found++;\r
+ service.set(x, obj[x]);\r
+ }\r
+ }\r
+ service.end();\r
+\r
+ if(found > 0) return true;\r
+ return false;\r
+ },\r
+\r
+ // an index to map values to different charts\r
+ lookups: {\r
+ nsstats: {},\r
+ resolver_stats: {},\r
+ numfetch: {}\r
+ },\r
+\r
+ processResponse: function(service, data) {\r
+ if(data !== null) {\r
+ var r = JSON.parse(data);\r
+\r
+ if(service.added !== true)\r
+ netdata.serviceAdd(service);\r
+\r
+ if(typeof r.nsstats !== 'undefined') {\r
+ // we split the nsstats object to several others\r
+ var global_requests = {}, global_requests_enable = false;\r
+ var global_failures = {}, global_failures_enable = false;\r
+ var global_failures_detail = {}, global_failures_detail_enable = false;\r
+ var global_updates = {}, global_updates_enable = false;\r
+ var protocol_queries = {}, protocol_queries_enable = false;\r
+ var global_queries = {}, global_queries_enable = false;\r
+ var global_queries_success = {}, global_queries_success_enable = false;\r
+ var default_enable = false;\r
+ var RecursClients = 0;\r
+\r
+ // RecursClients is an absolute value\r
+ if(typeof r.nsstats['RecursClients'] !== 'undefined') {\r
+ RecursClients = r.nsstats['RecursClients'];\r
+ delete r.nsstats['RecursClients'];\r
+ }\r
+\r
+ for( var x in r.nsstats ) {\r
+ // we maintain an index of the values found\r
+ // mapping them to objects splitted\r
+\r
+ var look = named.lookups.nsstats[x];\r
+ if(typeof look === 'undefined') {\r
+ // a new value, not found in the index\r
+ // index it:\r
+ if(x === 'Requestv4') {\r
+ named.lookups.nsstats[x] = {\r
+ name: 'IPv4',\r
+ type: 'global_requests'\r
+ };\r
+ }\r
+ else if(x === 'Requestv6') {\r
+ named.lookups.nsstats[x] = {\r
+ name: 'IPv6',\r
+ type: 'global_requests'\r
+ };\r
+ }\r
+ else if(x === 'QryFailure') {\r
+ named.lookups.nsstats[x] = {\r
+ name: 'failures',\r
+ type: 'global_failures'\r
+ };\r
+ }\r
+ else if(x === 'QryUDP') {\r
+ named.lookups.nsstats[x] = {\r
+ name: 'UDP',\r
+ type: 'protocol_queries'\r
+ };\r
+ }\r
+ else if(x === 'QryTCP') {\r
+ named.lookups.nsstats[x] = {\r
+ name: 'TCP',\r
+ type: 'protocol_queries'\r
+ };\r
+ }\r
+ else if(x === 'QrySuccess') {\r
+ named.lookups.nsstats[x] = {\r
+ name: 'queries',\r
+ type: 'global_queries_success'\r
+ };\r
+ }\r
+ else if(x.match(/QryRej$/) !== null) {\r
+ named.lookups.nsstats[x] = {\r
+ name: x,\r
+ type: 'global_failures_detail'\r
+ };\r
+ }\r
+ else if(x.match(/^Qry/) !== null) {\r
+ named.lookups.nsstats[x] = {\r
+ name: x,\r
+ type: 'global_queries'\r
+ };\r
+ }\r
+ else if(x.match(/^Update/) !== null) {\r
+ named.lookups.nsstats[x] = {\r
+ name: x,\r
+ type: 'global_updates'\r
+ };\r
+ }\r
+ else {\r
+ // values not mapped, will remain\r
+ // in the default map\r
+ named.lookups.nsstats[x] = {\r
+ name: x,\r
+ type: 'default'\r
+ };\r
+ }\r
+\r
+ look = named.lookups.nsstats[x];\r
+ // netdata.error('lookup nsstats value: ' + x + ' >>> ' + named.lookups.nsstats[x].type);\r
+ }\r
+\r
+ switch(look.type) {\r
+ case 'global_requests': global_requests[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_requests_enable = true; break;\r
+ case 'global_queries': global_queries[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_queries_enable = true; break;\r
+ case 'global_queries_success': global_queries_success[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_queries_success_enable = true; break;\r
+ case 'global_updates': global_updates[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_updates_enable = true; break;\r
+ case 'protocol_queries': protocol_queries[look.name] = r.nsstats[x]; delete r.nsstats[x]; protocol_queries_enable = true; break;\r
+ case 'global_failures': global_failures[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_failures_enable = true; break;\r
+ case 'global_failures_detail': global_failures_detail[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_failures_detail_enable = true; break;\r
+ default: default_enable = true; break;\r
+ }\r
+ }\r
+\r
+ if(global_requests_enable == true)\r
+ service.module.chartFromMembers(service, global_requests, 'received_requests', 'Bind, Global Received Requests by IP version', 'requests/s', 'named', 'named', netdata.chartTypes.stacked, 100, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(global_queries_success_enable == true)\r
+ service.module.chartFromMembers(service, global_queries_success, 'global_queries_success', 'Bind, Global Successful Queries', 'queries/s', 'named', 'named', netdata.chartTypes.line, 150, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(protocol_queries_enable == true)\r
+ service.module.chartFromMembers(service, protocol_queries, 'protocols_queries', 'Bind, Global Queries by IP Protocol', 'queries/s', 'named', 'named', netdata.chartTypes.stacked, 200, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(global_queries_enable == true)\r
+ service.module.chartFromMembers(service, global_queries, 'global_queries', 'Bind, Global Queries Analysis', 'queries/s', 'named', 'named', netdata.chartTypes.stacked, 300, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(global_updates_enable == true)\r
+ service.module.chartFromMembers(service, global_updates, 'received_updates', 'Bind, Global Received Updates', 'updates/s', 'named', 'named', netdata.chartTypes.stacked, 900, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(global_failures_enable == true)\r
+ service.module.chartFromMembers(service, global_failures, 'query_failures', 'Bind, Global Query Failures', 'failures/s', 'named', 'named', netdata.chartTypes.line, 950, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(global_failures_detail_enable == true)\r
+ service.module.chartFromMembers(service, global_failures_detail, 'query_failures_detail', 'Bind, Global Query Failures Analysis', 'failures/s', 'named', 'named', netdata.chartTypes.stacked, 960, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(default_enable === true)\r
+ service.module.chartFromMembers(service, r.nsstats, 'nsstats', 'Bind, Other Global Server Statistics', 'operations/s', 'named', 'named', netdata.chartTypes.line, 999, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ // RecursClients chart\r
+ {\r
+ var id = 'named_' + service.name + '.recursive_clients';\r
+ var chart = named.charts[id];\r
+\r
+ if(typeof chart === 'undefined') {\r
+ chart = {\r
+ id: id, // the unique id of the chart\r
+ name: '', // the unique name of the chart\r
+ title: service.name + ' Bind, Current Recursive Clients', // the title of the chart\r
+ units: 'clients', // the units of the chart dimensions\r
+ family: 'named', // the family of the chart\r
+ category: 'named', // the category of the chart\r
+ type: netdata.chartTypes.line, // the type of the chart\r
+ priority: 150, // the priority relative to others in the same family and category\r
+ update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart\r
+ dimensions: {\r
+ 'clients': {\r
+ id: 'clients', // the unique id of the dimension\r
+ name: '', // the name of the dimension\r
+ algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm\r
+ multiplier: 1, // the multiplier\r
+ divisor: 1, // the divisor\r
+ hidden: false // is hidden (boolean)\r
+ }\r
+ }\r
+ };\r
+\r
+ chart = service.chart(id, chart);\r
+ named.charts[id] = chart;\r
+ }\r
+\r
+ service.begin(chart);\r
+ service.set('clients', RecursClients);\r
+ service.end();\r
+ }\r
+ }\r
+\r
+ if(typeof r.opcodes !== 'undefined')\r
+ service.module.chartFromMembers(service, r.opcodes, 'in_opcodes', 'Bind, Global Incoming Requests by OpCode', 'requests/s', 'named', 'named', netdata.chartTypes.stacked, 1000, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(typeof r.qtypes !== 'undefined')\r
+ service.module.chartFromMembers(service, r.qtypes, 'in_qtypes', 'Bind, Global Incoming Requests by Query Type', 'requests/s', 'named', 'named', netdata.chartTypes.stacked, 2000, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(typeof r.views !== 'undefined') {\r
+ for( var x in r.views ) {\r
+ var resolver = r.views[x].resolver;\r
+\r
+ if(typeof resolver !== 'undefined') {\r
+ if(typeof resolver.stats !== 'undefined') {\r
+ var NumFetch = 0;\r
+ var key = service.name + '.' + x;\r
+ var default_enable = false;\r
+ var rtt = {}, rtt_enable = false;\r
+\r
+ // NumFetch is an absolute value\r
+ if(typeof resolver.stats['NumFetch'] !== 'undefined') {\r
+ named.lookups.numfetch[key] = true;\r
+ NumFetch = resolver.stats['NumFetch'];\r
+ delete resolver.stats['NumFetch'];\r
+ }\r
+ if(typeof resolver.stats['BucketSize'] !== 'undefined') {\r
+ delete resolver.stats['BucketSize'];\r
+ }\r
+\r
+ // split the QryRTT* from the main chart\r
+ for( var y in resolver.stats ) {\r
+ // we maintain an index of the values found\r
+ // mapping them to objects splitted\r
+\r
+ var look = named.lookups.resolver_stats[y];\r
+ if(typeof look === 'undefined') {\r
+ if(y.match(/^QryRTT/) !== null) {\r
+ named.lookups.resolver_stats[y] = {\r
+ name: y,\r
+ type: 'rtt'\r
+ };\r
+ }\r
+ else {\r
+ named.lookups.resolver_stats[y] = {\r
+ name: y,\r
+ type: 'default'\r
+ };\r
+ }\r
+\r
+ look = named.lookups.resolver_stats[y];\r
+ // netdata.error('lookup resolver stats value: ' + y + ' >>> ' + look.type);\r
+ }\r
+\r
+ switch(look.type) {\r
+ case 'rtt': rtt[look.name] = resolver.stats[y]; delete resolver.stats[y]; rtt_enable = true; break;\r
+ default: default_enable = true; break;\r
+ }\r
+ }\r
+\r
+ if(rtt_enable)\r
+ service.module.chartFromMembers(service, rtt, 'view_resolver_rtt_' + x, 'Bind, ' + x + ' View, Resolver Round Trip Timings', 'queries/s', 'named', 'named', netdata.chartTypes.stacked, 5600, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ if(default_enable)\r
+ service.module.chartFromMembers(service, resolver.stats, 'view_resolver_stats_' + x, 'Bind, ' + x + ' View, Resolver Statistics', 'operations/s', 'named', 'named', netdata.chartTypes.line, 5500, netdata.chartAlgorithms.incremental, 1, 1);\r
+\r
+ // NumFetch chart\r
+ if(typeof named.lookups.numfetch[key] !== 'undefined') {\r
+ var id = 'named_' + service.name + '.view_resolver_numfetch_' + x;\r
+ var chart = named.charts[id];\r
+\r
+ if(typeof chart === 'undefined') {\r
+ chart = {\r
+ id: id, // the unique id of the chart\r
+ name: '', // the unique name of the chart\r
+ title: service.name + ' Bind, ' + x + ' View, Resolver Active Queries', // the title of the chart\r
+ units: 'queries', // the units of the chart dimensions\r
+ family: 'named', // the family of the chart\r
+ category: 'named', // the category of the chart\r
+ type: netdata.chartTypes.line, // the type of the chart\r
+ priority: 5000, // the priority relative to others in the same family and category\r
+ update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart\r
+ dimensions: {\r
+ 'queries': {\r
+ id: 'queries', // the unique id of the dimension\r
+ name: '', // the name of the dimension\r
+ algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm\r
+ multiplier: 1, // the multiplier\r
+ divisor: 1, // the divisor\r
+ hidden: false // is hidden (boolean)\r
+ }\r
+ }\r
+ };\r
+\r
+ chart = service.chart(id, chart);\r
+ named.charts[id] = chart;\r
+ }\r
+\r
+ service.begin(chart);\r
+ service.set('queries', NumFetch);\r
+ service.end();\r
+ }\r
+ }\r
+ \r
+ if(typeof resolver.qtypes !== 'undefined')\r
+ service.module.chartFromMembers(service, resolver.qtypes, 'view_resolver_qtypes_' + x, 'Bind, ' + x + ' View, Requests by Query Type', 'requests/s', 'named', 'named', netdata.chartTypes.stacked, 6000, netdata.chartAlgorithms.incremental, 1, 1);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ },\r
+\r
+ // module.serviceExecute()\r
+ // this function is called only from this module\r
+ // its purpose is to prepare the request and call\r
+ // netdata.serviceExecute()\r
+ serviceExecute: function(name, a_url, update_every) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(this.name + ': ' + name + ': url: ' + a_url + ', update_every: ' + update_every);\r
+ netdata.serviceExecute({\r
+ name: name,\r
+ request: netdata.requestFromURL(a_url),\r
+ update_every: update_every,\r
+ added: false,\r
+ enabled: true,\r
+ module: this\r
+ }, this.processResponse);\r
+ },\r
+\r
+ configure: function(config) {\r
+ var added = 0;\r
+\r
+ if(this.enable_autodetect === true) {\r
+ this.serviceExecute('local', 'http://localhost:8888/json/v1', this.update_every);\r
+ added++;\r
+ }\r
+ \r
+ if(typeof(config.servers) !== 'undefined') {\r
+ var len = config.servers.length;\r
+ while(len--) {\r
+ if(typeof config.servers[len].update_every === 'undefined')\r
+ config.servers[len].update_every = this.update_every;\r
+ else\r
+ config.servers[len].update_every = config.servers[len].update_every * 1000;\r
+\r
+ this.serviceExecute(config.servers[len].name, config.servers[len].url, config.servers[len].update_every);\r
+ added++;\r
+ }\r
+ }\r
+\r
+ return added;\r
+ },\r
+\r
+ // module.update()\r
+ // this is called repeatidly to collect data, by calling\r
+ // netdata.serviceExecute()\r
+ update: function(service, callback) {\r
+ netdata.serviceExecute(service, function(serv, data) {\r
+ service.module.processResponse(serv, data);\r
+ callback();\r
+ });\r
+ },\r
+};\r
+\r
+module.exports = named;\r
--- /dev/null
+// https://github.com/justmoon/node-extend
+
+'use strict';
+
+var hasOwn = Object.prototype.hasOwnProperty;
+var toStr = Object.prototype.toString;
+
+var isArray = function isArray(arr) {
+ if (typeof Array.isArray === 'function') {
+ return Array.isArray(arr);
+ }
+
+ return toStr.call(arr) === '[object Array]';
+};
+
+var isPlainObject = function isPlainObject(obj) {
+ if (!obj || toStr.call(obj) !== '[object Object]') {
+ return false;
+ }
+
+ var hasOwnConstructor = hasOwn.call(obj, 'constructor');
+ var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
+ // Not own constructor property must be Object
+ if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+ var key;
+ for (key in obj) { /**/ }
+
+ return typeof key === 'undefined' || hasOwn.call(obj, key);
+};
+
+module.exports = function extend() {
+ var options, name, src, copy, copyIsArray, clone;
+ var target = arguments[0];
+ var i = 1;
+ var length = arguments.length;
+ var deep = false;
+
+ // Handle a deep copy situation
+ if (typeof target === 'boolean') {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ } else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) {
+ target = {};
+ }
+
+ for (; i < length; ++i) {
+ options = arguments[i];
+ // Only deal with non-null/undefined values
+ if (options != null) {
+ // Extend the base object
+ for (name in options) {
+ src = target[name];
+ copy = options[name];
+
+ // Prevent never-ending loop
+ if (target !== copy) {
+ // Recurse if we're merging plain objects or arrays
+ if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
+ if (copyIsArray) {
+ copyIsArray = false;
+ clone = src && isArray(src) ? src : [];
+ } else {
+ clone = src && isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[name] = extend(deep, clone, copy);
+
+ // Don't bring in undefined values
+ } else if (typeof copy !== 'undefined') {
+ target[name] = copy;
+ }
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
--- /dev/null
+'use strict';\r
+\r
+var url = require('url');\r
+var http = require('http');\r
+var util = require('util');\r
+\r
+/*\r
+var netdata = require('netdata');\r
+\r
+var example_chart = {\r
+ id: 'id', // the unique id of the chart\r
+ name: 'name', // the name of the chart\r
+ title: 'title', // the title of the chart\r
+ units: 'units', // the units of the chart dimensions\r
+ family: 'family', // the family of the chart\r
+ category: 'category', // the category of the chart\r
+ type: netdata.chartTypes.line, // the type of the chart\r
+ priority: 0, // the priority relative to others in the same family and category\r
+ update_every: 1, // the expected update frequency of the chart\r
+ dimensions: {\r
+ 'dim1': {\r
+ id: 'dim1', // the unique id of the dimension\r
+ name: 'name', // the name of the dimension\r
+ algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm\r
+ multiplier: 1, // the multiplier\r
+ divisor: 1, // the divisor\r
+ hidden: false, // is hidden (boolean)\r
+ },\r
+ 'dim2': {\r
+ id: 'dim2', // the unique id of the dimension\r
+ name: 'name', // the name of the dimension\r
+ algorithm: 'absolute', // the id of the netdata algorithm\r
+ multiplier: 1, // the multiplier\r
+ divisor: 1, // the divisor\r
+ hidden: false, // is hidden (boolean)\r
+ }\r
+ // add as many dimensions as needed\r
+ }\r
+};\r
+*/\r
+\r
+var netdata = {\r
+ options: {\r
+ filename: __filename,\r
+ DEBUG: false,\r
+ update_every: 1000,\r
+ },\r
+\r
+ chartAlgorithms: {\r
+ incremental: 'incremental',\r
+ absolute: 'absolute',\r
+ percentage_of_absolute_row: 'percentage-of-absolute-row',\r
+ percentage_of_incremental_row: 'percentage-of-incremental-row'\r
+ },\r
+\r
+ chartTypes: {\r
+ line: 'line',\r
+ area: 'area',\r
+ stacked: 'stacked'\r
+ },\r
+\r
+ services: new Array(),\r
+ modules_configuring: 0,\r
+ charts: {},\r
+\r
+ stringify: function(obj) {\r
+ return util.inspect(obj, {depth: 10});\r
+ },\r
+\r
+ // show debug info, if debug is enabled\r
+ debug: function(msg) {\r
+ if(this.options.DEBUG === true) {\r
+ var now = new Date();\r
+ console.error(now.toString() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());\r
+ }\r
+ },\r
+\r
+ // log an error\r
+ error: function(msg) {\r
+ var now = new Date();\r
+ console.error(now.toString() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());\r
+ },\r
+\r
+ // send data to netdata\r
+ send: function(msg) {\r
+ console.log(msg.toString());\r
+ },\r
+\r
+ serviceAdd: function(service) {\r
+ if(service.added !== true) {\r
+ service.updates = 0;\r
+ service.enabled = true;\r
+ service.added = true;\r
+ service.running = false;\r
+ service.started = 0;\r
+ service.ended = 0;\r
+ service._current_chart = null; // the current chart we work on\r
+ service._queue = '';\r
+ service.queue = function(txt) {\r
+ this._queue += txt + '\n';\r
+ };\r
+\r
+ service._send_chart_to_netdata = function(chart) {\r
+ // internal function to send a chart to netdata\r
+ this.queue('CHART "' + chart.id + '" "' + chart.name + '" "' + chart.title + '" "' + chart.units + '" "' + chart.family + '" "' + chart.category + '" "' + chart.type + '" ' + chart.priority.toString() + ' ' + chart.update_every.toString());\r
+ \r
+ for(var dim in chart.dimensions) {\r
+ var d = chart.dimensions[dim];\r
+\r
+ this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true)?'hidden':'').toString());\r
+ d._created = true;\r
+ d._updated = false;\r
+ }\r
+\r
+ chart._created = true;\r
+ chart._updated = false;\r
+ };\r
+\r
+ // begin data collection for a chart\r
+ service.begin = function(chart) {\r
+ if(this._current_chart !== null && this._current_chart !== chart) {\r
+ netdata.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.');\r
+ this.end();\r
+ }\r
+\r
+ if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] != chart) {\r
+ netdata.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.');\r
+ return false;\r
+ }\r
+\r
+ if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id);\r
+ this._current_chart = chart;\r
+ this._current_chart._began = true;\r
+\r
+ if(this._current_chart._dimensions_count !== 0) {\r
+ if(this._current_chart._created === false || this._current_chart._updated === true)\r
+ this._send_chart_to_netdata(this._current_chart);\r
+\r
+ var now = this.ended;\r
+ this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString());\r
+ }\r
+ // else netdata.error('Called begin() for chart ' + chart.id + ' which is empty.');\r
+\r
+ this._current_chart._last_updated = now;\r
+ this._current_chart._began = true;\r
+ this._current_chart._counter++;\r
+\r
+ return true;\r
+ };\r
+\r
+ // set a collected value for a chart\r
+ // we do most things on the first value we attempt to set\r
+ service.set = function(dimension, value) {\r
+ if(this._current_chart === null) {\r
+ netdata.error('Called set(' + dimension + ', ' + value + ') without an open chart.');\r
+ return false;\r
+ }\r
+\r
+ if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') {\r
+ netdata.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".');\r
+ return false;\r
+ }\r
+\r
+ if(this._current_chart._dimensions_count !== 0)\r
+ this.queue('SET ' + dimension + ' = ' + value);\r
+\r
+ return true;\r
+ };\r
+\r
+ // end data collection for the current chart - after calling begin()\r
+ service.end = function() {\r
+ if(this._current_chart !== null && this._current_chart._began === false) {\r
+ netdata.error('Called end() without an open chart.');\r
+ return false;\r
+ }\r
+\r
+ if(this._current_chart._dimensions_count !== 0) {\r
+ this.queue('END');\r
+ netdata.send(this._queue);\r
+ }\r
+\r
+ this._queue = '';\r
+ this._current_chart._began = false;\r
+ if(netdata.options.DEBUG === true) netdata.debug('committed chart ' + this._current_chart.id);\r
+ this._current_chart = null;\r
+ return true;\r
+ };\r
+\r
+ // discard the collected values for the current chart - after calling begin()\r
+ service.flush = function() {\r
+ if(this._current_chart === null || this._current_chart._began === false) {\r
+ netdata.error('Called flush() without an open chart.');\r
+ return false;\r
+ }\r
+\r
+ this._queue = '';\r
+ this._current_chart._began = false;\r
+ this._current_chart = null;\r
+ return true;\r
+ };\r
+\r
+ // create a netdata chart\r
+ service.chart = function(id, chart) {\r
+ if(typeof(netdata.charts[id]) === 'undefined') {\r
+ netdata.charts[id] = {\r
+ _created: false,\r
+ _updated: false,\r
+ _began: false,\r
+ _counter: 0,\r
+ _last_updated: 0,\r
+ _dimensions_count: 0,\r
+ id: id,\r
+ name: id,\r
+ title: 'untitled chart',\r
+ units: 'a unit',\r
+ family: id,\r
+ category: id,\r
+ type: netdata.chartTypes.line,\r
+ priority: 0,\r
+ update_every: netdata.options.update_every,\r
+ dimensions: {}\r
+ };\r
+ }\r
+\r
+ var c = netdata.charts[id];\r
+\r
+ if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its name');\r
+ c.name = chart.name;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its title');\r
+ c.title = chart.title;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its units');\r
+ c.units = chart.units;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its family');\r
+ c.family = chart.family;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.category) !== 'undefined' && chart.category !== c.category) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its category');\r
+ c.category = chart.category;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its type');\r
+ c.type = chart.type;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its priority');\r
+ c.priority = chart.priority;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every');\r
+ c.update_every = chart.update_every;\r
+ c._updated = true;\r
+ }\r
+\r
+ if(typeof(chart.dimensions) !== 'undefined') {\r
+ for(var x in chart.dimensions) {\r
+ if(typeof(c.dimensions[x]) === 'undefined') {\r
+ c._dimensions_count++;\r
+\r
+ c.dimensions[x] = {\r
+ _created: false,\r
+ _updated: false,\r
+ id: x, // the unique id of the dimension\r
+ name: x, // the name of the dimension\r
+ algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm\r
+ multiplier: 1, // the multiplier\r
+ divisor: 1, // the divisor\r
+ hidden: false, // is hidden (boolean)\r
+ };\r
+\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x);\r
+ c._updated = true;\r
+ }\r
+\r
+ var dim = chart.dimensions[x];\r
+ var d = c.dimensions[x];\r
+\r
+ if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name');\r
+ d.name = dim.name;\r
+ d._updated = true;\r
+ }\r
+\r
+ if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm);\r
+ d.algorithm = dim.algorithm;\r
+ d._updated = true;\r
+ }\r
+\r
+ if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier');\r
+ d.multiplier = dim.multiplier;\r
+ d._updated = true;\r
+ }\r
+\r
+ if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor');\r
+ d.divisor = dim.divisor;\r
+ d._updated = true;\r
+ }\r
+\r
+ if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) {\r
+ if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status');\r
+ d.hidden = dim.hidden;\r
+ d._updated = true;\r
+ }\r
+\r
+ if(d._updated) c._updated = true;\r
+ }\r
+ }\r
+\r
+ if(netdata.options.DEBUG === true) netdata.debug(netdata.charts);\r
+ return netdata.charts[id];\r
+ };\r
+\r
+ this.services.push(service);\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': service added.');\r
+ }\r
+ },\r
+\r
+ serviceRun: function(service) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': starting data collection...');\r
+ service.running = true;\r
+ service.started = new Date().getTime();\r
+ service.updates++;\r
+\r
+ service.module.update(service, function() {\r
+ service.ended = new Date().getTime();\r
+ service.duration = service.ended - service.started;\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.');\r
+ service.running = false;\r
+ });\r
+ },\r
+\r
+ runAllServices: function() {\r
+ if(netdata.options.DEBUG === true) netdata.debug('runAllServices()');\r
+\r
+ var now = new Date().getTime();\r
+ var len = netdata.services.length;\r
+ while(len--) {\r
+ var service = netdata.services[len];\r
+\r
+ if(service.enabled === false || service.running === true) continue;\r
+ if(now - service.ended < service.update_every) continue;\r
+\r
+ netdata.serviceRun(service);\r
+ }\r
+\r
+ setTimeout(netdata.runAllServices, 100);\r
+ },\r
+\r
+ start: function() {\r
+ if(netdata.options.DEBUG === true) this.debug('started, services:');\r
+\r
+ if(this.services.length === 0) {\r
+ this.disableNodePlugin();\r
+ process.exit(1);\r
+ }\r
+ else this.runAllServices();\r
+ },\r
+\r
+ // disable the whole node.js plugin\r
+ disableNodePlugin: function() {\r
+ this.send('DISABLE');\r
+ process.exit(1);\r
+ },\r
+\r
+ requestFromParams: function(protocol, hostname, port, path, method) {\r
+ return {\r
+ protocol: protocol,\r
+ hostname: hostname,\r
+ port: port,\r
+ path: path,\r
+ //family: 4,\r
+ method: method,\r
+ headers: {\r
+ 'Content-Type': 'application/x-www-form-urlencoded',\r
+ 'Connection': 'keep-alive'\r
+ },\r
+ agent: new http.Agent({\r
+ keepAlive: true,\r
+ keepAliveMsecs: netdata.options.update_every,\r
+ maxSockets: 2, // it must be 2 to work\r
+ maxFreeSockets: 1\r
+ })\r
+ };\r
+ },\r
+\r
+ requestFromURL: function(a_url) {\r
+ var u = url.parse(a_url);\r
+ return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET');\r
+ },\r
+\r
+ processResponse: function(service, data, callback) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': processing response...');\r
+\r
+ callback(service, data);\r
+\r
+ service.module.running--;\r
+ if(service.module.running <= 0) {\r
+ service.module.running = 0;\r
+\r
+ // check if we run under configure\r
+ if(service.module.configure_callback !== null) {\r
+ if(netdata.options.DEBUG === true) this.debug(service.module.name + ': configuration finish callback called from processResponse().');\r
+ var ccallback = service.module.configure_callback;\r
+ service.module.configure_callback = null;\r
+ ccallback();\r
+ }\r
+ }\r
+ },\r
+\r
+ getResponse: function(service, response, callback) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got response...');\r
+\r
+ var end = false;\r
+ var data = '';\r
+ response.setEncoding('utf8');\r
+\r
+ if(response.statusCode !== 200) {\r
+ if(end === false) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got HTTP code ' + response.statusCode + ', failed to get data.');\r
+ end = true;\r
+ netdata.processResponse(service, null, callback);\r
+ }\r
+ }\r
+\r
+ response.on('data', function(chunk) {\r
+ if(end === false) data += chunk;\r
+ });\r
+\r
+ response.on('error', function() {\r
+ if(end === false) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': Read error, failed to get data.');\r
+ end = true;\r
+ netdata.processResponse(service, null, callback);\r
+ }\r
+ });\r
+\r
+ response.on('end', function() {\r
+ if(end === false) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.');\r
+ end = true;\r
+ netdata.processResponse(service, data, callback);\r
+ }\r
+ });\r
+ },\r
+\r
+ serviceExecute: function(service, callback) {\r
+ service.module.running++;\r
+\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': making request: ' + netdata.stringify(service.request));\r
+ var req = http.request(service.request, function(response) {\r
+ if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': request done.');\r
+ netdata.getResponse(service, response, callback);\r
+ });\r
+\r
+ req.on('error', function(e) {\r
+ netdata.error(service.module.name + ': ' + service.name + ': failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);\r
+ netdata.processResponse(service, null, callback);\r
+ });\r
+\r
+ // write data to request body\r
+ if(typeof service.postData !== 'undefined' && service.request.method === 'POST')\r
+ req.write(service.postData);\r
+\r
+ req.end();\r
+ },\r
+\r
+ configure: function(module, config, callback) {\r
+ if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...');\r
+\r
+ module.running = 0;\r
+ module.update_every = this.options.update_every;\r
+\r
+ if(typeof config.update_every !== 'undefined')\r
+ module.update_every = config.update_every * 1000;\r
+\r
+ module.enable_autodetect = (config.enable_autodetect)?true:false;\r
+\r
+ if(typeof(callback) === 'function')\r
+ module.configure_callback = callback;\r
+ else\r
+ module.configure_callback = null;\r
+\r
+ var added = module.configure(config);\r
+\r
+ if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.');\r
+\r
+ if(module.configure_callback !== null && added === 0) {\r
+ if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().');\r
+ module.configure_callback = null;\r
+ callback();\r
+ }\r
+\r
+ return added;\r
+ }\r
+\r
+};\r
+\r
+if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from: ' + __filename);\r
+module.exports = netdata;\r
+\r
+/*\r
+var test1 = netdata.chart('test1', { name: 'test name', dimensions: { dim1: {}}});\r
+netdata.begin(test1);\r
+netdata.set('dim1', 1);\r
+netdata.end();\r
+netdata.begin(test1);\r
+netdata.set('dim1', 2);\r
+netdata.end();\r
+netdata.begin(test1);\r
+netdata.set('dim1', 3);\r
+netdata.end();\r
+netdata.begin(test1);\r
+netdata.set('dim1', 4);\r
+netdata.end();\r
+*/\r
--- /dev/null
+'use strict';
+
+// This program will connect to one or more SMA Sunny Webboxes
+// to get the Solar Power Generated (current, today, total).
+
+// example configuration in /etc/netdata/sma_webbox.conf
+/*
+{
+ "enable_autodetect": false,
+ "update_every": 5,
+ "servers": [
+ {
+ "name": "plant1",
+ "hostname": "10.0.1.1",
+ "update_every": 10
+ },
+ {
+ "name": "plant2",
+ "hostname": "10.0.2.1",
+ "update_every": 15
+ }
+ ]
+}
+*/
+
+var url = require('url');
+var http = require('http');
+var netdata = require('netdata');
+
+if(netdata.options.DEBUG === true) netdata.debug('loaded ' + __filename + ' plugin');
+
+var webbox = {
+ name: __filename,
+ enable_autodetect: true,
+ update_every: 1000,
+
+ charts: {},
+
+ processResponse: function(service, data) {
+ if(data !== null) {
+ var r = JSON.parse(data);
+
+ var d = {
+ 'GriPwr': {
+ unit: null,
+ value: null
+ },
+ 'GriEgyTdy': {
+ unit: null,
+ value: null
+ },
+ 'GriEgyTot': {
+ unit: null,
+ value: null
+ }
+ };
+
+ // parse the webbox response
+ // and put it in our d object
+ var found = 0;
+ var len = r.result.overview.length;
+ while(len--) {
+ var e = r.result.overview[len];
+ if(typeof(d[e.meta]) !== 'undefined') {
+ found++;
+ d[e.meta].value = e.value;
+ d[e.meta].unit = e.unit;
+ }
+ }
+
+ // add the service
+ if(found > 0 && service.added !== true)
+ netdata.serviceAdd(service);
+
+ // Grid Current Power Chart
+ if(d['GriPwr'].value !== null) {
+ var id = 'sma_webbox_' + service.name + '.current';
+ var chart = webbox.charts[id];
+
+ if(typeof chart === 'undefined') {
+ chart = {
+ id: id, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Current Grid Power', // the title of the chart
+ units: d['GriPwr'].unit, // the units of the chart dimensions
+ family: 'sma_webbox_' + service.name, // the family of the chart
+ category: 'sma_webbox_' + service.name, // the category of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: 1000, // the priority relative to others in the same family and category
+ update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
+ dimensions: {
+ 'GriPwr': {
+ id: 'GriPwr', // the unique id of the dimension
+ name: 'power', // 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)
+ }
+ }
+ };
+
+ chart = service.chart(id, chart);
+ webbox.charts[id] = chart;
+ }
+
+ service.begin(chart);
+ service.set('GriPwr', Math.round(d['GriPwr'].value));
+ service.end();
+ }
+
+ if(d['GriEgyTdy'].value !== null) {
+ var id = 'sma_webbox_' + service.name + '.today';
+ var chart = webbox.charts[id];
+
+ if(typeof chart === 'undefined') {
+ chart = {
+ id: id, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Today Grid Power', // the title of the chart
+ units: d['GriEgyTdy'].unit, // the units of the chart dimensions
+ family: 'sma_webbox_' + service.name, // the family of the chart
+ category: 'sma_webbox_' + service.name, // the category of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: 1000, // the priority relative to others in the same family and category
+ update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
+ dimensions: {
+ 'GriEgyTdy': {
+ id: 'GriEgyTdy', // the unique id of the dimension
+ name: 'power', // the name of the dimension
+ algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
+ multiplier: 1, // the multiplier
+ divisor: 1000, // the divisor
+ hidden: false // is hidden (boolean)
+ }
+ }
+ };
+
+ chart = service.chart(id, chart);
+ webbox.charts[id] = chart;
+ }
+
+ service.begin(chart);
+ service.set('GriEgyTdy', Math.round(d['GriEgyTdy'].value * 1000));
+ service.end();
+ }
+
+ if(d['GriEgyTot'].value !== null) {
+ var id = 'sma_webbox_' + service.name + '.total';
+ var chart = webbox.charts[id];
+
+ if(typeof chart === 'undefined') {
+ chart = {
+ id: id, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Total Grid Power', // the title of the chart
+ units: d['GriEgyTot'].unit, // the units of the chart dimensions
+ family: 'sma_webbox_' + service.name, // the family of the chart
+ category: 'sma_webbox_' + service.name, // the category of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: 1000, // the priority relative to others in the same family and category
+ update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
+ dimensions: {
+ 'GriEgyTot': {
+ id: 'GriEgyTot', // the unique id of the dimension
+ name: 'power', // the name of the dimension
+ algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
+ multiplier: 1, // the multiplier
+ divisor: 1000, // the divisor
+ hidden: false // is hidden (boolean)
+ }
+ }
+ };
+
+ chart = service.chart(id, chart);
+ webbox.charts[id] = chart;
+ }
+
+ service.begin(chart);
+ service.set('GriEgyTot', Math.round(d['GriEgyTot'].value * 1000));
+ service.end();
+ }
+ }
+ },
+
+ // module.serviceExecute()
+ // this function is called only from this module
+ // its purpose is to prepare the request and call
+ // netdata.serviceExecute()
+ serviceExecute: function(name, hostname, update_every) {
+ if(netdata.options.DEBUG === true) netdata.debug(this.name + ': ' + name + ': hostname: ' + hostname + ', update_every: ' + update_every);
+
+ var service = {
+ name: name,
+ request: netdata.requestFromURL('http://' + hostname + '/rpc'),
+ update_every: update_every,
+ added: false,
+ enabled: true,
+ module: this
+ };
+ service.postData = 'RPC={"proc":"GetPlantOverview","format":"JSON","version":"1.0","id":"1"}';
+ service.request.method = 'POST';
+ service.request.headers['Content-Length'] = service.postData.length;
+
+ netdata.serviceExecute(service, this.processResponse);
+ },
+
+ configure: function(config) {
+ var added = 0;
+
+ if(typeof(config.servers) !== 'undefined') {
+ var len = config.servers.length;
+ while(len--) {
+ if(typeof config.servers[len].update_every === 'undefined')
+ config.servers[len].update_every = this.update_every;
+ else
+ config.servers[len].update_every = config.servers[len].update_every * 1000;
+
+ if(config.servers[len].update_every < 5000)
+ config.servers[len].update_every = 5000;
+
+ this.serviceExecute(config.servers[len].name, config.servers[len].hostname, config.servers[len].update_every);
+ added++;
+ }
+ }
+
+ return added;
+ },
+
+ // module.update()
+ // this is called repeatidly to collect data, by calling
+ // netdata.serviceExecute()
+ update: function(service, callback) {
+ netdata.serviceExecute(service, function(serv, data) {
+ service.module.processResponse(serv, data);
+ callback();
+ });
+ },
+};
+
+module.exports = webbox;
dist_plugins_SCRIPTS = \
charts.d.dryrun-helper.sh \
charts.d.plugin \
+ node.d.plugin \
tc-qos-helper.sh \
loopsleepms.sh.inc \
- sma_webbox.plugin \
$(NULL)
+++ /dev/null
-#!/usr/bin/env node
-
-// This program will connect to one or more SMA Sunny Webboxes
-// to get the Solar Power Generated (current, today, total).
-
-// It needs a configuration file named sma_webbox.conf
-// The configuration is a JSON array, like this:
-
-/* --- BEGIN EXAMPLE CONFIGURATION ---
-
-[
- {
- "name": "label_of_solar_installation1",
- "hostname": "10.11.13.2"
- },
- {
- "name": "label_of_solar_installation2",
- "hostname": "10.11.13.3"
- }
-]
-
- --- END EXAMPLE CONFIGURATION --- */
-
-// you can add an unlimited number of webboxes
-
-var http = require('http');
-var fs = require('fs');
-
-// --------------------------------------------------------------------------------------------------------------------
-// get NETDATA environment variables
-
-var NETDATA_PLUGINS_DIR = process.env.NETDATA_PLUGINS_DIR || __dirname;
-var NETDATA_CONFIG_DIR = process.env.NETDATA_CONFIG_DIR || '/etc/netdata';
-var NETDATA_UPDATE_EVERY = process.env.NETDATA_UPDATE_EVERY || 1;
-NETDATA_UPDATE_EVERY = NETDATA_UPDATE_EVERY * 1000;
-
-var filename = NETDATA_CONFIG_DIR + '/sma_webbox.conf';
-
-// --------------------------------------------------------------------------------------------------------------------
-// get command line arguments
-
-var DEBUG = false;
-var UPDATE_EVERY = NETDATA_UPDATE_EVERY;
-var EXIT_AFTER_MS = 3600 * 1000;
-
-function debug(msg) {
- if(DEBUG) {
- var now = new Date();
- console.error(now.toString() + ': ' + __filename + ': DEBUG: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
- }
-}
-
-function error(msg) {
- var now = new Date();
- console.error(now.toString() + ': ' + __filename + ': ERROR: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
-}
-
-function netdata(msg) {
- console.log(msg.toString());
-}
-
-var found_myself = false;
-process.argv.forEach(function (val, index, array) {
- debug('PARAM: ' + val);
-
- if(!found_myself) {
- if(val === __filename)
- found_myself = true;
- }
- else {
- switch(val) {
- case 'debug':
- DEBUG = true;
- debug('DEBUG enabled');
- break;
-
- default:
- try {
- var x = parseInt(val);
- if(x > 0) {
- UPDATE_EVERY = x * 1000;
- if(UPDATE_EVERY < NETDATA_UPDATE_EVERY) {
- UPDATE_EVERY = NETDATA_UPDATE_EVERY;
- debug('Update frequency ' + x + 's is too low');
- }
-
- debug('Update frequency set to ' + UPDATE_EVERY + ' ms');
- }
- else error('Ignoring parameter: ' + val);
- }
- catch(e) {
- error('Cannot get value of parameter: ' + val);
- }
- }
- }
-});
-
-// there is no meaning to update the values sooner than 5 seconds
-// the SMA webbox collects data every 5 seconds
-if(UPDATE_EVERY < 5000) {
- debug('Adjusting update frequency to 5 seconds');
- UPDATE_EVERY = 5000;
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-// parse configuration
-
-var Servers = new Array();
-try {
- Servers = JSON.parse(fs.readFileSync(filename, 'utf8'));
-}
-catch(e) {
- error('Cannot read configuration file ' + filename + ': ' + e.message);
- netdata('DISABLE');
- process.exit(1);
-}
-
-
-// --------------------------------------------------------------------------------------------------------------------
-// library functions
-
-// function to get data from server
-function getSMAData(server, callback) {
- // make sure there is not a request in progress for this webbox
- if(typeof(server.running) === 'boolean' && server.running) {
- debug(server.name + ' is already running');
- return false;
- }
- server.running = true;
-
- var now = new Date().getTime();
-
- // align the time to collect the values
- if(typeof(server.next_run) === 'undefined')
- server.next_run = now - (now % UPDATE_EVERY) + UPDATE_EVERY;
-
- // if it is too soon, we will collect data later
- if(now < server.next_run) {
- debug(server.name + ' not yet');
- server.running = false;
- return false;
- }
-
- // find the next refresh for this server
- while(server.next_run < now)
- server.next_run += UPDATE_EVERY;
-
- if(typeof(server.last_updated) === 'undefined')
- server.last_updated = 0;
-
- // create an agent for keep-alive
- if(typeof(server.agent) === 'undefined') {
- server.agent = new http.Agent({
- keepAlive: true,
- keepAliveMsecs: UPDATE_EVERY * 5,
- maxSockets: 2, // it needs 2, otherwise it ignores keepAlive
- maxFreeSockets: 1
- });
- }
-
- // create the charts, if we haven't already
- if(typeof(server.created) === 'undefined') {
- netdata('CHART sma_webbox_' + server.name + '.current "" "Solar Power Production of ' + server.name + '" "Watts" sma_webbox_' + server.name + ' "" area 15000 ' + Math.round(UPDATE_EVERY / 1000));
- netdata('DIMENSION GriPwr power absolute 1 1');
-
- netdata('CHART sma_webbox_' + server.name + '.today "" "Today\'s Solar Power Production of ' + server.name + '" "kWatts" sma_webbox_' + server.name + ' "" area 15001 ' + Math.round(UPDATE_EVERY / 1000));
- netdata('DIMENSION GriEgyTdy today absolute 1 1000');
-
- netdata('CHART sma_webbox_' + server.name + '.total "" "Total Solar Power Production of ' + server.name + '" "kWatts" sma_webbox_' + server.name + ' "" area 15001 ' + Math.round(UPDATE_EVERY / 1000));
- netdata('DIMENSION GriEgyTot total absolute 1 1000');
-
- server.created = true;
- }
-
- // initialize our metrics to null
- // so that we will know if we read them or not
- server.data = {
- 'GriPwr': {
- unit: null,
- value: null
- },
- 'GriEgyTdy': {
- unit: null,
- value: null
- },
- 'GriEgyTot': {
- unit: null,
- value: null
- }
- };
-
- var postData = 'RPC={"proc":"GetPlantOverview","format":"JSON","version":"1.0","id":"1"}'
-
- var options = {
- protocol: server.protocol || 'http:',
- hostname: server.hostname,
- path: server.path || '/rpc',
- family: server.family || 4,
- port: server.port || 80,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Connection': 'keep-alive',
- 'Content-Length': postData.length
- },
- agent: server.agent
- };
-
- server.error = 0;
- server.success = false;
- server.response = '';
- server.request = http.request(options, function(res) {
- res.setEncoding('utf8');
- server.response_code = res.statusCode;
-
- // check if we got HTTP/200
- if(server.response_code !== 200) {
- // if this request is in error and we have
- // handled it, don't do anything more
- if(server.error === 0) {
- debug('Server responded with ' + server.response_code + ', failed to get data.');
- server.error = 503;
- server.success = false;
- server.running = false;
- if(typeof(callback) === 'function')
- callback(server);
- }
- }
-
- res.on('data', function(chunk) {
- // read more data, only if there is no error
- if(server.error === 0)
- server.response += chunk;
- });
-
- res.on('error', function() {
- // do we have already handled this error?
- if(server.error !== 0)
- return;
-
- debug('Received HTTP read error, failed to get data.');
- server.error = 504;
- server.success = false;
- server.running = false;
- if(typeof(callback) === 'function')
- callback(server);
- });
-
- res.on('end', function() {
- // if there is an error we have handled
- // don't do anything
- if(server.error !== 0)
- return;
-
- var t = new Date().getTime();
-
- server.dt = 0;
- if(server.last_updated !== 0)
- server.dt = t - server.last_updated;
-
- debug('RESPONSE: ' + server.response);
- try {
- server.response = JSON.parse(server.response);
-
- var len = server.response.result.overview.length;
- while(len--) {
- var e = server.response.result.overview[len];
- debug(e);
- if(typeof(server.data[e.meta]) !== 'undefined') {
- server.data[e.meta].value = e.value;
- server.data[e.meta].unit = e.unit;
- }
- }
-
- if(server.data['GriPwr'].value !== null) {
- netdata('BEGIN sma_webbox_' + server.name + '.current ' + ((server.dt)?server.dt*1000:'').toString());
- netdata('SET GriPwr = ' + server.data['GriPwr'].value);
- netdata('END');
- }
-
- if(server.data['GriEgyTdy'].value !== null) {
- netdata('BEGIN sma_webbox_' + server.name + '.today ' + ((server.dt)?server.dt*1000:'').toString());
- netdata('SET GriEgyTdy = ' + Math.round(server.data['GriEgyTdy'].value * 1000));
- netdata('END');
- }
-
- if(server.data['GriEgyTot'].value !== null) {
- netdata('BEGIN sma_webbox_' + server.name + '.total ' + ((server.dt)?server.dt*1000:'').toString());
- netdata('SET GriEgyTot = ' + Math.round(server.data['GriEgyTot'].value * 1000));
- netdata('END');
- }
-
- server.error = 0;
- server.success = true;
- server.last_updated = t;
- }
- catch(e) {
- server.error = 501;
- server.success = false;
- server.failure_message = e.message;
- error('FAILED TO PARSE DATA: ' + e.message);
- }
-
- server.running = false;
- if(typeof(callback) === 'function')
- callback(server);
- });
- });
-
- server.request.on('error', function(e) {
- // do we have already handled this error?
- if(server.error !== 0)
- return;
-
- server.error = 502;
- server.success = false;
- server.running = false;
- server.failure_message = e.message;
- error('problem with request to ' + server.hostname + ': ' + e.message);
-
- if(typeof(callback) === 'function')
- callback(server);
- });
-
- // write data to request body
- server.request.write(postData);
- server.request.end();
-
- return true;
-}
-
-// get a message for the current error
-function getSMAError(server) {
- if(server.success) return 'OK';
-
- if(typeof(server.error) === 'undefined')
- return 'Not initialized';
-
- switch(server.error) {
- case 500: return 'Pre-fetch Error';
- case 501: return 'Failed to parse server response';
- case 502: return 'HTTP error while making request';
- case 504: return 'Failed to read response data';
- default: return 'Undefined Error';
- }
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-
-// if we don't have any servers to run
-// inform netdata we don't need to run on this machine
-if(!Servers.length) {
- error('No SMA webbox servers defined.');
- netdata('DISABLE');
- process.exit(1);
-}
-
-var started = new Date().getTime();
-function runServers() {
- var now = new Date().getTime();
- if(now - started > EXIT_AFTER_MS) {
- debug('We have to exit now. Netdata will restart us.');
- // FIXME
- // We should wait for any currently running requests to complete
- process.exit(0);
- }
-
- var len = Servers.length;
- while(len--) {
- getSMAData(Servers[len], function(server) {
- if(server.success)
- debug(' OK ' + server.name + ' time since last run: ' + server.dt + ' ms');
- else
- error(getSMAError(server) + server.name);
- });
- }
-
- setTimeout(runServers, 100);
-}
-
-runServers();
st = NULL;
}
else if(likely(hash == CHART_HASH && !strcmp(s, "CHART"))) {
+ int noname = 0;
st = NULL;
+ if((words[1]) != NULL && (words[2]) != NULL && strcmp(words[1], words[2]) == 0)
+ noname = 1;
+
char *type = words[1];
char *id = NULL;
if(likely(type)) {
int chart_type = RRDSET_TYPE_LINE;
if(unlikely(chart)) chart_type = rrdset_type_id(chart);
- if(unlikely(!name || !*name)) name = NULL;
+ if(unlikely(noname || !name || !*name || strcasecmp(name, "NULL") == 0 || strcasecmp(name, "(NULL)") == 0)) name = NULL;
if(unlikely(!family || !*family)) family = id;
if(unlikely(!category || !*category)) category = type;
NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
})
.fail(function() {
+ NETDATA.chartLibraries.easypiechart.enabled = false;
NETDATA.error(100, NETDATA.easypiechart_js);
})
.always(function() {
NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
})
.fail(function() {
+ NETDATA.chartLibraries.gauge.enabled = false;
NETDATA.error(100, NETDATA.gauge_js);
})
.always(function() {
<!-- <script> netdataServer = "http://box:19999"; </script> -->
<!-- load the dashboard manager - it will do the rest -->
- <script type="text/javascript" src="dashboard.js?v23"></script>
+ <script type="text/javascript" src="dashboard.js?v24"></script>
</head>
<body data-spy="scroll" data-target="#sidebar">