]> arthur.barton.de Git - netdata.git/commitdiff
added node.js plugin manager, implemented bind (named) 9.10+ JSON plugin, refactored...
authorCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Mon, 1 Feb 2016 23:43:28 +0000 (01:43 +0200)
committerCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Mon, 1 Feb 2016 23:43:28 +0000 (01:43 +0200)
13 files changed:
Makefile.am
configure.ac
node.d/Makefile.am [new file with mode: 0644]
node.d/README.md [new file with mode: 0644]
node.d/named.node.js [new file with mode: 0755]
node.d/node_modules/extend.js [new file with mode: 0644]
node.d/node_modules/netdata.js [new file with mode: 0755]
node.d/sma_webbox.node.js [new file with mode: 0755]
plugins.d/Makefile.am
plugins.d/sma_webbox.plugin [deleted file]
src/plugins_d.c
web/dashboard.js
web/index.html

index 8e14db9f139270828e6fceb43234f6bd720709e4..59e7451559bb7da22daa404a5de6b09bef9976e5 100644 (file)
@@ -28,6 +28,7 @@ EXTRA_DIST = \
 
 SUBDIRS = \
        charts.d \
+       node.d \
        conf.d \
        plugins.d \
        src \
index 49d59918a705204b5cf1c925552d3cf11deb08a2..7fb4815605f4556660cab7862d6bb0be9dd0facf 100644 (file)
@@ -141,6 +141,7 @@ AC_DEFINE_UNQUOTED([NETDATA_USER], ["${with_user}"], [use this user to drop priv
 
 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"])
@@ -156,6 +157,7 @@ AC_SUBST([OPTIONAL_ZLIB_LIBS])
 AC_CONFIG_FILES([
        Makefile
        charts.d/Makefile
+       node.d/Makefile
        conf.d/Makefile
        netdata.spec
        plugins.d/Makefile
diff --git a/node.d/Makefile.am b/node.d/Makefile.am
new file mode 100644 (file)
index 0000000..3436f73
--- /dev/null
@@ -0,0 +1,12 @@
+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)
diff --git a/node.d/README.md b/node.d/README.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/node.d/named.node.js b/node.d/named.node.js
new file mode 100755 (executable)
index 0000000..82728f4
--- /dev/null
@@ -0,0 +1,462 @@
+'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
diff --git a/node.d/node_modules/extend.js b/node.d/node_modules/extend.js
new file mode 100644 (file)
index 0000000..0fdd8be
--- /dev/null
@@ -0,0 +1,87 @@
+// 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;
+};
diff --git a/node.d/node_modules/netdata.js b/node.d/node_modules/netdata.js
new file mode 100755 (executable)
index 0000000..1e594ba
--- /dev/null
@@ -0,0 +1,538 @@
+'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
diff --git a/node.d/sma_webbox.node.js b/node.d/sma_webbox.node.js
new file mode 100755 (executable)
index 0000000..7cc6ced
--- /dev/null
@@ -0,0 +1,241 @@
+'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;
index b2d336a6ed9e3c7202f98b3496e71bb0c11d58d6..a89ee4cdd03968868785cfce4d0cc9ead742444f 100644 (file)
@@ -10,7 +10,7 @@ dist_plugins_DATA = \
 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)
diff --git a/plugins.d/sma_webbox.plugin b/plugins.d/sma_webbox.plugin
deleted file mode 100755 (executable)
index 5aa5b99..0000000
+++ /dev/null
@@ -1,381 +0,0 @@
-#!/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();
index 767ca27c062665320a052c9be111b5409ddd9fb3..c07e592ea27fd07559809314f16c27716582104b 100755 (executable)
@@ -228,8 +228,12 @@ void *pluginsd_worker_thread(void *arg)
                                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)) {
@@ -262,7 +266,7 @@ void *pluginsd_worker_thread(void *arg)
                                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;
 
index 5ec0390124db50bbdd8e848e7727f8ce355c7edf..8f2a66baadc0ef919023adb45cbbbb94cdedd3db 100755 (executable)
                                        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() {
index 0151b85fbb0e1b2c9b38d1bc9e8ff1655c598ccc..d0b7b115af0fd4cf3f1bf969a1b33e3e0716e75a 100755 (executable)
        <!-- <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">