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">