@cryptotaxi247 / netdata-1 / commits / aa0e9e779

added node.js plugin manager, implemented bind (named) 9.10+ JSON plugin, refactored sma_webbox for the new node.js manager

Costa Tsaousis (ktsaou) committed Feb 2, 2016 at 01:43 UTC aa0e9e779065dce04a2acfabbf6de128aff33f50
13 files changed +1352 -384
Makefile.am
+1
@@ -28,6 +28,7 @@ EXTRA_DIST = \
28
29 SUBDIRS = \
30 charts.d \
31 + node.d \
32 conf.d \
33 plugins.d \
34 src \
configure.ac
+2
@@ -141,6 +141,7 @@ AC_DEFINE_UNQUOTED([NETDATA_USER], ["${with_user}"], [use this user to drop priv
141
142 AC_SUBST([cachedir], ["\$(localstatedir)/cache/netdata"])
143 AC_SUBST([chartsdir], ["\$(libexecdir)/netdata/charts.d"])
144 +AC_SUBST([nodedir], ["\$(libexecdir)/netdata/node.d"])
145 AC_SUBST([configdir], ["\$(sysconfdir)/netdata"])
146 AC_SUBST([logdir], ["\$(localstatedir)/log/netdata"])
147 AC_SUBST([pluginsdir], ["\$(libexecdir)/netdata/plugins.d"])
@@ -156,6 +157,7 @@ AC_SUBST([OPTIONAL_ZLIB_LIBS])
157 AC_CONFIG_FILES([
158 Makefile
159 charts.d/Makefile
160 + node.d/Makefile
161 conf.d/Makefile
162 netdata.spec
163 plugins.d/Makefile
node.d/Makefile.am new
+12
@@ -0,0 +1,12 @@
1 +MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
2 +
3 +dist_node_SCRIPTS = \
4 + README.md \
5 + named.node.js \
6 + $(NULL)
7 +
8 +nodemodulesdir=$(nodedir)/node_modules
9 +dist_nodemodules_DATA = \
10 + node_modules/netdata.js \
11 + node_modules/extend.js \
12 + $(NULL)
node.d/README.md
node.d/named.node.js new
+462
@@ -0,0 +1,462 @@
1 +'use strict';
2 +
3 +// collect statistics from bind (named) v9.10+
4 +//
5 +// bind statistics documentation at:
6 +// https://ftp.isc.org/isc/bind/9.10.3/doc/arm/Bv9ARM.ch06.html#statistics
7 +
8 +// example configuration in /etc/netdata/named.conf
9 +// the module supports auto-detection if bind is running in localhost
10 +
11 +/*
12 +{
13 + "enable_autodetect": true,
14 + "update_every": 5,
15 + "servers": [
16 + {
17 + "name": "bind1",
18 + "url": "http://127.0.0.1:8888/json/v1",
19 + "update_every": 1
20 + },
21 + {
22 + "name": "bind2",
23 + "url": "http://10.1.2.3:8888/json/v1",
24 + "update_every": 2
25 + }
26 + ]
27 +}
28 +*/
29 +
30 +// the following is the bind named.conf configuration required
31 +
32 +/*
33 +statistics-channels {
34 + inet 127.0.0.1 port 8888 allow { 127.0.0.1; };
35 +};
36 +*/
37 +
38 +var url = require('url');
39 +var http = require('http');
40 +var netdata = require('netdata');
41 +
42 +if(netdata.options.DEBUG === true) netdata.debug('loaded ' + __filename + ' plugin');
43 +
44 +var named = {
45 + name: __filename,
46 + enable_autodetect: true,
47 + update_every: 1000,
48 +
49 + charts: {},
50 +
51 + chartFromMembersCreate: function(service, obj, id, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor) {
52 + var chart = {
53 + id: id, // the unique id of the chart
54 + name: '', // the unique name of the chart
55 + title: service.name + ' ' + title_suffix, // the title of the chart
56 + units: units, // the units of the chart dimensions
57 + family: family_prefix + '_' + service.name, // the family of the chart
58 + category: category_prefix + '_' + service.name, // the category of the chart
59 + type: type, // the type of the chart
60 + priority: priority, // the priority relative to others in the same family and category
61 + update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
62 + dimensions: {}
63 + }
64 +
65 + var found = 0;
66 + for(var x in obj) {
67 + if(typeof(obj[x]) !== 'undefined' && obj[x] !== 0) {
68 + found++;
69 + chart.dimensions[x] = {
70 + id: x, // the unique id of the dimension
71 + name: x, // the name of the dimension
72 + algorithm: algorithm, // the id of the netdata algorithm
73 + multiplier: multiplier, // the multiplier
74 + divisor: divisor, // the divisor
75 + hidden: false // is hidden (boolean)
76 + }
77 + }
78 + }
79 +
80 + if(found === false)
81 + return null;
82 +
83 + chart = service.chart(id, chart);
84 + this.charts[id] = chart;
85 + return chart;
86 + },
87 +
88 + chartFromMembers: function(service, obj, id_suffix, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor) {
89 + var id = 'named_' + service.name + '.' + id_suffix;
90 + var chart = this.charts[id];
91 +
92 + if(typeof chart === 'undefined') {
93 + chart = this.chartFromMembersCreate(service, obj, id, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor);
94 + if(chart === null) return false;
95 + }
96 + else {
97 + // check if we need to re-generate the chart
98 + for(var x in obj) {
99 + if(typeof(chart.dimensions[x]) === 'undefined') {
100 + chart = this.chartFromMembersCreate(service, obj, id, title_suffix, units, family_prefix, category_prefix, type, priority, algorithm, multiplier, divisor);
101 + if(chart === null) return false;
102 + break;
103 + }
104 + }
105 + }
106 +
107 + var found = 0;
108 + service.begin(chart);
109 + for(var x in obj) {
110 + if(typeof(chart.dimensions[x]) !== 'undefined') {
111 + found++;
112 + service.set(x, obj[x]);
113 + }
114 + }
115 + service.end();
116 +
117 + if(found > 0) return true;
118 + return false;
119 + },
120 +
121 + // an index to map values to different charts
122 + lookups: {
123 + nsstats: {},
124 + resolver_stats: {},
125 + numfetch: {}
126 + },
127 +
128 + processResponse: function(service, data) {
129 + if(data !== null) {
130 + var r = JSON.parse(data);
131 +
132 + if(service.added !== true)
133 + netdata.serviceAdd(service);
134 +
135 + if(typeof r.nsstats !== 'undefined') {
136 + // we split the nsstats object to several others
137 + var global_requests = {}, global_requests_enable = false;
138 + var global_failures = {}, global_failures_enable = false;
139 + var global_failures_detail = {}, global_failures_detail_enable = false;
140 + var global_updates = {}, global_updates_enable = false;
141 + var protocol_queries = {}, protocol_queries_enable = false;
142 + var global_queries = {}, global_queries_enable = false;
143 + var global_queries_success = {}, global_queries_success_enable = false;
144 + var default_enable = false;
145 + var RecursClients = 0;
146 +
147 + // RecursClients is an absolute value
148 + if(typeof r.nsstats['RecursClients'] !== 'undefined') {
149 + RecursClients = r.nsstats['RecursClients'];
150 + delete r.nsstats['RecursClients'];
151 + }
152 +
153 + for( var x in r.nsstats ) {
154 + // we maintain an index of the values found
155 + // mapping them to objects splitted
156 +
157 + var look = named.lookups.nsstats[x];
158 + if(typeof look === 'undefined') {
159 + // a new value, not found in the index
160 + // index it:
161 + if(x === 'Requestv4') {
162 + named.lookups.nsstats[x] = {
163 + name: 'IPv4',
164 + type: 'global_requests'
165 + };
166 + }
167 + else if(x === 'Requestv6') {
168 + named.lookups.nsstats[x] = {
169 + name: 'IPv6',
170 + type: 'global_requests'
171 + };
172 + }
173 + else if(x === 'QryFailure') {
174 + named.lookups.nsstats[x] = {
175 + name: 'failures',
176 + type: 'global_failures'
177 + };
178 + }
179 + else if(x === 'QryUDP') {
180 + named.lookups.nsstats[x] = {
181 + name: 'UDP',
182 + type: 'protocol_queries'
183 + };
184 + }
185 + else if(x === 'QryTCP') {
186 + named.lookups.nsstats[x] = {
187 + name: 'TCP',
188 + type: 'protocol_queries'
189 + };
190 + }
191 + else if(x === 'QrySuccess') {
192 + named.lookups.nsstats[x] = {
193 + name: 'queries',
194 + type: 'global_queries_success'
195 + };
196 + }
197 + else if(x.match(/QryRej$/) !== null) {
198 + named.lookups.nsstats[x] = {
199 + name: x,
200 + type: 'global_failures_detail'
201 + };
202 + }
203 + else if(x.match(/^Qry/) !== null) {
204 + named.lookups.nsstats[x] = {
205 + name: x,
206 + type: 'global_queries'
207 + };
208 + }
209 + else if(x.match(/^Update/) !== null) {
210 + named.lookups.nsstats[x] = {
211 + name: x,
212 + type: 'global_updates'
213 + };
214 + }
215 + else {
216 + // values not mapped, will remain
217 + // in the default map
218 + named.lookups.nsstats[x] = {
219 + name: x,
220 + type: 'default'
221 + };
222 + }
223 +
224 + look = named.lookups.nsstats[x];
225 + // netdata.error('lookup nsstats value: ' + x + ' >>> ' + named.lookups.nsstats[x].type);
226 + }
227 +
228 + switch(look.type) {
229 + case 'global_requests': global_requests[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_requests_enable = true; break;
230 + case 'global_queries': global_queries[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_queries_enable = true; break;
231 + case 'global_queries_success': global_queries_success[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_queries_success_enable = true; break;
232 + case 'global_updates': global_updates[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_updates_enable = true; break;
233 + case 'protocol_queries': protocol_queries[look.name] = r.nsstats[x]; delete r.nsstats[x]; protocol_queries_enable = true; break;
234 + case 'global_failures': global_failures[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_failures_enable = true; break;
235 + case 'global_failures_detail': global_failures_detail[look.name] = r.nsstats[x]; delete r.nsstats[x]; global_failures_detail_enable = true; break;
236 + default: default_enable = true; break;
237 + }
238 + }
239 +
240 + if(global_requests_enable == true)
241 + 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);
242 +
243 + if(global_queries_success_enable == true)
244 + 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);
245 +
246 + if(protocol_queries_enable == true)
247 + 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);
248 +
249 + if(global_queries_enable == true)
250 + 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);
251 +
252 + if(global_updates_enable == true)
253 + 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);
254 +
255 + if(global_failures_enable == true)
256 + 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);
257 +
258 + if(global_failures_detail_enable == true)
259 + 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);
260 +
261 + if(default_enable === true)
262 + 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);
263 +
264 + // RecursClients chart
265 + {
266 + var id = 'named_' + service.name + '.recursive_clients';
267 + var chart = named.charts[id];
268 +
269 + if(typeof chart === 'undefined') {
270 + chart = {
271 + id: id, // the unique id of the chart
272 + name: '', // the unique name of the chart
273 + title: service.name + ' Bind, Current Recursive Clients', // the title of the chart
274 + units: 'clients', // the units of the chart dimensions
275 + family: 'named', // the family of the chart
276 + category: 'named', // the category of the chart
277 + type: netdata.chartTypes.line, // the type of the chart
278 + priority: 150, // the priority relative to others in the same family and category
279 + update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
280 + dimensions: {
281 + 'clients': {
282 + id: 'clients', // the unique id of the dimension
283 + name: '', // the name of the dimension
284 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
285 + multiplier: 1, // the multiplier
286 + divisor: 1, // the divisor
287 + hidden: false // is hidden (boolean)
288 + }
289 + }
290 + };
291 +
292 + chart = service.chart(id, chart);
293 + named.charts[id] = chart;
294 + }
295 +
296 + service.begin(chart);
297 + service.set('clients', RecursClients);
298 + service.end();
299 + }
300 + }
301 +
302 + if(typeof r.opcodes !== 'undefined')
303 + 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);
304 +
305 + if(typeof r.qtypes !== 'undefined')
306 + 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);
307 +
308 + if(typeof r.views !== 'undefined') {
309 + for( var x in r.views ) {
310 + var resolver = r.views[x].resolver;
311 +
312 + if(typeof resolver !== 'undefined') {
313 + if(typeof resolver.stats !== 'undefined') {
314 + var NumFetch = 0;
315 + var key = service.name + '.' + x;
316 + var default_enable = false;
317 + var rtt = {}, rtt_enable = false;
318 +
319 + // NumFetch is an absolute value
320 + if(typeof resolver.stats['NumFetch'] !== 'undefined') {
321 + named.lookups.numfetch[key] = true;
322 + NumFetch = resolver.stats['NumFetch'];
323 + delete resolver.stats['NumFetch'];
324 + }
325 + if(typeof resolver.stats['BucketSize'] !== 'undefined') {
326 + delete resolver.stats['BucketSize'];
327 + }
328 +
329 + // split the QryRTT* from the main chart
330 + for( var y in resolver.stats ) {
331 + // we maintain an index of the values found
332 + // mapping them to objects splitted
333 +
334 + var look = named.lookups.resolver_stats[y];
335 + if(typeof look === 'undefined') {
336 + if(y.match(/^QryRTT/) !== null) {
337 + named.lookups.resolver_stats[y] = {
338 + name: y,
339 + type: 'rtt'
340 + };
341 + }
342 + else {
343 + named.lookups.resolver_stats[y] = {
344 + name: y,
345 + type: 'default'
346 + };
347 + }
348 +
349 + look = named.lookups.resolver_stats[y];
350 + // netdata.error('lookup resolver stats value: ' + y + ' >>> ' + look.type);
351 + }
352 +
353 + switch(look.type) {
354 + case 'rtt': rtt[look.name] = resolver.stats[y]; delete resolver.stats[y]; rtt_enable = true; break;
355 + default: default_enable = true; break;
356 + }
357 + }
358 +
359 + if(rtt_enable)
360 + 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);
361 +
362 + if(default_enable)
363 + 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);
364 +
365 + // NumFetch chart
366 + if(typeof named.lookups.numfetch[key] !== 'undefined') {
367 + var id = 'named_' + service.name + '.view_resolver_numfetch_' + x;
368 + var chart = named.charts[id];
369 +
370 + if(typeof chart === 'undefined') {
371 + chart = {
372 + id: id, // the unique id of the chart
373 + name: '', // the unique name of the chart
374 + title: service.name + ' Bind, ' + x + ' View, Resolver Active Queries', // the title of the chart
375 + units: 'queries', // the units of the chart dimensions
376 + family: 'named', // the family of the chart
377 + category: 'named', // the category of the chart
378 + type: netdata.chartTypes.line, // the type of the chart
379 + priority: 5000, // the priority relative to others in the same family and category
380 + update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
381 + dimensions: {
382 + 'queries': {
383 + id: 'queries', // the unique id of the dimension
384 + name: '', // the name of the dimension
385 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
386 + multiplier: 1, // the multiplier
387 + divisor: 1, // the divisor
388 + hidden: false // is hidden (boolean)
389 + }
390 + }
391 + };
392 +
393 + chart = service.chart(id, chart);
394 + named.charts[id] = chart;
395 + }
396 +
397 + service.begin(chart);
398 + service.set('queries', NumFetch);
399 + service.end();
400 + }
401 + }
402 +
403 + if(typeof resolver.qtypes !== 'undefined')
404 + 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);
405 + }
406 + }
407 + }
408 + }
409 + },
410 +
411 + // module.serviceExecute()
412 + // this function is called only from this module
413 + // its purpose is to prepare the request and call
414 + // netdata.serviceExecute()
415 + serviceExecute: function(name, a_url, update_every) {
416 + if(netdata.options.DEBUG === true) netdata.debug(this.name + ': ' + name + ': url: ' + a_url + ', update_every: ' + update_every);
417 + netdata.serviceExecute({
418 + name: name,
419 + request: netdata.requestFromURL(a_url),
420 + update_every: update_every,
421 + added: false,
422 + enabled: true,
423 + module: this
424 + }, this.processResponse);
425 + },
426 +
427 + configure: function(config) {
428 + var added = 0;
429 +
430 + if(this.enable_autodetect === true) {
431 + this.serviceExecute('local', 'http://localhost:8888/json/v1', this.update_every);
432 + added++;
433 + }
434 +
435 + if(typeof(config.servers) !== 'undefined') {
436 + var len = config.servers.length;
437 + while(len--) {
438 + if(typeof config.servers[len].update_every === 'undefined')
439 + config.servers[len].update_every = this.update_every;
440 + else
441 + config.servers[len].update_every = config.servers[len].update_every * 1000;
442 +
443 + this.serviceExecute(config.servers[len].name, config.servers[len].url, config.servers[len].update_every);
444 + added++;
445 + }
446 + }
447 +
448 + return added;
449 + },
450 +
451 + // module.update()
452 + // this is called repeatidly to collect data, by calling
453 + // netdata.serviceExecute()
454 + update: function(service, callback) {
455 + netdata.serviceExecute(service, function(serv, data) {
456 + service.module.processResponse(serv, data);
457 + callback();
458 + });
459 + },
460 +};
461 +
462 +module.exports = named;
node.d/node_modules/extend.js new
+87
@@ -0,0 +1,87 @@
1 +// https://github.com/justmoon/node-extend
2 +
3 +'use strict';
4 +
5 +var hasOwn = Object.prototype.hasOwnProperty;
6 +var toStr = Object.prototype.toString;
7 +
8 +var isArray = function isArray(arr) {
9 + if (typeof Array.isArray === 'function') {
10 + return Array.isArray(arr);
11 + }
12 +
13 + return toStr.call(arr) === '[object Array]';
14 +};
15 +
16 +var isPlainObject = function isPlainObject(obj) {
17 + if (!obj || toStr.call(obj) !== '[object Object]') {
18 + return false;
19 + }
20 +
21 + var hasOwnConstructor = hasOwn.call(obj, 'constructor');
22 + var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
23 + // Not own constructor property must be Object
24 + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
25 + return false;
26 + }
27 +
28 + // Own properties are enumerated firstly, so to speed up,
29 + // if last one is own, then all properties are own.
30 + var key;
31 + for (key in obj) { /**/ }
32 +
33 + return typeof key === 'undefined' || hasOwn.call(obj, key);
34 +};
35 +
36 +module.exports = function extend() {
37 + var options, name, src, copy, copyIsArray, clone;
38 + var target = arguments[0];
39 + var i = 1;
40 + var length = arguments.length;
41 + var deep = false;
42 +
43 + // Handle a deep copy situation
44 + if (typeof target === 'boolean') {
45 + deep = target;
46 + target = arguments[1] || {};
47 + // skip the boolean and the target
48 + i = 2;
49 + } else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) {
50 + target = {};
51 + }
52 +
53 + for (; i < length; ++i) {
54 + options = arguments[i];
55 + // Only deal with non-null/undefined values
56 + if (options != null) {
57 + // Extend the base object
58 + for (name in options) {
59 + src = target[name];
60 + copy = options[name];
61 +
62 + // Prevent never-ending loop
63 + if (target !== copy) {
64 + // Recurse if we're merging plain objects or arrays
65 + if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
66 + if (copyIsArray) {
67 + copyIsArray = false;
68 + clone = src && isArray(src) ? src : [];
69 + } else {
70 + clone = src && isPlainObject(src) ? src : {};
71 + }
72 +
73 + // Never move original objects, clone them
74 + target[name] = extend(deep, clone, copy);
75 +
76 + // Don't bring in undefined values
77 + } else if (typeof copy !== 'undefined') {
78 + target[name] = copy;
79 + }
80 + }
81 + }
82 + }
83 + }
84 +
85 + // Return the modified object
86 + return target;
87 +};
node.d/node_modules/netdata.js new
+538
@@ -0,0 +1,538 @@
1 +'use strict';
2 +
3 +var url = require('url');
4 +var http = require('http');
5 +var util = require('util');
6 +
7 +/*
8 +var netdata = require('netdata');
9 +
10 +var example_chart = {
11 + id: 'id', // the unique id of the chart
12 + name: 'name', // the name of the chart
13 + title: 'title', // the title of the chart
14 + units: 'units', // the units of the chart dimensions
15 + family: 'family', // the family of the chart
16 + category: 'category', // the category of the chart
17 + type: netdata.chartTypes.line, // the type of the chart
18 + priority: 0, // the priority relative to others in the same family and category
19 + update_every: 1, // the expected update frequency of the chart
20 + dimensions: {
21 + 'dim1': {
22 + id: 'dim1', // the unique id of the dimension
23 + name: 'name', // the name of the dimension
24 + algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm
25 + multiplier: 1, // the multiplier
26 + divisor: 1, // the divisor
27 + hidden: false, // is hidden (boolean)
28 + },
29 + 'dim2': {
30 + id: 'dim2', // the unique id of the dimension
31 + name: 'name', // the name of the dimension
32 + algorithm: 'absolute', // the id of the netdata algorithm
33 + multiplier: 1, // the multiplier
34 + divisor: 1, // the divisor
35 + hidden: false, // is hidden (boolean)
36 + }
37 + // add as many dimensions as needed
38 + }
39 +};
40 +*/
41 +
42 +var netdata = {
43 + options: {
44 + filename: __filename,
45 + DEBUG: false,
46 + update_every: 1000,
47 + },
48 +
49 + chartAlgorithms: {
50 + incremental: 'incremental',
51 + absolute: 'absolute',
52 + percentage_of_absolute_row: 'percentage-of-absolute-row',
53 + percentage_of_incremental_row: 'percentage-of-incremental-row'
54 + },
55 +
56 + chartTypes: {
57 + line: 'line',
58 + area: 'area',
59 + stacked: 'stacked'
60 + },
61 +
62 + services: new Array(),
63 + modules_configuring: 0,
64 + charts: {},
65 +
66 + stringify: function(obj) {
67 + return util.inspect(obj, {depth: 10});
68 + },
69 +
70 + // show debug info, if debug is enabled
71 + debug: function(msg) {
72 + if(this.options.DEBUG === true) {
73 + var now = new Date();
74 + console.error(now.toString() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
75 + }
76 + },
77 +
78 + // log an error
79 + error: function(msg) {
80 + var now = new Date();
81 + console.error(now.toString() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
82 + },
83 +
84 + // send data to netdata
85 + send: function(msg) {
86 + console.log(msg.toString());
87 + },
88 +
89 + serviceAdd: function(service) {
90 + if(service.added !== true) {
91 + service.updates = 0;
92 + service.enabled = true;
93 + service.added = true;
94 + service.running = false;
95 + service.started = 0;
96 + service.ended = 0;
97 + service._current_chart = null; // the current chart we work on
98 + service._queue = '';
99 + service.queue = function(txt) {
100 + this._queue += txt + '\n';
101 + };
102 +
103 + service._send_chart_to_netdata = function(chart) {
104 + // internal function to send a chart to netdata
105 + this.queue('CHART "' + chart.id + '" "' + chart.name + '" "' + chart.title + '" "' + chart.units + '" "' + chart.family + '" "' + chart.category + '" "' + chart.type + '" ' + chart.priority.toString() + ' ' + chart.update_every.toString());
106 +
107 + for(var dim in chart.dimensions) {
108 + var d = chart.dimensions[dim];
109 +
110 + this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true)?'hidden':'').toString());
111 + d._created = true;
112 + d._updated = false;
113 + }
114 +
115 + chart._created = true;
116 + chart._updated = false;
117 + };
118 +
119 + // begin data collection for a chart
120 + service.begin = function(chart) {
121 + if(this._current_chart !== null && this._current_chart !== chart) {
122 + netdata.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.');
123 + this.end();
124 + }
125 +
126 + if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] != chart) {
127 + netdata.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.');
128 + return false;
129 + }
130 +
131 + if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id);
132 + this._current_chart = chart;
133 + this._current_chart._began = true;
134 +
135 + if(this._current_chart._dimensions_count !== 0) {
136 + if(this._current_chart._created === false || this._current_chart._updated === true)
137 + this._send_chart_to_netdata(this._current_chart);
138 +
139 + var now = this.ended;
140 + this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString());
141 + }
142 + // else netdata.error('Called begin() for chart ' + chart.id + ' which is empty.');
143 +
144 + this._current_chart._last_updated = now;
145 + this._current_chart._began = true;
146 + this._current_chart._counter++;
147 +
148 + return true;
149 + };
150 +
151 + // set a collected value for a chart
152 + // we do most things on the first value we attempt to set
153 + service.set = function(dimension, value) {
154 + if(this._current_chart === null) {
155 + netdata.error('Called set(' + dimension + ', ' + value + ') without an open chart.');
156 + return false;
157 + }
158 +
159 + if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') {
160 + netdata.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".');
161 + return false;
162 + }
163 +
164 + if(this._current_chart._dimensions_count !== 0)
165 + this.queue('SET ' + dimension + ' = ' + value);
166 +
167 + return true;
168 + };
169 +
170 + // end data collection for the current chart - after calling begin()
171 + service.end = function() {
172 + if(this._current_chart !== null && this._current_chart._began === false) {
173 + netdata.error('Called end() without an open chart.');
174 + return false;
175 + }
176 +
177 + if(this._current_chart._dimensions_count !== 0) {
178 + this.queue('END');
179 + netdata.send(this._queue);
180 + }
181 +
182 + this._queue = '';
183 + this._current_chart._began = false;
184 + if(netdata.options.DEBUG === true) netdata.debug('committed chart ' + this._current_chart.id);
185 + this._current_chart = null;
186 + return true;
187 + };
188 +
189 + // discard the collected values for the current chart - after calling begin()
190 + service.flush = function() {
191 + if(this._current_chart === null || this._current_chart._began === false) {
192 + netdata.error('Called flush() without an open chart.');
193 + return false;
194 + }
195 +
196 + this._queue = '';
197 + this._current_chart._began = false;
198 + this._current_chart = null;
199 + return true;
200 + };
201 +
202 + // create a netdata chart
203 + service.chart = function(id, chart) {
204 + if(typeof(netdata.charts[id]) === 'undefined') {
205 + netdata.charts[id] = {
206 + _created: false,
207 + _updated: false,
208 + _began: false,
209 + _counter: 0,
210 + _last_updated: 0,
211 + _dimensions_count: 0,
212 + id: id,
213 + name: id,
214 + title: 'untitled chart',
215 + units: 'a unit',
216 + family: id,
217 + category: id,
218 + type: netdata.chartTypes.line,
219 + priority: 0,
220 + update_every: netdata.options.update_every,
221 + dimensions: {}
222 + };
223 + }
224 +
225 + var c = netdata.charts[id];
226 +
227 + if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) {
228 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its name');
229 + c.name = chart.name;
230 + c._updated = true;
231 + }
232 +
233 + if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) {
234 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its title');
235 + c.title = chart.title;
236 + c._updated = true;
237 + }
238 +
239 + if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) {
240 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its units');
241 + c.units = chart.units;
242 + c._updated = true;
243 + }
244 +
245 + if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) {
246 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its family');
247 + c.family = chart.family;
248 + c._updated = true;
249 + }
250 +
251 + if(typeof(chart.category) !== 'undefined' && chart.category !== c.category) {
252 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its category');
253 + c.category = chart.category;
254 + c._updated = true;
255 + }
256 +
257 + if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) {
258 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its type');
259 + c.type = chart.type;
260 + c._updated = true;
261 + }
262 +
263 + if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) {
264 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its priority');
265 + c.priority = chart.priority;
266 + c._updated = true;
267 + }
268 +
269 + if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) {
270 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every');
271 + c.update_every = chart.update_every;
272 + c._updated = true;
273 + }
274 +
275 + if(typeof(chart.dimensions) !== 'undefined') {
276 + for(var x in chart.dimensions) {
277 + if(typeof(c.dimensions[x]) === 'undefined') {
278 + c._dimensions_count++;
279 +
280 + c.dimensions[x] = {
281 + _created: false,
282 + _updated: false,
283 + id: x, // the unique id of the dimension
284 + name: x, // the name of the dimension
285 + algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm
286 + multiplier: 1, // the multiplier
287 + divisor: 1, // the divisor
288 + hidden: false, // is hidden (boolean)
289 + };
290 +
291 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x);
292 + c._updated = true;
293 + }
294 +
295 + var dim = chart.dimensions[x];
296 + var d = c.dimensions[x];
297 +
298 + if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) {
299 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name');
300 + d.name = dim.name;
301 + d._updated = true;
302 + }
303 +
304 + if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) {
305 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm);
306 + d.algorithm = dim.algorithm;
307 + d._updated = true;
308 + }
309 +
310 + if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) {
311 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier');
312 + d.multiplier = dim.multiplier;
313 + d._updated = true;
314 + }
315 +
316 + if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) {
317 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor');
318 + d.divisor = dim.divisor;
319 + d._updated = true;
320 + }
321 +
322 + if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) {
323 + if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status');
324 + d.hidden = dim.hidden;
325 + d._updated = true;
326 + }
327 +
328 + if(d._updated) c._updated = true;
329 + }
330 + }
331 +
332 + if(netdata.options.DEBUG === true) netdata.debug(netdata.charts);
333 + return netdata.charts[id];
334 + };
335 +
336 + this.services.push(service);
337 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': service added.');
338 + }
339 + },
340 +
341 + serviceRun: function(service) {
342 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': starting data collection...');
343 + service.running = true;
344 + service.started = new Date().getTime();
345 + service.updates++;
346 +
347 + service.module.update(service, function() {
348 + service.ended = new Date().getTime();
349 + service.duration = service.ended - service.started;
350 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.');
351 + service.running = false;
352 + });
353 + },
354 +
355 + runAllServices: function() {
356 + if(netdata.options.DEBUG === true) netdata.debug('runAllServices()');
357 +
358 + var now = new Date().getTime();
359 + var len = netdata.services.length;
360 + while(len--) {
361 + var service = netdata.services[len];
362 +
363 + if(service.enabled === false || service.running === true) continue;
364 + if(now - service.ended < service.update_every) continue;
365 +
366 + netdata.serviceRun(service);
367 + }
368 +
369 + setTimeout(netdata.runAllServices, 100);
370 + },
371 +
372 + start: function() {
373 + if(netdata.options.DEBUG === true) this.debug('started, services:');
374 +
375 + if(this.services.length === 0) {
376 + this.disableNodePlugin();
377 + process.exit(1);
378 + }
379 + else this.runAllServices();
380 + },
381 +
382 + // disable the whole node.js plugin
383 + disableNodePlugin: function() {
384 + this.send('DISABLE');
385 + process.exit(1);
386 + },
387 +
388 + requestFromParams: function(protocol, hostname, port, path, method) {
389 + return {
390 + protocol: protocol,
391 + hostname: hostname,
392 + port: port,
393 + path: path,
394 + //family: 4,
395 + method: method,
396 + headers: {
397 + 'Content-Type': 'application/x-www-form-urlencoded',
398 + 'Connection': 'keep-alive'
399 + },
400 + agent: new http.Agent({
401 + keepAlive: true,
402 + keepAliveMsecs: netdata.options.update_every,
403 + maxSockets: 2, // it must be 2 to work
404 + maxFreeSockets: 1
405 + })
406 + };
407 + },
408 +
409 + requestFromURL: function(a_url) {
410 + var u = url.parse(a_url);
411 + return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET');
412 + },
413 +
414 + processResponse: function(service, data, callback) {
415 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': processing response...');
416 +
417 + callback(service, data);
418 +
419 + service.module.running--;
420 + if(service.module.running <= 0) {
421 + service.module.running = 0;
422 +
423 + // check if we run under configure
424 + if(service.module.configure_callback !== null) {
425 + if(netdata.options.DEBUG === true) this.debug(service.module.name + ': configuration finish callback called from processResponse().');
426 + var ccallback = service.module.configure_callback;
427 + service.module.configure_callback = null;
428 + ccallback();
429 + }
430 + }
431 + },
432 +
433 + getResponse: function(service, response, callback) {
434 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got response...');
435 +
436 + var end = false;
437 + var data = '';
438 + response.setEncoding('utf8');
439 +
440 + if(response.statusCode !== 200) {
441 + if(end === false) {
442 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got HTTP code ' + response.statusCode + ', failed to get data.');
443 + end = true;
444 + netdata.processResponse(service, null, callback);
445 + }
446 + }
447 +
448 + response.on('data', function(chunk) {
449 + if(end === false) data += chunk;
450 + });
451 +
452 + response.on('error', function() {
453 + if(end === false) {
454 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': Read error, failed to get data.');
455 + end = true;
456 + netdata.processResponse(service, null, callback);
457 + }
458 + });
459 +
460 + response.on('end', function() {
461 + if(end === false) {
462 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.');
463 + end = true;
464 + netdata.processResponse(service, data, callback);
465 + }
466 + });
467 + },
468 +
469 + serviceExecute: function(service, callback) {
470 + service.module.running++;
471 +
472 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': making request: ' + netdata.stringify(service.request));
473 + var req = http.request(service.request, function(response) {
474 + if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': request done.');
475 + netdata.getResponse(service, response, callback);
476 + });
477 +
478 + req.on('error', function(e) {
479 + netdata.error(service.module.name + ': ' + service.name + ': failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
480 + netdata.processResponse(service, null, callback);
481 + });
482 +
483 + // write data to request body
484 + if(typeof service.postData !== 'undefined' && service.request.method === 'POST')
485 + req.write(service.postData);
486 +
487 + req.end();
488 + },
489 +
490 + configure: function(module, config, callback) {
491 + if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...');
492 +
493 + module.running = 0;
494 + module.update_every = this.options.update_every;
495 +
496 + if(typeof config.update_every !== 'undefined')
497 + module.update_every = config.update_every * 1000;
498 +
499 + module.enable_autodetect = (config.enable_autodetect)?true:false;
500 +
501 + if(typeof(callback) === 'function')
502 + module.configure_callback = callback;
503 + else
504 + module.configure_callback = null;
505 +
506 + var added = module.configure(config);
507 +
508 + if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.');
509 +
510 + if(module.configure_callback !== null && added === 0) {
511 + if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().');
512 + module.configure_callback = null;
513 + callback();
514 + }
515 +
516 + return added;
517 + }
518 +
519 +};
520 +
521 +if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from: ' + __filename);
522 +module.exports = netdata;
523 +
524 +/*
525 +var test1 = netdata.chart('test1', { name: 'test name', dimensions: { dim1: {}}});
526 +netdata.begin(test1);
527 +netdata.set('dim1', 1);
528 +netdata.end();
529 +netdata.begin(test1);
530 +netdata.set('dim1', 2);
531 +netdata.end();
532 +netdata.begin(test1);
533 +netdata.set('dim1', 3);
534 +netdata.end();
535 +netdata.begin(test1);
536 +netdata.set('dim1', 4);
537 +netdata.end();
538 +*/
node.d/sma_webbox.node.js new
+241
@@ -0,0 +1,241 @@
1 +'use strict';
2 +
3 +// This program will connect to one or more SMA Sunny Webboxes
4 +// to get the Solar Power Generated (current, today, total).
5 +
6 +// example configuration in /etc/netdata/sma_webbox.conf
7 +/*
8 +{
9 + "enable_autodetect": false,
10 + "update_every": 5,
11 + "servers": [
12 + {
13 + "name": "plant1",
14 + "hostname": "10.0.1.1",
15 + "update_every": 10
16 + },
17 + {
18 + "name": "plant2",
19 + "hostname": "10.0.2.1",
20 + "update_every": 15
21 + }
22 + ]
23 +}
24 +*/
25 +
26 +var url = require('url');
27 +var http = require('http');
28 +var netdata = require('netdata');
29 +
30 +if(netdata.options.DEBUG === true) netdata.debug('loaded ' + __filename + ' plugin');
31 +
32 +var webbox = {
33 + name: __filename,
34 + enable_autodetect: true,
35 + update_every: 1000,
36 +
37 + charts: {},
38 +
39 + processResponse: function(service, data) {
40 + if(data !== null) {
41 + var r = JSON.parse(data);
42 +
43 + var d = {
44 + 'GriPwr': {
45 + unit: null,
46 + value: null
47 + },
48 + 'GriEgyTdy': {
49 + unit: null,
50 + value: null
51 + },
52 + 'GriEgyTot': {
53 + unit: null,
54 + value: null
55 + }
56 + };
57 +
58 + // parse the webbox response
59 + // and put it in our d object
60 + var found = 0;
61 + var len = r.result.overview.length;
62 + while(len--) {
63 + var e = r.result.overview[len];
64 + if(typeof(d[e.meta]) !== 'undefined') {
65 + found++;
66 + d[e.meta].value = e.value;
67 + d[e.meta].unit = e.unit;
68 + }
69 + }
70 +
71 + // add the service
72 + if(found > 0 && service.added !== true)
73 + netdata.serviceAdd(service);
74 +
75 + // Grid Current Power Chart
76 + if(d['GriPwr'].value !== null) {
77 + var id = 'sma_webbox_' + service.name + '.current';
78 + var chart = webbox.charts[id];
79 +
80 + if(typeof chart === 'undefined') {
81 + chart = {
82 + id: id, // the unique id of the chart
83 + name: '', // the unique name of the chart
84 + title: service.name + ' Current Grid Power', // the title of the chart
85 + units: d['GriPwr'].unit, // the units of the chart dimensions
86 + family: 'sma_webbox_' + service.name, // the family of the chart
87 + category: 'sma_webbox_' + service.name, // the category of the chart
88 + type: netdata.chartTypes.area, // the type of the chart
89 + priority: 1000, // the priority relative to others in the same family and category
90 + update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
91 + dimensions: {
92 + 'GriPwr': {
93 + id: 'GriPwr', // the unique id of the dimension
94 + name: 'power', // the name of the dimension
95 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
96 + multiplier: 1, // the multiplier
97 + divisor: 1, // the divisor
98 + hidden: false // is hidden (boolean)
99 + }
100 + }
101 + };
102 +
103 + chart = service.chart(id, chart);
104 + webbox.charts[id] = chart;
105 + }
106 +
107 + service.begin(chart);
108 + service.set('GriPwr', Math.round(d['GriPwr'].value));
109 + service.end();
110 + }
111 +
112 + if(d['GriEgyTdy'].value !== null) {
113 + var id = 'sma_webbox_' + service.name + '.today';
114 + var chart = webbox.charts[id];
115 +
116 + if(typeof chart === 'undefined') {
117 + chart = {
118 + id: id, // the unique id of the chart
119 + name: '', // the unique name of the chart
120 + title: service.name + ' Today Grid Power', // the title of the chart
121 + units: d['GriEgyTdy'].unit, // the units of the chart dimensions
122 + family: 'sma_webbox_' + service.name, // the family of the chart
123 + category: 'sma_webbox_' + service.name, // the category of the chart
124 + type: netdata.chartTypes.area, // the type of the chart
125 + priority: 1000, // the priority relative to others in the same family and category
126 + update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
127 + dimensions: {
128 + 'GriEgyTdy': {
129 + id: 'GriEgyTdy', // the unique id of the dimension
130 + name: 'power', // the name of the dimension
131 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
132 + multiplier: 1, // the multiplier
133 + divisor: 1000, // the divisor
134 + hidden: false // is hidden (boolean)
135 + }
136 + }
137 + };
138 +
139 + chart = service.chart(id, chart);
140 + webbox.charts[id] = chart;
141 + }
142 +
143 + service.begin(chart);
144 + service.set('GriEgyTdy', Math.round(d['GriEgyTdy'].value * 1000));
145 + service.end();
146 + }
147 +
148 + if(d['GriEgyTot'].value !== null) {
149 + var id = 'sma_webbox_' + service.name + '.total';
150 + var chart = webbox.charts[id];
151 +
152 + if(typeof chart === 'undefined') {
153 + chart = {
154 + id: id, // the unique id of the chart
155 + name: '', // the unique name of the chart
156 + title: service.name + ' Total Grid Power', // the title of the chart
157 + units: d['GriEgyTot'].unit, // the units of the chart dimensions
158 + family: 'sma_webbox_' + service.name, // the family of the chart
159 + category: 'sma_webbox_' + service.name, // the category of the chart
160 + type: netdata.chartTypes.area, // the type of the chart
161 + priority: 1000, // the priority relative to others in the same family and category
162 + update_every: Math.round(service.update_every / 1000), // the expected update frequency of the chart
163 + dimensions: {
164 + 'GriEgyTot': {
165 + id: 'GriEgyTot', // the unique id of the dimension
166 + name: 'power', // the name of the dimension
167 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
168 + multiplier: 1, // the multiplier
169 + divisor: 1000, // the divisor
170 + hidden: false // is hidden (boolean)
171 + }
172 + }
173 + };
174 +
175 + chart = service.chart(id, chart);
176 + webbox.charts[id] = chart;
177 + }
178 +
179 + service.begin(chart);
180 + service.set('GriEgyTot', Math.round(d['GriEgyTot'].value * 1000));
181 + service.end();
182 + }
183 + }
184 + },
185 +
186 + // module.serviceExecute()
187 + // this function is called only from this module
188 + // its purpose is to prepare the request and call
189 + // netdata.serviceExecute()
190 + serviceExecute: function(name, hostname, update_every) {
191 + if(netdata.options.DEBUG === true) netdata.debug(this.name + ': ' + name + ': hostname: ' + hostname + ', update_every: ' + update_every);
192 +
193 + var service = {
194 + name: name,
195 + request: netdata.requestFromURL('http://' + hostname + '/rpc'),
196 + update_every: update_every,
197 + added: false,
198 + enabled: true,
199 + module: this
200 + };
201 + service.postData = 'RPC={"proc":"GetPlantOverview","format":"JSON","version":"1.0","id":"1"}';
202 + service.request.method = 'POST';
203 + service.request.headers['Content-Length'] = service.postData.length;
204 +
205 + netdata.serviceExecute(service, this.processResponse);
206 + },
207 +
208 + configure: function(config) {
209 + var added = 0;
210 +
211 + if(typeof(config.servers) !== 'undefined') {
212 + var len = config.servers.length;
213 + while(len--) {
214 + if(typeof config.servers[len].update_every === 'undefined')
215 + config.servers[len].update_every = this.update_every;
216 + else
217 + config.servers[len].update_every = config.servers[len].update_every * 1000;
218 +
219 + if(config.servers[len].update_every < 5000)
220 + config.servers[len].update_every = 5000;
221 +
222 + this.serviceExecute(config.servers[len].name, config.servers[len].hostname, config.servers[len].update_every);
223 + added++;
224 + }
225 + }
226 +
227 + return added;
228 + },
229 +
230 + // module.update()
231 + // this is called repeatidly to collect data, by calling
232 + // netdata.serviceExecute()
233 + update: function(service, callback) {
234 + netdata.serviceExecute(service, function(serv, data) {
235 + service.module.processResponse(serv, data);
236 + callback();
237 + });
238 + },
239 +};
240 +
241 +module.exports = webbox;
plugins.d/Makefile.am
+1 -1
@@ -10,7 +10,7 @@ dist_plugins_DATA = \
10 dist_plugins_SCRIPTS = \
11 charts.d.dryrun-helper.sh \
12 charts.d.plugin \
13 + node.d.plugin \
14 tc-qos-helper.sh \
15 loopsleepms.sh.inc \
15 - sma_webbox.plugin \
16 $(NULL)
plugins.d/sma_webbox.plugin deleted
-381
@@ -1,381 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -// This program will connect to one or more SMA Sunny Webboxes
4 -// to get the Solar Power Generated (current, today, total).
5 -
6 -// It needs a configuration file named sma_webbox.conf
7 -// The configuration is a JSON array, like this:
8 -
9 -/* --- BEGIN EXAMPLE CONFIGURATION ---
10 -
11 -[
12 - {
13 - "name": "label_of_solar_installation1",
14 - "hostname": "10.11.13.2"
15 - },
16 - {
17 - "name": "label_of_solar_installation2",
18 - "hostname": "10.11.13.3"
19 - }
20 -]
21 -
22 - --- END EXAMPLE CONFIGURATION --- */
23 -
24 -// you can add an unlimited number of webboxes
25 -
26 -var http = require('http');
27 -var fs = require('fs');
28 -
29 -// --------------------------------------------------------------------------------------------------------------------
30 -// get NETDATA environment variables
31 -
32 -var NETDATA_PLUGINS_DIR = process.env.NETDATA_PLUGINS_DIR || __dirname;
33 -var NETDATA_CONFIG_DIR = process.env.NETDATA_CONFIG_DIR || '/etc/netdata';
34 -var NETDATA_UPDATE_EVERY = process.env.NETDATA_UPDATE_EVERY || 1;
35 -NETDATA_UPDATE_EVERY = NETDATA_UPDATE_EVERY * 1000;
36 -
37 -var filename = NETDATA_CONFIG_DIR + '/sma_webbox.conf';
38 -
39 -// --------------------------------------------------------------------------------------------------------------------
40 -// get command line arguments
41 -
42 -var DEBUG = false;
43 -var UPDATE_EVERY = NETDATA_UPDATE_EVERY;
44 -var EXIT_AFTER_MS = 3600 * 1000;
45 -
46 -function debug(msg) {
47 - if(DEBUG) {
48 - var now = new Date();
49 - console.error(now.toString() + ': ' + __filename + ': DEBUG: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
50 - }
51 -}
52 -
53 -function error(msg) {
54 - var now = new Date();
55 - console.error(now.toString() + ': ' + __filename + ': ERROR: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
56 -}
57 -
58 -function netdata(msg) {
59 - console.log(msg.toString());
60 -}
61 -
62 -var found_myself = false;
63 -process.argv.forEach(function (val, index, array) {
64 - debug('PARAM: ' + val);
65 -
66 - if(!found_myself) {
67 - if(val === __filename)
68 - found_myself = true;
69 - }
70 - else {
71 - switch(val) {
72 - case 'debug':
73 - DEBUG = true;
74 - debug('DEBUG enabled');
75 - break;
76 -
77 - default:
78 - try {
79 - var x = parseInt(val);
80 - if(x > 0) {
81 - UPDATE_EVERY = x * 1000;
82 - if(UPDATE_EVERY < NETDATA_UPDATE_EVERY) {
83 - UPDATE_EVERY = NETDATA_UPDATE_EVERY;
84 - debug('Update frequency ' + x + 's is too low');
85 - }
86 -
87 - debug('Update frequency set to ' + UPDATE_EVERY + ' ms');
88 - }
89 - else error('Ignoring parameter: ' + val);
90 - }
91 - catch(e) {
92 - error('Cannot get value of parameter: ' + val);
93 - }
94 - }
95 - }
96 -});
97 -
98 -// there is no meaning to update the values sooner than 5 seconds
99 -// the SMA webbox collects data every 5 seconds
100 -if(UPDATE_EVERY < 5000) {
101 - debug('Adjusting update frequency to 5 seconds');
102 - UPDATE_EVERY = 5000;
103 -}
104 -
105 -// --------------------------------------------------------------------------------------------------------------------
106 -// parse configuration
107 -
108 -var Servers = new Array();
109 -try {
110 - Servers = JSON.parse(fs.readFileSync(filename, 'utf8'));
111 -}
112 -catch(e) {
113 - error('Cannot read configuration file ' + filename + ': ' + e.message);
114 - netdata('DISABLE');
115 - process.exit(1);
116 -}
117 -
118 -
119 -// --------------------------------------------------------------------------------------------------------------------
120 -// library functions
121 -
122 -// function to get data from server
123 -function getSMAData(server, callback) {
124 - // make sure there is not a request in progress for this webbox
125 - if(typeof(server.running) === 'boolean' && server.running) {
126 - debug(server.name + ' is already running');
127 - return false;
128 - }
129 - server.running = true;
130 -
131 - var now = new Date().getTime();
132 -
133 - // align the time to collect the values
134 - if(typeof(server.next_run) === 'undefined')
135 - server.next_run = now - (now % UPDATE_EVERY) + UPDATE_EVERY;
136 -
137 - // if it is too soon, we will collect data later
138 - if(now < server.next_run) {
139 - debug(server.name + ' not yet');
140 - server.running = false;
141 - return false;
142 - }
143 -
144 - // find the next refresh for this server
145 - while(server.next_run < now)
146 - server.next_run += UPDATE_EVERY;
147 -
148 - if(typeof(server.last_updated) === 'undefined')
149 - server.last_updated = 0;
150 -
151 - // create an agent for keep-alive
152 - if(typeof(server.agent) === 'undefined') {
153 - server.agent = new http.Agent({
154 - keepAlive: true,
155 - keepAliveMsecs: UPDATE_EVERY * 5,
156 - maxSockets: 2, // it needs 2, otherwise it ignores keepAlive
157 - maxFreeSockets: 1
158 - });
159 - }
160 -
161 - // create the charts, if we haven't already
162 - if(typeof(server.created) === 'undefined') {
163 - 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));
164 - netdata('DIMENSION GriPwr power absolute 1 1');
165 -
166 - 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));
167 - netdata('DIMENSION GriEgyTdy today absolute 1 1000');
168 -
169 - 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));
170 - netdata('DIMENSION GriEgyTot total absolute 1 1000');
171 -
172 - server.created = true;
173 - }
174 -
175 - // initialize our metrics to null
176 - // so that we will know if we read them or not
177 - server.data = {
178 - 'GriPwr': {
179 - unit: null,
180 - value: null
181 - },
182 - 'GriEgyTdy': {
183 - unit: null,
184 - value: null
185 - },
186 - 'GriEgyTot': {
187 - unit: null,
188 - value: null
189 - }
190 - };
191 -
192 - var postData = 'RPC={"proc":"GetPlantOverview","format":"JSON","version":"1.0","id":"1"}'
193 -
194 - var options = {
195 - protocol: server.protocol || 'http:',
196 - hostname: server.hostname,
197 - path: server.path || '/rpc',
198 - family: server.family || 4,
199 - port: server.port || 80,
200 - method: 'POST',
201 - headers: {
202 - 'Content-Type': 'application/x-www-form-urlencoded',
203 - 'Connection': 'keep-alive',
204 - 'Content-Length': postData.length
205 - },
206 - agent: server.agent
207 - };
208 -
209 - server.error = 0;
210 - server.success = false;
211 - server.response = '';
212 - server.request = http.request(options, function(res) {
213 - res.setEncoding('utf8');
214 - server.response_code = res.statusCode;
215 -
216 - // check if we got HTTP/200
217 - if(server.response_code !== 200) {
218 - // if this request is in error and we have
219 - // handled it, don't do anything more
220 - if(server.error === 0) {
221 - debug('Server responded with ' + server.response_code + ', failed to get data.');
222 - server.error = 503;
223 - server.success = false;
224 - server.running = false;
225 - if(typeof(callback) === 'function')
226 - callback(server);
227 - }
228 - }
229 -
230 - res.on('data', function(chunk) {
231 - // read more data, only if there is no error
232 - if(server.error === 0)
233 - server.response += chunk;
234 - });
235 -
236 - res.on('error', function() {
237 - // do we have already handled this error?
238 - if(server.error !== 0)
239 - return;
240 -
241 - debug('Received HTTP read error, failed to get data.');
242 - server.error = 504;
243 - server.success = false;
244 - server.running = false;
245 - if(typeof(callback) === 'function')
246 - callback(server);
247 - });
248 -
249 - res.on('end', function() {
250 - // if there is an error we have handled
251 - // don't do anything
252 - if(server.error !== 0)
253 - return;
254 -
255 - var t = new Date().getTime();
256 -
257 - server.dt = 0;
258 - if(server.last_updated !== 0)
259 - server.dt = t - server.last_updated;
260 -
261 - debug('RESPONSE: ' + server.response);
262 - try {
263 - server.response = JSON.parse(server.response);
264 -
265 - var len = server.response.result.overview.length;
266 - while(len--) {
267 - var e = server.response.result.overview[len];
268 - debug(e);
269 - if(typeof(server.data[e.meta]) !== 'undefined') {
270 - server.data[e.meta].value = e.value;
271 - server.data[e.meta].unit = e.unit;
272 - }
273 - }
274 -
275 - if(server.data['GriPwr'].value !== null) {
276 - netdata('BEGIN sma_webbox_' + server.name + '.current ' + ((server.dt)?server.dt*1000:'').toString());
277 - netdata('SET GriPwr = ' + server.data['GriPwr'].value);
278 - netdata('END');
279 - }
280 -
281 - if(server.data['GriEgyTdy'].value !== null) {
282 - netdata('BEGIN sma_webbox_' + server.name + '.today ' + ((server.dt)?server.dt*1000:'').toString());
283 - netdata('SET GriEgyTdy = ' + Math.round(server.data['GriEgyTdy'].value * 1000));
284 - netdata('END');
285 - }
286 -
287 - if(server.data['GriEgyTot'].value !== null) {
288 - netdata('BEGIN sma_webbox_' + server.name + '.total ' + ((server.dt)?server.dt*1000:'').toString());
289 - netdata('SET GriEgyTot = ' + Math.round(server.data['GriEgyTot'].value * 1000));
290 - netdata('END');
291 - }
292 -
293 - server.error = 0;
294 - server.success = true;
295 - server.last_updated = t;
296 - }
297 - catch(e) {
298 - server.error = 501;
299 - server.success = false;
300 - server.failure_message = e.message;
301 - error('FAILED TO PARSE DATA: ' + e.message);
302 - }
303 -
304 - server.running = false;
305 - if(typeof(callback) === 'function')
306 - callback(server);
307 - });
308 - });
309 -
310 - server.request.on('error', function(e) {
311 - // do we have already handled this error?
312 - if(server.error !== 0)
313 - return;
314 -
315 - server.error = 502;
316 - server.success = false;
317 - server.running = false;
318 - server.failure_message = e.message;
319 - error('problem with request to ' + server.hostname + ': ' + e.message);
320 -
321 - if(typeof(callback) === 'function')
322 - callback(server);
323 - });
324 -
325 - // write data to request body
326 - server.request.write(postData);
327 - server.request.end();
328 -
329 - return true;
330 -}
331 -
332 -// get a message for the current error
333 -function getSMAError(server) {
334 - if(server.success) return 'OK';
335 -
336 - if(typeof(server.error) === 'undefined')
337 - return 'Not initialized';
338 -
339 - switch(server.error) {
340 - case 500: return 'Pre-fetch Error';
341 - case 501: return 'Failed to parse server response';
342 - case 502: return 'HTTP error while making request';
343 - case 504: return 'Failed to read response data';
344 - default: return 'Undefined Error';
345 - }
346 -}
347 -
348 -// --------------------------------------------------------------------------------------------------------------------
349 -
350 -// if we don't have any servers to run
351 -// inform netdata we don't need to run on this machine
352 -if(!Servers.length) {
353 - error('No SMA webbox servers defined.');
354 - netdata('DISABLE');
355 - process.exit(1);
356 -}
357 -
358 -var started = new Date().getTime();
359 -function runServers() {
360 - var now = new Date().getTime();
361 - if(now - started > EXIT_AFTER_MS) {
362 - debug('We have to exit now. Netdata will restart us.');
363 - // FIXME
364 - // We should wait for any currently running requests to complete
365 - process.exit(0);
366 - }
367 -
368 - var len = Servers.length;
369 - while(len--) {
370 - getSMAData(Servers[len], function(server) {
371 - if(server.success)
372 - debug(' OK ' + server.name + ' time since last run: ' + server.dt + ' ms');
373 - else
374 - error(getSMAError(server) + server.name);
375 - });
376 - }
377 -
378 - setTimeout(runServers, 100);
379 -}
380 -
381 -runServers();
src/plugins_d.c
+5 -1
@@ -228,8 +228,12 @@ void *pluginsd_worker_thread(void *arg)
228 st = NULL;
229 }
230 else if(likely(hash == CHART_HASH && !strcmp(s, "CHART"))) {
231 + int noname = 0;
232 st = NULL;
233
234 + if((words[1]) != NULL && (words[2]) != NULL && strcmp(words[1], words[2]) == 0)
235 + noname = 1;
236 +
237 char *type = words[1];
238 char *id = NULL;
239 if(likely(type)) {
@@ -262,7 +266,7 @@ void *pluginsd_worker_thread(void *arg)
266 int chart_type = RRDSET_TYPE_LINE;
267 if(unlikely(chart)) chart_type = rrdset_type_id(chart);
268
265 - if(unlikely(!name || !*name)) name = NULL;
269 + if(unlikely(noname || !name || !*name || strcasecmp(name, "NULL") == 0 || strcasecmp(name, "(NULL)") == 0)) name = NULL;
270 if(unlikely(!family || !*family)) family = id;
271 if(unlikely(!category || !*category)) category = type;
272
web/dashboard.js
+2
@@ -4319,6 +4319,7 @@
4319 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4320 })
4321 .fail(function() {
4322 + NETDATA.chartLibraries.easypiechart.enabled = false;
4323 NETDATA.error(100, NETDATA.easypiechart_js);
4324 })
4325 .always(function() {
@@ -4510,6 +4511,7 @@
4511 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4512 })
4513 .fail(function() {
4514 + NETDATA.chartLibraries.gauge.enabled = false;
4515 NETDATA.error(100, NETDATA.gauge_js);
4516 })
4517 .always(function() {
web/index.html
+1 -1
@@ -281,7 +281,7 @@
281 <!-- <script> netdataServer = "http://box:19999"; </script> -->
282
283 <!-- load the dashboard manager - it will do the rest -->
284 - <script type="text/javascript" src="dashboard.js?v23"></script>
284 + <script type="text/javascript" src="dashboard.js?v24"></script>
285 </head>
286
287 <body data-spy="scroll" data-target="#sidebar">