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