]> arthur.barton.de Git - netdata.git/blob - node.d/snmp.node.js
Merge pull request #1520 from ktsaou/master
[netdata.git] / node.d / snmp.node.js
1 'use strict';
2
3 // This program will connect to one or more SNMP Agents
4
5 // example configuration in /etc/netdata/node.d/snmp.conf
6 /*
7 {
8     "enable_autodetect": false,
9     "update_every": 5,
10     "max_request_size": 50,
11     "servers": [
12         {
13             "hostname": "10.11.12.8",
14             "community": "public",
15             "update_every": 10,
16             "max_request_size": 50,
17             "options": { "timeout": 10000 },
18             "charts": {
19                 "snmp_switch.bandwidth_port1": {
20                     "title": "Switch Bandwidth for port 1",
21                     "units": "kilobits/s",
22                     "type": "area",
23                     "priority": 1,
24                     "dimensions": {
25                         "in": {
26                             "oid": ".1.3.6.1.2.1.2.2.1.10.1",
27                             "algorithm": "incremental",
28                             "multiplier": 8,
29                             "divisor": 1024
30                         },
31                         "out": {
32                             "oid": ".1.3.6.1.2.1.2.2.1.16.1",
33                             "algorithm": "incremental",
34                             "multiplier": -8,
35                             "divisor": 1024
36                         }
37                     }
38                 },
39                 "snmp_switch.bandwidth_port2": {
40                     "title": "Switch Bandwidth for port 2",
41                     "units": "kilobits/s",
42                     "type": "area",
43                     "priority": 1,
44                     "dimensions": {
45                         "in": {
46                             "oid": ".1.3.6.1.2.1.2.2.1.10.2",
47                             "algorithm": "incremental",
48                             "multiplier": 8,
49                             "divisor": 1024
50                         },
51                         "out": {
52                             "oid": ".1.3.6.1.2.1.2.2.1.16.2",
53                             "algorithm": "incremental",
54                             "multiplier": -8,
55                             "divisor": 1024
56                         }
57                     }
58                 }
59             }
60         }
61     ]
62 }
63 */
64
65 // You can also give ranges of charts like the following.
66 // This will append 1-24 to id, title, oid (on each dimension)
67 // so that 24 charts will be created.
68 /*
69 {
70     "enable_autodetect": false,
71     "update_every": 10,
72     "max_request_size": 50,
73     "servers": [
74         {
75             "hostname": "10.11.12.8",
76             "community": "public",
77             "update_every": 10,
78             "max_request_size": 50,
79             "options": { "timeout": 20000 },
80             "charts": {
81                 "snmp_switch.bandwidth_port": {
82                     "title": "Switch Bandwidth for port ",
83                     "units": "kilobits/s",
84                     "type": "area",
85                     "priority": 1,
86                     "multiply_range": [ 1, 24 ],
87                     "dimensions": {
88                         "in": {
89                             "oid": ".1.3.6.1.2.1.2.2.1.10.",
90                             "algorithm": "incremental",
91                             "multiplier": 8,
92                             "divisor": 1024
93                         },
94                         "out": {
95                             "oid": ".1.3.6.1.2.1.2.2.1.16.",
96                             "algorithm": "incremental",
97                             "multiplier": -8,
98                             "divisor": 1024
99                         }
100                     }
101                 }
102             }
103         }
104     ]
105 }
106 */
107
108 var net_snmp = require('net-snmp');
109 var extend = require('extend');
110 var netdata = require('netdata');
111
112 if(netdata.options.DEBUG === true) netdata.debug('loaded', __filename, ' plugin');
113
114 netdata.processors.snmp = {
115     name: 'snmp',
116
117     fixoid: function(oid) {
118         if(typeof oid !== 'string')
119             return oid;
120
121         if(oid.charAt(0) === '.')
122             return oid.substring(1, oid.length);
123
124         return oid;
125     },
126
127     prepare: function(service) {
128         var __DEBUG = netdata.options.DEBUG;
129
130         if(typeof service.snmp_oids === 'undefined' || service.snmp_oids === null || service.snmp_oids.length === 0) {
131             // this is the first time we see this service
132
133             if(__DEBUG === true)
134                 netdata.debug(service.module.name + ': ' + service.name + ': preparing ' + this.name + ' OIDs');
135
136             // build an index of all OIDs
137             service.snmp_oids_index = {};
138             var chart_keys = Object.keys(service.request.charts);
139             var chart_keys_len = chart_keys.length;
140             while(chart_keys_len--) {
141                 var c = chart_keys[chart_keys_len];
142                 var chart = service.request.charts[c];
143
144                 // for each chart
145
146                 if(__DEBUG === true)
147                     netdata.debug(service.module.name + ': ' + service.name + ': indexing ' + this.name + ' chart: ' + c);
148
149                 if(typeof chart.titleoid !== 'undefined') {
150                         service.snmp_oids_index[this.fixoid(chart.titleoid)] = {
151                             type: 'title',
152                             link: chart
153                         };
154                     }
155
156                 var dim_keys = Object.keys(chart.dimensions);
157                 var dim_keys_len = dim_keys.length;
158                 while(dim_keys_len--) {
159                     var d = dim_keys[dim_keys_len];
160                     var dim = chart.dimensions[d];
161
162                     // for each dimension in the chart
163
164                     var oid = this.fixoid(dim.oid);
165                     var oidname = this.fixoid(dim.oidname);
166                     
167                     if(__DEBUG === true)
168                         netdata.debug(service.module.name + ': ' + service.name + ': indexing ' + this.name + ' chart: ' + c + ', dimension: ' + d + ', OID: ' + oid + ", OID name: " + oidname);
169
170                     // link it to the point we need to set the value to
171                     service.snmp_oids_index[oid] = {
172                         type: 'value',
173                         link: dim
174                     };
175
176                     if(typeof oidname !== 'undefined')
177                         service.snmp_oids_index[oidname] = {
178                             type: 'name',
179                             link: dim
180                         };
181
182                     // and set the value to null
183                     dim.value = null;
184                 }
185             }
186
187             if(__DEBUG === true)
188                 netdata.debug(service.module.name + ': ' + service.name + ': indexed ' + this.name + ' OIDs: ' + netdata.stringify(service.snmp_oids_index));
189
190             // now create the array of OIDs needed by net-snmp
191             service.snmp_oids = Object.keys(service.snmp_oids_index);
192
193             if(__DEBUG === true)
194                 netdata.debug(service.module.name + ': ' + service.name + ': final list of ' + this.name + ' OIDs: ' + netdata.stringify(service.snmp_oids));
195
196             service.snmp_oids_cleaned = 0;
197         }
198         else if(service.snmp_oids_cleaned === 0) {
199             service.snmp_oids_cleaned = 1;
200
201             // the second time, keep only values
202
203             service.snmp_oids = new Array();
204             var oid_keys = Object.keys(service.snmp_oids_index);
205             var oid_keys_len = oid_keys.length;
206             while(oid_keys_len--) {
207                 if (service.snmp_oids_index[oid_keys[oid_keys_len]].type === 'value')
208                     service.snmp_oids.push(oid_keys[oid_keys_len]);
209             }
210         }
211     },
212
213     getdata: function(service, index, ok, failed, callback) {
214         var __DEBUG = netdata.options.DEBUG;
215         var that = this;
216
217         if(index >= service.snmp_oids.length) {
218             callback((ok > 0)?{ ok: ok, failed: failed }:null);
219             return;
220         }
221
222         var slice;
223         if(service.snmp_oids.length <= service.request.max_request_size) {
224             slice = service.snmp_oids;
225             index = service.snmp_oids.length;
226         }
227         else if(service.snmp_oids.length - index <= service.request.max_request_size) {
228             slice = service.snmp_oids.slice(index, service.snmp_oids.length);
229             index = service.snmp_oids.length;
230         }
231         else {
232             slice = service.snmp_oids.slice(index, index + service.request.max_request_size);
233             index += service.request.max_request_size;
234         }
235
236         if(__DEBUG === true)
237             netdata.debug(service.module.name + ': ' + service.name + ': making ' + slice.length + ' entries request, max is: ' + service.request.max_request_size);
238
239         service.snmp_session.get(slice, function(error, varbinds) {
240             if(error) {
241                 service.error('Received error = ' + netdata.stringify(error) + ' varbinds = ' + netdata.stringify(varbinds));
242
243                 // make all values null
244                 var len = slice.length;
245                 while(len--)
246                     service.snmp_oids_index[slice[len]].value = null;
247             }
248             else {
249                 if(__DEBUG === true)
250                     netdata.debug(service.module.name + ': ' + service.name + ': got valid ' + service.module.name + ' response: ' + netdata.stringify(varbinds));
251
252                 var varbinds_len = varbinds.length;
253                 for(var i = 0; i < varbinds_len ; i++) {
254                     var value = null;
255
256                     if(net_snmp.isVarbindError(varbinds[i])) {
257                         if(__DEBUG === true)
258                             netdata.debug(service.module.name + ': ' + service.name + ': failed ' + service.module.name + ' get for OIDs ' + varbinds[i].oid);
259
260                         service.error('OID ' + varbinds[i].oid + ' gave error: ' + snmp.varbindError(varbinds[i]));
261                         value = null;
262                         failed++;
263                     }
264                     else {
265                         if(__DEBUG === true)
266                             netdata.debug(service.module.name + ': ' + service.name + ': found ' + service.module.name + ' value of OIDs ' + varbinds[i].oid + " = " + varbinds[i].value);
267
268                         if(varbinds[i].type === net_snmp.ObjectType.OctetString)
269                             value = parseFloat(varbinds[i].value) * 1000;
270                         else
271                             value = varbinds[i].value;
272
273                         ok++;
274                     }
275
276                     if(value !== null) {
277                         switch(service.snmp_oids_index[varbinds[i].oid].type) {
278                             case 'title': service.snmp_oids_index[varbinds[i].oid].link.title += ' ' + value; break;
279                             case 'name' : service.snmp_oids_index[varbinds[i].oid].link.name = value; break;
280                             case 'value': service.snmp_oids_index[varbinds[i].oid].link.value = value; break;
281                         }
282                     }
283                 }
284
285                 if(__DEBUG === true)
286                     netdata.debug(service.module.name + ': ' + service.name + ': finished ' + service.module.name + ' with ' + ok + ' successful and ' + failed + ' failed values');
287             }
288             that.getdata(service, index, ok, failed, callback);
289         });
290     },
291
292     process: function(service, callback) {
293         var __DEBUG = netdata.options.DEBUG;
294
295         this.prepare(service);
296
297         if(service.snmp_oids.length === 0) {
298             // no OIDs found for this service
299
300             if(__DEBUG === true)
301                 service.error('no OIDs to process.');
302
303             callback(null);
304             return;
305         }
306
307         if(typeof service.snmp_session === 'undefined' || service.snmp_session === null) {
308             // no SNMP session has been created for this service
309             // the SNMP session is just the initialization of NET-SNMP
310
311             if(__DEBUG === true)
312                 netdata.debug(service.module.name + ': ' + service.name + ': opening ' + this.name + ' session on ' + service.request.hostname + ' community ' + service.request.community + ' options ' + netdata.stringify(service.request.options));
313
314             // create the SNMP session
315             service.snmp_session = net_snmp.createSession (service.request.hostname, service.request.community, service.request.options);
316
317             if(__DEBUG === true)
318                 netdata.debug(service.module.name + ': ' + service.name + ': got ' + this.name + ' session: ' + netdata.stringify(service.snmp_session));
319
320             // if we later need traps, this is how to do it:
321             //service.snmp_session.trap(net_snmp.TrapType.LinkDown, function(error) {
322             //  if(error) console.error('trap error: ' + netdata.stringify(error));
323             //});
324         }
325
326         // do it, get the SNMP values for the sessions we need
327         this.getdata(service, 0, 0, 0, callback);
328     }
329 };
330
331 var snmp = {
332     name: __filename,
333     enable_autodetect: true,
334     update_every: 1,
335     base_priority: 50000,
336
337     charts: {},
338
339     processResponse: function(service, data) {
340         if(data !== null) {
341             if(service.added !== true)
342                 service.commit();
343
344             var chart_keys = Object.keys(service.request.charts);
345             var chart_keys_len = chart_keys.length;
346             for(var i = 0; i < chart_keys_len; i++) {
347                 var c = chart_keys[i];
348
349                 var chart = snmp.charts[c];
350                 if(typeof chart === 'undefined') {
351                     chart = service.chart(c, service.request.charts[c]);
352                     snmp.charts[c] = chart;
353                 }
354
355                 service.begin(chart);
356
357                 var dimensions = service.request.charts[c].dimensions;
358                 var dim_keys = Object.keys(dimensions);
359                 var dim_keys_len = dim_keys.length;
360                 for(var j = 0; j < dim_keys_len ; j++) {
361                     var d = dim_keys[j];
362
363                     if (dimensions[d].value !== null)
364                         service.set(d, dimensions[d].value);
365                 }
366
367                 service.end();
368             }
369         }
370     },
371
372     // module.serviceExecute()
373     // this function is called only from this module
374     // its purpose is to prepare the request and call
375     // netdata.serviceExecute()
376     serviceExecute: function(conf) {
377         var __DEBUG = netdata.options.DEBUG;
378
379         if(__DEBUG === true)
380             netdata.debug(this.name + ': snmp hostname: ' + conf.hostname + ', update_every: ' + conf.update_every);
381
382         var service = netdata.service({
383             name: conf.hostname,
384             request: conf,
385             update_every: conf.update_every,
386             module: this,
387             processor: netdata.processors.snmp
388         });
389
390         // multiply the charts, if required
391         var chart_keys = Object.keys(service.request.charts);
392         var chart_keys_len = chart_keys.length;
393         for( var i = 0; i < chart_keys_len ; i++ ) {
394             var c = chart_keys[i];
395             var service_request_chart = service.request.charts[c];
396
397             if(__DEBUG === true)
398                 netdata.debug(this.name + ': snmp hostname: ' + conf.hostname + ', examining chart: ' + c);
399
400             if(typeof service_request_chart.update_every === 'undefined')
401                 service_request_chart.update_every = service.update_every;
402
403             if(typeof service_request_chart.multiply_range !== 'undefined') {
404                 var from = service_request_chart.multiply_range[0];
405                 var to = service_request_chart.multiply_range[1];
406                 var prio = service_request_chart.priority || 1;
407
408                 if(prio < snmp.base_priority) prio += snmp.base_priority;
409
410                 while(from <= to) {
411                     var id = c + from.toString();
412                     var chart = extend(true, {}, service_request_chart);
413                     chart.title += from.toString();
414                     
415                     if(typeof chart.titleoid !== 'undefined')
416                         chart.titleoid += from.toString();
417
418                     chart.priority = prio++;
419
420                     var dim_keys = Object.keys(chart.dimensions);
421                     var dim_keys_len = dim_keys.length;
422                     for(var j = 0; j < dim_keys_len ; j++) {
423                         var d = dim_keys[j];
424
425                         chart.dimensions[d].oid += from.toString();
426
427                         if(typeof chart.dimensions[d].oidname !== 'undefined')
428                             chart.dimensions[d].oidname += from.toString();
429                     }
430                     service.request.charts[id] = chart;
431                     from++;
432                 }
433
434                 delete service.request.charts[c];
435             }
436             else {
437                 if(service.request.charts[c].priority < snmp.base_priority)
438                     service.request.charts[c].priority += snmp.base_priority;
439             }
440         }
441
442         service.execute(this.processResponse);
443     },
444
445     configure: function(config) {
446         var added = 0;
447
448         if(typeof config.max_request_size === 'undefined')
449             config.max_request_size = 50;
450
451         if(typeof(config.servers) !== 'undefined') {
452             var len = config.servers.length;
453             while(len--) {
454                 if(typeof config.servers[len].update_every === 'undefined')
455                     config.servers[len].update_every = this.update_every;
456
457                 if(typeof config.servers[len].max_request_size === 'undefined')
458                     config.servers[len].max_request_size = config.max_request_size;
459
460                 this.serviceExecute(config.servers[len]);
461                 added++;
462             }
463         }
464
465         return added;
466     },
467
468     // module.update()
469     // this is called repeatidly to collect data, by calling
470     // service.execute()
471     update: function(service, callback) {
472         service.execute(function(serv, data) {
473             service.module.processResponse(serv, data);
474             callback();
475         });
476     }
477 };
478
479 module.exports = snmp;