]> git.sesse.net Git - nms/blob - clients/snmpfetch.pl
Lower the sleep interval.
[nms] / clients / snmpfetch.pl
1 #! /usr/bin/perl
2 use BER;
3 use DBI;
4 use POSIX;
5 use Time::HiRes;
6 use Net::Telnet;
7 use strict;
8 use warnings;
9 require 'SNMP_Session.pm';
10
11 use lib '../include';
12 use nms;
13
14 my $password = 'removed';
15 my $timeout = 15;
16
17 my $dbh = nms::db_connect();
18 $dbh->{AutoCommit} = 0;
19
20 # normal mode: fetch switches from the database
21 # instant mode: poll the switches specified on the command line
22 my $instant = (defined($ARGV[0]));
23 my $qualification;
24 if ($instant) {
25         $qualification = "sysname=?";
26 } else {
27         $qualification = <<"EOF";
28   (last_updated IS NULL OR now() - last_updated > poll_frequency)
29   AND (locked='f' OR now() - last_updated > '15 minutes'::interval)
30 EOF
31 }
32
33 my $qswitch = $dbh->prepare(<<"EOF")
34 SELECT 
35   *,
36   DATE_TRUNC('second', now() - last_updated - poll_frequency) AS overdue
37 FROM
38   switches
39   NATURAL LEFT JOIN switchtypes
40 WHERE $qualification
41 ORDER BY
42   priority DESC,
43   overdue DESC
44 LIMIT 1
45 FOR UPDATE OF switches
46 EOF
47         or die "Couldn't prepare qswitch";
48 my $qlock = $dbh->prepare("UPDATE switches SET locked='t', last_updated=now() WHERE switch=?")
49         or die "Couldn't prepare qlock";
50 my $qunlock = $dbh->prepare("UPDATE switches SET locked='f', last_updated=now() WHERE switch=?")
51         or die "Couldn't prepare qunlock";
52 my $qpoll = $dbh->prepare("INSERT INTO polls (time, switch, port, bytes_in, bytes_out, errors_in, errors_out) VALUES (timeofday()::timestamp,?,?,?,?,?,?)")
53         or die "Couldn't prepare qpoll";
54 my $qtemppoll = $dbh->prepare("INSERT INTO temppoll (time, switch, temp) VALUES (timeofday()::timestamp,?::text::int,?::text::float)")
55         or die "Couldn't prepare qtemppoll";
56 my $qcpupoll = $dbh->prepare("INSERT INTO cpuloadpoll (time, switch, entity, value) VALUES (timeofday()::timestamp,?::text::int,?,?)")
57         or die "Couldn't prepare qtemppoll";
58
59 while (1) {
60         my $sysname;
61         if ($instant) {
62                 $sysname = shift @ARGV;
63                 exit if (!defined($sysname));
64                 $qswitch->execute($sysname)
65                         or die "Couldn't get switch";
66         } else {
67                 # Find a switch to grab
68                 $qswitch->execute()
69                         or die "Couldn't get switch";
70         }
71         my $switch = $qswitch->fetchrow_hashref();
72
73         if (!defined($switch)) {
74                 $dbh->commit;
75
76                 if ($instant) {
77                         mylog("No such switch $sysname available, quitting.");
78                         exit;
79                 } else {        
80                         mylog("No available switches in pool, sleeping.");
81                         sleep 15;
82                         next;
83                 }
84         }
85
86         $qlock->execute($switch->{'switch'})
87                 or die "Couldn't lock switch";
88         $dbh->commit;
89
90         if ($switch->{'locked'}) {
91                 mylog("WARNING: Lock timed out on $switch->{'ip'}, breaking lock");
92         }
93
94         my $msg;
95         if (defined($switch->{'overdue'})) {
96                 $msg = sprintf "Polling ports %s on %s (%s), %s overdue.",
97                         $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'}, $switch->{'overdue'};
98         } else {
99                 $msg = sprintf "Polling ports %s on %s (%s), never polled before.",
100                         $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'};
101         }
102         mylog($msg);
103
104         my $ip = $switch->{'ip'};
105
106         if ($ip eq '127.0.0.1') {
107                 mylog("Polling disabled for this switch, skipping.");
108                 $qunlock->execute($switch->{'switch'})
109                         or die "Couldn't unlock switch";
110                 $dbh->commit;
111                 next;
112         }
113
114         my $community = $switch->{'community'};
115         my $start = [Time::HiRes::gettimeofday];
116         eval {
117                 my $session;
118                 if ($switch->{'wide_counters'}) {
119                         $session = SNMPv2c_Session->open($ip, $community, 161)
120                                 or die "Couldn't talk to switch";
121                 } else {
122                         $session = SNMP_Session->open($ip, $community, 161)
123                                 or die "Couldn't talk to switch";
124                 }
125                 my @ports = expand_ports($switch->{'ports'});
126
127                 for my $port (@ports) {
128                         my $in = fetch_data($session, $port, 0, $switch->{'wide_counters'});
129                         my $out = fetch_data($session, $port, 1, $switch->{'wide_counters'});
130                         my $ine = fetch_errors($session, $port, 0);
131                         my $oute = fetch_errors($session, $port, 1);
132
133                         $qpoll->execute($switch->{'switch'}, $port, $in, $out, $ine, $oute);
134                 }
135                 my $conn = switch_connect($ip);
136                 if (!defined($conn)) {
137                         print "Could not connect to switch ".$switch->{'switch'}."\n";
138                 } elsif ($switch->{'switchtype'} eq 'es3024') { 
139                         my @data = switch_exec('sys monitor status', $conn);
140                         my @fields = split(/\s+/, $data[2]);
141                         # The temp fields are 6, 7, 8
142                         print "$fields[7] + $fields[8] + $fields[9]\n";
143                         my $avgtemp = ($fields[7] + $fields[8] + $fields[9]) / 3;
144                         print $avgtemp." avgtemp\n";
145                         $qtemppoll->execute($switch->{'switch'},
146                                         $avgtemp) or die "Could not exec qtemppoll";
147                 } elsif ($switch->{'switchtype'} eq 'cisco6509' && 0) {
148                         # fetch load data for the entities
149                         for my $i (1..5) {
150                                 # find the ID
151                                 my $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 109, 1, 1, 1, 1, 2, $i);
152                                 my $entity = fetch_snmp($session, $oid);
153
154                                 next if (!defined($entity));
155                         
156                                 # last-minute load for the given entity
157                                 $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 109, 1, 1, 1, 1, 4, $i);
158                                 my $value = fetch_snmp($session, $oid);
159                                 
160                                 $qcpupoll->execute($switch->{'switch'}, $entity, $value)
161                                         or die "Could not exec qcpupoll";
162                         }
163                         
164                         # fetch load data for the matrix
165                         for my $i ([1,0], [1,1], [2,0], [2,1], [4,0], [5,0], [6,0]) {
166                                 # in
167                                 my $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 217, 1, 3, 1, 1, 6, $i->[0], $i->[1]);
168                                 my $in_util = fetch_snmp($session, $oid);
169
170                                 $qcpupoll->execute($switch->{'switch'}, 100000 + $i->[0] * 10 + $i->[1], $in_util)
171                                         or die "Could not exec qcpupoll";
172                                 
173                                 # ou
174                                 $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 217, 1, 3, 1, 1, 7, $i->[0], $i->[1]);
175                                 my $out_util = fetch_snmp($session, $oid);
176
177                                 $qcpupoll->execute($switch->{'switch'}, 200000 + $i->[0] * 10 + $i->[1], $out_util)
178                                         or die "Could not exec qcpupoll";
179                         }
180                 }
181                 $session->close;
182         };
183         if ($@) {
184                 mylog("ERROR: $@ (during poll of $ip)");
185                 $dbh->rollback;
186         }
187         
188         my $elapsed = Time::HiRes::tv_interval($start);
189         $msg = sprintf "Polled $switch->{'ip'} in %5.3f seconds.", $elapsed;            
190         mylog($msg);
191
192         $qunlock->execute($switch->{'switch'})
193                 or die "Couldn't unlock switch";
194         $dbh->commit;
195 }
196
197 sub fetch_data {
198         my ($session, $port, $out, $wide_counters) = @_;
199         
200         my $oid;
201         if ($wide_counters) {
202                 if ($out) {
203                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 10, $port);  # interfaces.ifTable.ifEntry.ifHCOutOctets
204                 } else {
205                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, $port);  # interfaces.ifTable.ifEntry.ifHCInOctets
206                 }
207         } else {
208                 if ($out) {
209                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 16, $port);  # interfaces.ifTable.ifEntry.ifOutOctets
210                 } else {
211                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 10, $port);  # interfaces.ifTable.ifEntry.ifInOctets
212                 }
213         }
214
215         return fetch_snmp($session, $oid);
216 }
217 sub fetch_errors {
218         my ($session, $port, $out) = @_;
219         
220         my $oid;
221         if ($out) {
222                 $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 20, $port);     # interfaces.ifTable.ifEntry.ifOutErrors
223         } else {
224                 $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 14, $port);     # interfaces.ifTable.ifEntry.ifInErrors
225         }
226
227         return fetch_snmp($session, $oid);
228 }
229         
230 sub fetch_snmp {
231         my ($session, $oid) = @_;
232
233         if ($session->get_request_response($oid)) {
234                 my ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
235                 my $binding;
236                 while ($bindings ne '') {
237                         ($binding,$bindings) = &decode_sequence ($bindings);
238                         my ($oid,$value) = &decode_by_template ($binding, "%O%@");
239                         return BER::pretty_print($value);
240                 }
241         }
242         die "Couldn't get info from switch";
243 }
244
245 sub expand_ports {
246         my $in = shift;
247         my @ranges = split /,/, $in;
248         my @ret = ();
249
250         for my $range (@ranges) {
251                 if ($range =~ /^\d+$/) {
252                         push @ret, $range;
253                 } elsif ($range =~ /^(\d+)-(\d+)$/) {
254                         for my $i ($1..$2) {
255                                 push @ret, $i;
256                         }
257                 } else {
258                         die "Couldn't understand '$range' in ports";
259                 }
260         }
261
262         return (sort { $a <=> $b } @ret); 
263 }
264
265 sub mylog {
266         my $msg = shift;
267         my $time = POSIX::ctime(time);
268         $time =~ s/\n.*$//;
269         printf STDERR "[%s] %s\n", $time, $msg;
270 }
271
272 #sub switch_exec {
273 #       my ($cmd, $conn) = @_;
274 #
275 #       # Send the command and get data from switch
276 ##      $conn->dump_log(*STDOUT);
277 #       my @data = $conn->cmd($cmd);
278 #       my @lines = ();
279 #       foreach my $line (@data) {
280 #               # Remove escape-7 sequence
281 #               $line =~ s/\x1b\x37//g;
282 #               push @lines, $line;
283 #       }
284 #
285 #       return @lines;
286 #}
287
288 #sub switch_connect {
289 #       my ($ip) = @_;
290 #
291 #       my $conn = new Net::Telnet(     Timeout => $timeout,
292 #                                       Dump_Log => '/tmp/dumplog-tempfetch',
293 #                                       Errmode => 'return',
294 #                                       Prompt => '/es-3024|e(\-)?\d+\-\dsw>/i');
295 #       my $ret = $conn->open(  Host => $ip);
296 #       if (!$ret || $ret != 1) {
297 #               return (0);
298 #       }
299 #       # XXX: Just send the password as text, I did not figure out how to
300 #       # handle authentication with only password through $conn->login().
301 #       #$conn->login(  Prompt => '/password[: ]*$/i',
302 #       #              Name => $password,
303 #       #              Password => $password);
304 #       my @data = $conn->cmd($password);
305 #       # Get rid of banner
306 #       $conn->get;
307 #       return $conn;
308 #}
309