]> arthur.barton.de Git - netdata.git/blob - plugins.d/sma_webbox.plugin
log web access error to error.log #52
[netdata.git] / plugins.d / sma_webbox.plugin
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();