1#!/usr/local/bin/perl -w 2###################################################################### 3### Observe interface counters in real time. 4###################################################################### 5### Copyright (c) 1995-2000, Simon Leinen. 6### 7### This program is free software; you can redistribute it under the 8### "Artistic License" included in this distribution (file "Artistic"). 9###################################################################### 10### Author: Simon Leinen <simon@switch.ch> 11### Date Created: 21-Feb-1999 12### 13### Real-time full-screen display of the octet and (Cisco-specific) 14### CRC error counters on interfaces of an SNMP-capable node 15### 16### Description: 17### 18### Call this script with "-h" to learn about command usage. 19### 20### The script will poll the RFC 1213 ifTable at specified intervals 21### (default is every five seconds). 22### 23### For each interface except for those that are down, a line is 24### written to the terminal which lists the interfaces name (ifDescr), 25### well as the input and output transfer rates, as computed from the 26### deltas of the respective octet counts since the last sample. 27### 28### "Alarms" 29### 30### When an interface is found to have had CRC errors in the last 31### sampling interval, or only output, but no input traffic, it is 32### shown in inverse video. In addition, when a link changes state 33### (from normal to inverse or vice versa), a bell character is sent 34### to the terminal. 35### 36### Miscellaneous 37### 38### Note that on the very first display, the actual SNMP counter 39### values are displayed. THOSE ABSOLUTE COUNTER VALUES HAVE NO 40### DEFINED SEMANTICS WHATSOEVER. However, in some versions of 41### Cisco's software, the values seem to correspond to the total 42### number of counted items since system boot (modulo 2^32). This can 43### be useful for certain kinds of slowly advancing counters (such as 44### CRC errors, hopefully). 45### 46### The topmost screen line shows the name of the managed node, as 47### well as a few hard-to-explain items I found useful while debugging 48### the script. 49### 50### Please send any patches and suggestions for improvement to the 51### author (see e-mail address above). Hope you find this useful! 52### 53### Original Purpose: 54### 55### This script should serve as an example of how to "correctly" 56### traverse the rows of a table. This functionality is implemented in 57### the map_table() subroutine. The example script displays a few 58### columns of the RFC 1213 interface table and Cisco's locIfTable. The 59### tables share the same index, so they can be handled by a single 60### invocation of map_table(). 61### 62require 5.003; 63 64use strict; 65 66use BER; 67use SNMP_Session "0.96"; # requires map_table_4() and ipv4only 68use POSIX; # for exact time 69use Curses; 70use Math::BigInt; 71use Math::BigFloat; 72 73### Forward declarations 74sub out_interface ($$$$$$@); 75sub pretty_ps ($$); 76sub usage ($ ); 77 78my $version = '1'; 79 80my $desired_interval = 5.0; 81 82my $switch_engine_p = 0; 83 84my $all_p = 0; 85 86my $port = 161; 87 88my $max_repetitions = 0; 89 90my $suppress_output = 0; 91 92my $suppress_curses = 0; 93 94my $debug = 0; 95 96my $show_out_discards = 0; 97 98my $cisco_p = 0; 99 100## Whether to use 64-bit counters. Can be requested with `-l' option. 101my $counter64_p = 0; 102 103## Whether to select IPv4-only in open(). Can be set using `-4' option. 104my $ipv4_only_p = 0; 105 106my $host; 107 108my $community; 109 110my $use_getbulk_p = 1; 111 112while (defined $ARGV[0]) { 113 if ($ARGV[0] =~ /^-v/) { 114 if ($ARGV[0] eq '-v') { 115 shift @ARGV; 116 usage (1) unless defined $ARGV[0]; 117 } else { 118 $ARGV[0] = substr($ARGV[0], 2); 119 } 120 if ($ARGV[0] eq '1') { 121 $version = '1'; 122 } elsif ($ARGV[0] eq '2c' or $ARGV[0] eq '2') { 123 $version = '2c'; 124 } else { 125 usage (1); 126 } 127 } elsif ($ARGV[0] =~ /^-m/) { 128 if ($ARGV[0] eq '-m') { 129 shift @ARGV; 130 usage (1) unless defined $ARGV[0]; 131 } else { 132 $ARGV[0] = substr($ARGV[0], 2); 133 } 134 if ($ARGV[0] =~ /^[0-9]+$/) { 135 $max_repetitions = $ARGV[0]; 136 } else { 137 usage (1); 138 } 139 } elsif ($ARGV[0] =~ /^-p/) { 140 if ($ARGV[0] eq '-p') { 141 shift @ARGV; 142 usage (1) unless defined $ARGV[0]; 143 } else { 144 $ARGV[0] = substr($ARGV[0], 2); 145 } 146 if ($ARGV[0] =~ /^[0-9]+$/) { 147 $port = $ARGV[0]; 148 } else { 149 usage (1); 150 } 151 } elsif ($ARGV[0] =~ /^-t/) { 152 if ($ARGV[0] eq '-t') { 153 shift @ARGV; 154 usage (1) unless defined $ARGV[0]; 155 } else { 156 $ARGV[0] = substr($ARGV[0], 2); 157 } 158 if ($ARGV[0] =~ /^[0-9]+(\.[0-9]+)?$/) { 159 $desired_interval = $ARGV[0]; 160 } else { 161 usage (1); 162 } 163 } elsif ($ARGV[0] eq '-B') { 164 $use_getbulk_p = 0; 165 } elsif ($ARGV[0] eq '-s') { 166 $switch_engine_p = 1; 167 } elsif ($ARGV[0] eq '-a') { 168 $all_p = 1; 169 } elsif ($ARGV[0] eq '-c') { 170 $cisco_p = 1; 171 } elsif ($ARGV[0] eq '-l') { 172 $counter64_p = 1; 173 } elsif ($ARGV[0] eq '-n') { 174 $suppress_output = 1; 175 $suppress_curses = 1; 176 } elsif ($ARGV[0] eq '-C') { 177 $suppress_output = 0; 178 $suppress_curses = 1; 179 } elsif ($ARGV[0] eq '-d') { 180 $suppress_output = 0; 181 $suppress_curses = 1; 182 $debug = 1; 183 } elsif ($ARGV[0] eq '-D') { 184 $show_out_discards = 1; 185 } elsif ($ARGV[0] eq '-4') { 186 $ipv4_only_p = 1; 187 } elsif ($ARGV[0] eq '-h') { 188 usage (0); 189 exit 0; 190 } elsif ($ARGV[0] =~ /^-/) { 191 usage (1); 192 } else { 193 if (!defined $host) { 194 $host = $ARGV[0]; 195 } elsif (!defined $community) { 196 $community = $ARGV[0]; 197 } else { 198 usage (1); 199 } 200 } 201 shift @ARGV; 202} 203defined $host or usage (1); 204defined $community or $community = 'public'; 205usage (1) if $#ARGV >= $[; 206 207my $ifDescr = [1,3,6,1,2,1,2,2,1,2]; 208my $ifAdminStatus = [1,3,6,1,2,1,2,2,1,7]; 209my $ifOperStatus = [1,3,6,1,2,1,2,2,1,8]; 210my $ifInOctets = [1,3,6,1,2,1,2,2,1,10]; 211my $ifOutOctets = [1,3,6,1,2,1,2,2,1,16]; 212my $ifInUcastPkts = [1,3,6,1,2,1,2,2,1,11]; 213my $ifOutUcastPkts = [1,3,6,1,2,1,2,2,1,17]; 214my $ifOutDiscards = [1,3,6,1,2,1,2,2,1,19]; 215my $ifAlias = [1,3,6,1,2,1,31,1,1,1,18]; 216## Counter64 variants 217my $ifHCInOctets = [1,3,6,1,2,1,31,1,1,1,6]; 218my $ifHCOutOctets = [1,3,6,1,2,1,31,1,1,1,10]; 219## Cisco-specific variables enabled by `-c' option 220my $locIfInCRC = [1,3,6,1,4,1,9,2,2,1,1,12]; 221my $locIfOutCRC = [1,3,6,1,4,1,9,2,2,1,1,12]; 222 223my $cseL3SwitchedTotalPkts = [1,3,6,1,4,1,9,9,97,1,4,1,1,1]; 224my $cseL3SwitchedTotalOctets = [1,3,6,1,4,1,9,9,97,1,4,1,1,2]; 225my $cseL3CandidateFlowHits = [1,3,6,1,4,1,9,9,97,1,4,1,1,3]; 226my $cseL3EstablishedFlowHits = [1,3,6,1,4,1,9,9,97,1,4,1,1,4]; 227my $cseL3ActiveFlows = [1,3,6,1,4,1,9,9,97,1,4,1,1,5]; 228my $cseL3FlowLearnFailures = [1,3,6,1,4,1,9,9,97,1,4,1,1,6]; 229my $cseL3IntFlowInvalids = [1,3,6,1,4,1,9,9,97,1,4,1,1,7]; 230my $cseL3ExtFlowInvalids = [1,3,6,1,4,1,9,9,97,1,4,1,1,8]; 231 232my $clock_ticks = POSIX::sysconf( &POSIX::_SC_CLK_TCK ); 233 234my $win = new Curses 235 unless $suppress_curses; 236 237my %old; 238my $sleep_interval = $desired_interval + 0.0; 239my $interval; 240my $linecount; 241 242sub rate_32 ($$$@) { 243 my ($old, $new, $interval, $multiplier) = @_; 244 $multiplier = 1 unless defined $multiplier; 245 my $diff = $new-$old; 246 if ($diff < 0) { 247 $diff += (2**32); 248 } 249 return $diff / $interval * $multiplier; 250} 251 252sub rate_64 ($$$@) { 253 my ($old, $new, $interval, $multiplier) = @_; 254 $multiplier = 1 unless defined $multiplier; 255 return 0 if $old == $new; 256 my $diff = Math::BigInt->new ($new-$old); 257 if ($diff < 0) { 258 $diff = $diff->add (2**64); 259 } 260 warn "rate_64 ($old, $new, $interval, $multiplier)\n" 261 if $debug; 262 warn " diff: $diff\n" 263 if $debug; 264 ## hrm. Why is this so complicated? 265 ## I want a real programming language (such as Lisp). 266 my $result = new Math::BigFloat ($diff->bnorm ()); 267 warn " result: $result\n" 268 if $debug; 269 $result /= $interval; 270 warn " result: $result\n" 271 if $debug; 272 $result *= $multiplier; 273 warn " result: $result\n" 274 if $debug; 275 return $result; 276} 277 278sub rate ($$$$@) { 279 my ($old, $new, $interval, $counter64_p, $multiplier) = @_; 280 $multiplier = 1 unless defined $multiplier; 281 return $counter64_p 282 ? rate_64 ($old, $new, $interval, $multiplier) 283 : rate_32 ($old, $new, $interval, $multiplier); 284} 285 286sub rate_or_0 ($$$@) { 287 my ($old, $new, $interval, $counter64_p, $multiplier) = @_; 288 $counter64_p = 0 unless defined $counter64_p; 289 $multiplier = 1 unless defined $multiplier; 290 return defined $new 291 ? rate ($old, $new, $interval, $counter64_p, $multiplier) 292 : 0; 293} 294 295sub out_interface ($$$$$$@) { 296 my ($index, $descr, $admin, $oper, $in, $out); 297 my ($crc, $comment); 298 my ($drops); 299 my ($clock) = POSIX::times(); 300 my $alarm = 0; 301 302 ($index, $descr, $admin, $oper, $in, $out, $comment, @_) = @_; 303 ($crc, @_) = @_ if $cisco_p; 304 ($drops, @_) = @_ if $show_out_discards; 305 306 grep (defined $_ && ($_=pretty_print $_), 307 ($descr, $admin, $oper, $in, $out, $crc, $comment, $drops)); 308 $win->clrtoeol () 309 unless $suppress_curses; 310 return unless $all_p || defined $oper && $oper == 1; # up 311 return unless defined $in && defined $out; 312 ## Suppress interfaces called "unrouted VLAN..." 313 return if $descr =~ /^unrouted VLAN/; 314 if (!defined $old{$index}) { 315 if ($suppress_output) { 316 # do nothing 317 } elsif ($suppress_curses) { 318 printf STDOUT ("%5d %-24s %10s %10s", 319 $index, 320 defined $descr ? $descr : '', 321 defined $in ? $in : '-', 322 defined $out ? $out : '-'); 323 } else { 324 $win->addstr ($linecount, 0, 325 sprintf ("%5d %-24s %10s %10s", 326 $index, 327 defined $descr ? $descr : '', 328 defined $in ? $in : '-', 329 defined $out ? $out : '-')); 330 } 331 if ($show_out_discards) { 332 if ($suppress_output) { 333 # do nothing 334 } elsif ($suppress_curses) { 335 printf STDOUT (" %8s", defined $drops ? $drops : '-'); 336 } else { 337 $win->addstr (sprintf (" %8s", 338 defined $drops ? $drops : '-')); 339 } 340 } 341 if ($cisco_p) { 342 if ($suppress_output) { 343 # do nothing 344 } elsif ($suppress_curses) { 345 printf STDOUT (" %10s", defined $crc ? $crc : '-'); 346 } else { 347 $win->addstr (sprintf (" %10s", 348 defined $crc ? $crc : '-')); 349 } 350 } 351 if ($suppress_output) { 352 # do nothing 353 } elsif ($suppress_curses) { 354 printf STDOUT (" %s", defined $comment ? $comment : ''); 355 } else { 356 $win->addstr (sprintf (" %s", defined $comment ? $comment : '')); 357 } 358 print "\n" if !$suppress_output and $suppress_curses; 359 } else { 360 my $old = $old{$index}; 361 362 $interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks; 363 my $d_in = rate_or_0 ($old->{'in'}, $in, $interval, $counter64_p, 8); 364 my $d_out = rate_or_0 ($old->{'out'}, $out, $interval, $counter64_p, 8); 365 my $d_drops = rate_or_0 ($old->{'drops'}, $drops, $interval, 0); 366 my $d_crc = rate_or_0 ($old->{'crc'}, $crc, $interval, 0); 367 $alarm = ($d_crc != 0) 368 || 0 && ($d_out > 0 && $d_in == 0); 369 print STDERR "\007" if $alarm && !$old->{'alarm'}; 370 print STDERR "\007" if !$alarm && $old->{'alarm'}; 371 $win->standout() if $alarm && !$suppress_curses; 372 if ($suppress_output) { 373 # do nothing 374 } elsif ($suppress_curses) { 375 printf STDOUT ("%5d %-24s %s %s", 376 $index, 377 defined $descr ? $descr : '', 378 pretty_ps ($in, $d_in), 379 pretty_ps ($out, $d_out)); 380 } else { 381 $win->addstr ($linecount, 0, 382 sprintf ("%5d %-24s %s %s", 383 $index, 384 defined $descr ? $descr : '', 385 pretty_ps ($in, $d_in), 386 pretty_ps ($out, $d_out))); 387 } 388 if ($show_out_discards) { 389 if ($suppress_output) { 390 # do nothing 391 } elsif ($suppress_curses) { 392 printf STDOUT (" %8.1f %s", defined $drops ? $d_drops : 0); 393 } else { 394 $win->addstr (sprintf (" %8.1f %s", 395 defined $drops ? $d_drops : 0)); 396 } 397 } 398 if ($cisco_p) { 399 if ($suppress_output) { 400 # do nothing 401 } elsif ($suppress_curses) { 402 printf STDOUT (" %10.1f", defined $crc ? $d_crc : 0); 403 } else { 404 $win->addstr (sprintf (" %10.1f", 405 defined $crc ? $d_crc : 0)); 406 } 407 } 408 if ($suppress_output) { 409 # do nothing 410 } elsif ($suppress_curses) { 411 printf STDOUT (" %s", defined $comment ? $comment : ''); 412 } else { 413 $win->addstr (sprintf (" %s", 414 defined $comment ? $comment : '')); 415 } 416 $win->standend() if $alarm && !$suppress_output; 417 print "\n" if !$suppress_output and $suppress_curses; 418 } 419 $old{$index} = {'in' => $in, 420 'out' => $out, 421 'crc' => $crc, 422 'drops' => $drops, 423 'clock' => $clock, 424 'alarm' => $alarm}; 425 ++$linecount; 426 $win->refresh () 427 unless $suppress_output; 428} 429 430sub out_switching_engine ($$$$$$@) { 431 my ($index, 432 $pkts, $octets, 433 $candidate_flow_hits, 434 $established_flow_hits, 435 $active_flows, 436 $flow_learn_failures, 437 $int_flow_invalids, 438 $ext_flow_invalids) = @_; 439 my ($clock) = POSIX::times(); 440 my $alarm = 0; 441 442 grep (defined $_ && ($_=pretty_print $_), 443 ($pkts, $octets, 444 $candidate_flow_hits, 445 $established_flow_hits, 446 $active_flows, 447 $flow_learn_failures, 448 $int_flow_invalids, 449 $ext_flow_invalids)); 450 warn "RETRIEVED: pkts: $pkts\noctets: $octets\n" 451 if $debug; 452 $win->clrtoeol () 453 unless $suppress_curses; 454 return unless defined $pkts and defined $octets; 455 ## Suppress interfaces called "unrouted VLAN..." 456 if (!defined $old{$index}) { 457 if ($suppress_output) { 458 # do nothing 459 } elsif ($suppress_curses) { 460 printf STDOUT ("%5d %10s %10s\n", 461 $index, 462 defined $pkts ? $pkts : '-', 463 defined $octets ? $octets : '-'); 464 } else { 465 $win->addstr ($linecount, 0, 466 sprintf ("%5d %10s %10s", 467 $index, 468 defined $pkts ? $pkts : '-', 469 defined $octets ? $octets : '-')); 470 } 471 } else { 472 my $old = $old{$index}; 473 474 $interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks; 475 my $d_pkts = rate_or_0 ($old->{'pkts'}, $pkts, $interval, 0); 476 my $d_octets = rate_or_0 ($old->{'octets'}, $octets, $interval, 1, 8); 477 warn "RATE: pkts: $d_pkts\nbits: $d_octets\n" 478 if $debug; 479 $alarm = 0; 480 print STDERR "\007" if $alarm && !$old->{'alarm'}; 481 print STDERR "\007" if !$alarm && $old->{'alarm'}; 482 $win->standout() if $alarm && !$suppress_curses; 483 if ($suppress_output) { 484 # do nothing 485 } elsif ($suppress_curses) { 486 printf STDOUT ("%2d %s %s", 487 $index, 488 pretty_ps ($pkts, $d_pkts), 489 pretty_ps ($octets, $d_octets)); 490 } else { 491 $win->addstr ($linecount, 0, 492 sprintf ("%2d %s %s", 493 $index, 494 pretty_ps ($pkts, $d_pkts), 495 pretty_ps ($octets, $d_octets))); 496 } 497 $win->standend() if $alarm && !$suppress_curses; 498 print "\n" if !$suppress_output and $suppress_curses; 499 } 500 $old{$index} = {'pkts' => $pkts, 501 'octets' => $octets, 502 'clock' => $clock, 503 'alarm' => $alarm}; 504 ++$linecount; 505 $win->refresh () 506 unless $suppress_curses; 507} 508 509sub pretty_ps ($$) { 510 my ($count, $bps) = @_; 511 if (! defined $count) { 512 return ' - '; 513 } elsif ($bps > 1000000) { 514 return sprintf ("%8.4f M", $bps/1000000); 515 } elsif ($bps > 1000) { 516 return sprintf ("%9.1fk", $bps/1000); 517 } else { 518 return sprintf ("%10.0f", $bps); 519 } 520} 521 522$win->erase () 523 unless $suppress_curses; 524my $session = 525 ($version eq '1' ? SNMPv1_Session->open ($host, $community, $port, undef, undef, undef, undef, $ipv4_only_p) 526 : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, $port, undef, undef, undef, undef, $ipv4_only_p) 527 : die "Unknown SNMP version $version") 528 || die "Opening SNMP_Session"; 529$session->debug (1) if $debug; 530$use_getbulk_p = 0 if $version eq '1'; 531$session->{'use_getbulk'} = 0 unless $use_getbulk_p; 532 533### max_repetitions: 534### 535### We try to be smart about the value of $max_repetitions. Starting 536### with the session default, we use the number of rows in the table 537### (returned from map_table_4) to compute the next value. It should 538### be one more than the number of rows in the table, because 539### map_table needs an extra set of bindings to detect the end of the 540### table. 541### 542$max_repetitions = $session->default_max_repetitions 543 unless $max_repetitions; 544while (1) { 545 unless ($suppress_output) { 546 if ($suppress_curses) { 547 printf STDOUT ("interval: %4.1fs %d reps\n", 548 $interval || $desired_interval, 549 $max_repetitions); 550 } else { 551 $win->addstr (0, 0, sprintf ("%-20s interval %4.1fs %d reps", 552 $host, 553 $interval || $desired_interval, 554 $max_repetitions)); 555 $win->standout(); 556 $win->addstr (1, 0, 557 sprintf (("%5s %-24s %10s %10s"), 558 "index", "name", 559 "bits/s", "bits/s")); 560 if ($show_out_discards) { 561 $win->addstr (sprintf ((" %8s"), 562 "drops/s")); 563 } 564 if ($cisco_p) { 565 $win->addstr (sprintf ((" %10s"), "pkts/s")); 566 } 567 $win->addstr (sprintf ((" %s"), "description")); 568 $win->addstr (2, 0, 569 sprintf (("%2s %-24s %10s %10s"), 570 "", "", 571 "in", "out")); 572 if ($show_out_discards) { 573 $win->addstr (sprintf ((" %8s"), 574 "")); 575 } 576 if ($cisco_p) { 577 $win->addstr (2, 0, 578 sprintf ((" %10s %s"), 579 "CRC", 580 "")); 581 } 582 $win->clrtoeol (); 583 $win->standend(); 584 } 585 } 586 $linecount = 3; 587 my @oids; 588 589 if ($switch_engine_p) { 590 @oids = ( 591 $cseL3SwitchedTotalPkts, 592 $cseL3SwitchedTotalOctets, 593 $cseL3CandidateFlowHits, 594 $cseL3EstablishedFlowHits, 595 $cseL3ActiveFlows, 596 $cseL3FlowLearnFailures, 597 $cseL3IntFlowInvalids, 598 $cseL3ExtFlowInvalids 599 ); 600 } else { 601 @oids = ($ifDescr,$ifAdminStatus,$ifOperStatus); 602 if ($counter64_p) { 603 @oids = (@oids,$ifHCInOctets,$ifHCOutOctets); 604 } else { 605 @oids = (@oids,$ifInOctets,$ifOutOctets); 606 } 607 @oids = (@oids,$ifAlias); 608 if ($cisco_p) { 609 push @oids, $locIfInCRC; 610 } 611 if ($show_out_discards) { 612 push @oids, $ifOutDiscards; 613 } 614 } 615 my $calls = 616 $switch_engine_p 617 ? $session->map_table_4 618 (\@oids, \&out_switching_engine, $max_repetitions) 619 : $session->map_table_4 620 (\@oids, \&out_interface, $max_repetitions); 621 $win->clrtobot (), $win->refresh () 622 unless $suppress_curses; 623 $max_repetitions = $calls + 1 624 if $calls > 0; 625 $sleep_interval -= ($interval - $desired_interval) 626 if defined $interval; 627 select (undef, undef, undef, $sleep_interval); 628} 6291; 630 631sub usage ($) { 632 warn <<EOM; 633Usage: $0 [-t secs] [-v (1|2c)] [-c] [-l] [-m max] [-4] [-p port] host [community] 634 $0 -h 635 636 -h print this usage message and exit. 637 638 -c also use Cisco-specific variables (locIfInCrc) 639 640 -l use 64-bit counters (requires SNMPv2 or higher) 641 642 -t secs specifies the sampling interval. Defaults to 5 seconds. 643 644 -v version can be used to select the SNMP version. The default 645 is SNMPv1, which is what most devices support. If your box 646 supports SNMPv2c, you should enable this by passing "-v 2c" 647 to the script. SNMPv2c is much more efficient for walking 648 tables, which is what this tool does. 649 650 -B do not use get-bulk 651 652 -m max specifies the maxRepetitions value to use in getBulk requests 653 (only relevant for SNMPv2c). 654 655 -4 use only IPv4 addresses, even if host also has an IPv6 656 address. Use this for devices that are IPv6-capable 657 but whose SNMP agent doesn\'t listen to IPv6 requests. 658 659 -m port can be used to specify a non-standard UDP port of the SNMP 660 agent (the default is UDP port 161). 661 662 host hostname or IP address of a router 663 664 community SNMP community string to use. Defaults to "public". 665EOM 666 exit (1) if $_[0]; 667} 668