]> arthur.barton.de Git - netdata.git/blob - node.d/snmp.node.js
Merge pull request #1249 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         if(typeof service.snmp_oids === 'undefined' || service.snmp_oids === null || service.snmp_oids.length === 0) {
129             // this is the first time we see this service
130
131             if(netdata.options.DEBUG === true)
132                 netdata.debug(service.module.name + ': ' + service.name + ': preparing ' + this.name + ' OIDs');
133
134             // build an index of all OIDs
135             service.snmp_oids_index = {};
136             for(var c in service.request.charts) {
137                 // for each chart
138
139                 if(netdata.options.DEBUG === true)
140                     netdata.debug(service.module.name + ': ' + service.name + ': indexing ' + this.name + ' chart: ' + c);
141
142                 if(typeof service.request.charts[c].titleoid !== 'undefined') {
143                         service.snmp_oids_index[this.fixoid(service.request.charts[c].titleoid)] = {
144                             type: 'title',
145                             link: service.request.charts[c]
146                         };
147                     }
148
149                 for(var d in service.request.charts[c].dimensions) {
150                     // for each dimension in the chart
151
152                     var oid = this.fixoid(service.request.charts[c].dimensions[d].oid);
153                     var oidname = this.fixoid(service.request.charts[c].dimensions[d].oidname);
154                     
155                     if(netdata.options.DEBUG === true)
156                         netdata.debug(service.module.name + ': ' + service.name + ': indexing ' + this.name + ' chart: ' + c + ', dimension: ' + d + ', OID: ' + oid + ", OID name: " + oidname);
157
158                     // link it to the point we need to set the value to
159                     service.snmp_oids_index[oid] = {
160                         type: 'value',
161                         link: service.request.charts[c].dimensions[d]
162                     };
163
164                     if(typeof oidname !== 'undefined')
165                         service.snmp_oids_index[oidname] = {
166                             type: 'name',
167                             link: service.request.charts[c].dimensions[d]
168                         };
169
170                     // and set the value to null
171                     service.request.charts[c].dimensions[d].value = null;
172                 }
173             }
174
175             if(netdata.options.DEBUG === true)
176                 netdata.debug(service.module.name + ': ' + service.name + ': indexed ' + this.name + ' OIDs: ' + netdata.stringify(service.snmp_oids_index));
177
178             // now create the array of OIDs needed by net-snmp
179             service.snmp_oids = new Array();
180             for(var o in service.snmp_oids_index)
181                 service.snmp_oids.push(o);
182
183             if(netdata.options.DEBUG === true)
184                 netdata.debug(service.module.name + ': ' + service.name + ': final list of ' + this.name + ' OIDs: ' + netdata.stringify(service.snmp_oids));
185
186             service.snmp_oids_cleaned = 0;
187         }
188         else if(service.snmp_oids_cleaned === 0) {
189             service.snmp_oids_cleaned = 1;
190
191             // the second time, keep only values
192             service.snmp_oids = new Array();
193             for(var o in service.snmp_oids_index)
194                 if(service.snmp_oids_index[o].type === 'value')
195                     service.snmp_oids.push(o);
196         }
197     },
198
199     getdata: function(service, index, ok, failed, callback) {
200         var that = this;
201
202         if(index >= service.snmp_oids.length) {
203             callback((ok > 0)?{ ok: ok, failed: failed }:null);
204             return;
205         }
206
207         var slice;
208         if(service.snmp_oids.length <= service.request.max_request_size) {
209             slice = service.snmp_oids;
210             index = service.snmp_oids.length;
211         }
212         else if(service.snmp_oids.length - index <= service.request.max_request_size) {
213             slice = service.snmp_oids.slice(index, service.snmp_oids.length);
214             index = service.snmp_oids.length;
215         }
216         else {
217             slice = service.snmp_oids.slice(index, index + service.request.max_request_size);
218             index += service.request.max_request_size;
219         }
220
221         if(netdata.options.DEBUG === true)
222             netdata.debug(service.module.name + ': ' + service.name + ': making ' + slice.length + ' entries request, max is: ' + service.request.max_request_size);
223
224         service.snmp_session.get(slice, function(error, varbinds) {
225             if(error) {
226                 service.error('Received error = ' + netdata.stringify(error) + ' varbinds = ' + netdata.stringify(varbinds));
227
228                 // make all values null
229                 var len = slice.length;
230                 while(len--)
231                     service.snmp_oids_index[slice[len]].value = null;
232             }
233             else {
234                 if(netdata.options.DEBUG === true)
235                     netdata.debug(service.module.name + ': ' + service.name + ': got valid ' + service.module.name + ' response: ' + netdata.stringify(varbinds));
236
237                 for(var i = 0; i < varbinds.length; i++) {
238                     var value = null;
239
240                     if(net_snmp.isVarbindError(varbinds[i])) {
241                         if(netdata.options.DEBUG === true)
242                             netdata.debug(service.module.name + ': ' + service.name + ': failed ' + service.module.name + ' get for OIDs ' + varbinds[i].oid);
243
244                         service.error('OID ' + varbinds[i].oid + ' gave error: ' + snmp.varbindError(varbinds[i]));
245                         value = null;
246                         failed++;
247                     }
248                     else {
249                         if(netdata.options.DEBUG === true)
250                             netdata.debug(service.module.name + ': ' + service.name + ': found ' + service.module.name + ' value of OIDs ' + varbinds[i].oid + " = " + varbinds[i].value);
251
252                         value = varbinds[i].value;
253                         ok++;
254                     }
255
256                     if(value !== null) {
257                         switch(service.snmp_oids_index[varbinds[i].oid].type) {
258                             case 'title': service.snmp_oids_index[varbinds[i].oid].link.title += ' ' + value; break;
259                             case 'name' : service.snmp_oids_index[varbinds[i].oid].link.name = value; break;
260                             case 'value': service.snmp_oids_index[varbinds[i].oid].link.value = value; break;
261                         }
262                     }
263                 }
264
265                 if(netdata.options.DEBUG === true)
266                     netdata.debug(service.module.name + ': ' + service.name + ': finished ' + service.module.name + ' with ' + ok + ' successful and ' + failed + ' failed values');
267             }
268             that.getdata(service, index, ok, failed, callback);
269         });
270     },
271
272     process: function(service, callback) {
273         this.prepare(service);
274
275         if(service.snmp_oids.length === 0) {
276             // no OIDs found for this service
277
278             if(netdata.options.DEBUG === true)
279                 service.error('no OIDs to process.');
280
281             callback(null);
282             return;
283         }
284
285         if(typeof service.snmp_session === 'undefined' || service.snmp_session === null) {
286             // no SNMP session has been created for this service
287             // the SNMP session is just the initialization of NET-SNMP
288
289             if(netdata.options.DEBUG === true)
290                 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));
291
292             // create the SNMP session
293             service.snmp_session = net_snmp.createSession (service.request.hostname, service.request.community, service.request.options);
294
295             if(netdata.options.DEBUG === true)
296                 netdata.debug(service.module.name + ': ' + service.name + ': got ' + this.name + ' session: ' + netdata.stringify(service.snmp_session));
297
298             // if we later need traps, this is how to do it:
299             //service.snmp_session.trap(net_snmp.TrapType.LinkDown, function(error) {
300             //  if(error) console.error('trap error: ' + netdata.stringify(error));
301             //});
302         }
303
304         // do it, get the SNMP values for the sessions we need
305         this.getdata(service, 0, 0, 0, callback);
306     }
307 };
308
309 var snmp = {
310     name: __filename,
311     enable_autodetect: true,
312     update_every: 1,
313     base_priority: 50000,
314
315     charts: {},
316
317     processResponse: function(service, data) {
318         if(data !== null) {
319             if(service.added !== true)
320                 service.commit();
321
322             for(var c in service.request.charts) {
323                 var chart = snmp.charts[c];
324
325                 if(typeof chart === 'undefined') {
326                     chart = service.chart(c, service.request.charts[c]);
327                     snmp.charts[c] = chart;
328                 }
329
330                 service.begin(chart);
331                 
332                 for( var d in service.request.charts[c].dimensions )
333                     if(service.request.charts[c].dimensions[d].value !== null)
334                         service.set(d, service.request.charts[c].dimensions[d].value);
335
336                 service.end();
337             }
338         }
339     },
340
341     // module.serviceExecute()
342     // this function is called only from this module
343     // its purpose is to prepare the request and call
344     // netdata.serviceExecute()
345     serviceExecute: function(conf) {
346         if(netdata.options.DEBUG === true)
347             netdata.debug(this.name + ': snmp hostname: ' + conf.hostname + ', update_every: ' + conf.update_every);
348
349         var service = netdata.service({
350             name: conf.hostname,
351             request: conf,
352             update_every: conf.update_every,
353             module: this,
354             processor: netdata.processors.snmp
355         });
356
357         // multiply the charts, if required
358         for(var c in service.request.charts) {
359             if(netdata.options.DEBUG === true)
360                 netdata.debug(this.name + ': snmp hostname: ' + conf.hostname + ', examining chart: ' + c);
361
362             if(typeof service.request.charts[c].update_every === 'undefined')
363                 service.request.charts[c].update_every = service.update_every;
364
365             if(typeof service.request.charts[c].multiply_range !== 'undefined') {
366                 var from = service.request.charts[c].multiply_range[0];
367                 var to = service.request.charts[c].multiply_range[1];
368                 var prio = service.request.charts[c].priority || 1;
369
370                 if(prio < snmp.base_priority) prio += snmp.base_priority;
371
372                 while(from <= to) {
373                     var id = c + from.toString();
374                     var chart = extend(true, {}, service.request.charts[c]);
375                     chart.title += from.toString();
376                     
377                     if(typeof chart.titleoid !== 'undefined')
378                         chart.titleoid += from.toString();
379
380                     chart.priority = prio++;
381                     for(var d in chart.dimensions) {
382                         chart.dimensions[d].oid += from.toString();
383
384                         if(typeof chart.dimensions[d].oidname !== 'undefined')
385                             chart.dimensions[d].oidname += from.toString();
386                     }
387                     service.request.charts[id] = chart;
388                     from++;
389                 }
390
391                 delete service.request.charts[c];
392             }
393             else {
394                 if(service.request.charts[c].priority < snmp.base_priority)
395                     service.request.charts[c].priority += snmp.base_priority;
396             }
397         }
398
399         service.execute(this.processResponse);
400     },
401
402     configure: function(config) {
403         var added = 0;
404
405         if(typeof config.max_request_size === 'undefined')
406             config.max_request_size = 50;
407
408         if(typeof(config.servers) !== 'undefined') {
409             var len = config.servers.length;
410             while(len--) {
411                 if(typeof config.servers[len].update_every === 'undefined')
412                     config.servers[len].update_every = this.update_every;
413
414                 if(typeof config.servers[len].max_request_size === 'undefined')
415                     config.servers[len].max_request_size = config.max_request_size;
416
417                 this.serviceExecute(config.servers[len]);
418                 added++;
419             }
420         }
421
422         return added;
423     },
424
425     // module.update()
426     // this is called repeatidly to collect data, by calling
427     // service.execute()
428     update: function(service, callback) {
429         service.execute(function(serv, data) {
430             service.module.processResponse(serv, data);
431             callback();
432         });
433     },
434 };
435
436 module.exports = snmp;