]> git.sesse.net Git - nms/blob - clients/snmpfetch.pl
Fetch load data for the 6509 matrix as well.
[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) 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 60;
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
131                         $qpoll->execute($switch->{'switch'}, $port, $in, $out);
132                 }
133                 my $conn = switch_connect($ip);
134                 if (!defined($conn)) {
135                         print "Could not connect to switch ".$switch->{'switch'}."\n";
136                 } elsif ($switch->{'switchtype'} eq 'es3024') { 
137                         my @data = switch_exec('sys monitor status', $conn);
138                         my @fields = split(/\s+/, $data[2]);
139                         # The temp fields are 6, 7, 8
140                         print "$fields[7] + $fields[8] + $fields[9]\n";
141                         my $avgtemp = ($fields[7] + $fields[8] + $fields[9]) / 3;
142                         print $avgtemp." avgtemp\n";
143                         $qtemppoll->execute($switch->{'switch'},
144                                         $avgtemp) or die "Could not exec qtemppoll";
145                 } elsif ($switch->{'switchtype'} eq 'cisco6509') {
146                         # fetch load data for the entities
147                         for my $i (1..5) {
148                                 # find the ID
149                                 my $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 109, 1, 1, 1, 1, 2, $i);
150                                 my $entity = fetch_snmp($session, $oid);
151
152                                 next if (!defined($entity));
153                         
154                                 # last-minute load for the given entity
155                                 $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 109, 1, 1, 1, 1, 4, $i);
156                                 my $value = fetch_snmp($session, $oid);
157                                 
158                                 $qcpupoll->execute($switch->{'switch'}, $entity, $value)
159                                         or die "Could not exec qcpupoll";
160                         }
161                         
162                         # fetch load data for the matrix
163                         for my $i ([1,0], [1,1], [2,0], [2,1], [4,0], [5,0], [6,0]) {
164                                 # in
165                                 my $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 217, 1, 3, 1, 1, 6, $i->[0], $i->[1]);
166                                 my $in_util = fetch_snmp($session, $oid);
167
168                                 $qcpupoll->execute($switch->{'switch'}, 100000 + $i->[0] * 10 + $i->[1], $in_util)
169                                         or die "Could not exec qcpupoll";
170                                 
171                                 # ou
172                                 $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 217, 1, 3, 1, 1, 7, $i->[0], $i->[1]);
173                                 my $out_util = fetch_snmp($session, $oid);
174
175                                 $qcpupoll->execute($switch->{'switch'}, 200000 + $i->[0] * 10 + $i->[1], $out_util)
176                                         or die "Could not exec qcpupoll";
177                         }
178                 }
179                 $session->close;
180         };
181         if ($@) {
182                 mylog("ERROR: $@ (during poll of $ip)");
183                 $dbh->rollback;
184         }
185         
186         my $elapsed = Time::HiRes::tv_interval($start);
187         $msg = sprintf "Polled $switch->{'ip'} in %5.3f seconds.", $elapsed;            
188         mylog($msg);
189
190         $qunlock->execute($switch->{'switch'})
191                 or die "Couldn't unlock switch";
192         $dbh->commit;
193 }
194
195 sub fetch_data {
196         my ($session, $port, $out, $wide_counters) = @_;
197         
198         my $oid;
199         if ($wide_counters) {
200                 if ($out) {
201                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 10, $port);  # interfaces.ifTable.ifEntry.ifHCOutOctets
202                 } else {
203                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, $port);  # interfaces.ifTable.ifEntry.ifHCInOctets
204                 }
205         } else {
206                 if ($out) {
207                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 16, $port);  # interfaces.ifTable.ifEntry.ifOutOctets
208                 } else {
209                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 10, $port);  # interfaces.ifTable.ifEntry.ifInOctets
210                 }
211         }
212
213         return fetch_snmp($session, $oid);
214 }
215         
216 sub fetch_snmp {
217         my ($session, $oid) = @_;
218
219         if ($session->get_request_response($oid)) {
220                 my ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
221                 my $binding;
222                 while ($bindings ne '') {
223                         ($binding,$bindings) = &decode_sequence ($bindings);
224                         my ($oid,$value) = &decode_by_template ($binding, "%O%@");
225                         return BER::pretty_print($value);
226                 }
227         }
228         die "Couldn't get info from switch";
229 }
230
231 sub expand_ports {
232         my $in = shift;
233         my @ranges = split /,/, $in;
234         my @ret = ();
235
236         for my $range (@ranges) {
237                 if ($range =~ /^\d+$/) {
238                         push @ret, $range;
239                 } elsif ($range =~ /^(\d+)-(\d+)$/) {
240                         for my $i ($1..$2) {
241                                 push @ret, $i;
242                         }
243                 } else {
244                         die "Couldn't understand '$range' in ports";
245                 }
246         }
247
248         return (sort { $a <=> $b } @ret); 
249 }
250
251 sub mylog {
252         my $msg = shift;
253         my $time = POSIX::ctime(time);
254         $time =~ s/\n.*$//;
255         printf STDERR "[%s] %s\n", $time, $msg;
256 }
257
258 sub switch_exec {
259         my ($cmd, $conn) = @_;
260
261         # Send the command and get data from switch
262 #       $conn->dump_log(*STDOUT);
263         my @data = $conn->cmd($cmd);
264         my @lines = ();
265         foreach my $line (@data) {
266                 # Remove escape-7 sequence
267                 $line =~ s/\x1b\x37//g;
268                 push @lines, $line;
269         }
270
271         return @lines;
272 }
273
274 sub switch_connect {
275         my ($ip) = @_;
276
277         my $conn = new Net::Telnet(     Timeout => $timeout,
278                                         Dump_Log => '/tmp/dumplog-tempfetch',
279                                         Errmode => 'return',
280                                         Prompt => '/es-3024|e(\-)?\d+\-\dsw>/i');
281         my $ret = $conn->open(  Host => $ip);
282         if (!$ret || $ret != 1) {
283                 return (0);
284         }
285         # XXX: Just send the password as text, I did not figure out how to
286         # handle authentication with only password through $conn->login().
287         #$conn->login(  Prompt => '/password[: ]*$/i',
288         #              Name => $password,
289         #              Password => $password);
290         my @data = $conn->cmd($password);
291         # Get rid of banner
292         $conn->get;
293         return $conn;
294 }
295