http://qs1969.pair.com?node_id=104326
Category: Networking Code
Author/Contact Info ybiC
Description: ## deprecated by "(code) Net::SNMP, Bandwidth, GnuPlot, PNG, PostScript, Excel" ##

netsnmpiio.pl
Periodically query SNMP-enabled devices for interface (in|out)put octets, and record results in csv and xls outfiles.

Intended for relatively short term spot-tests of individual interfaces.   Other tools like MRTG, SNMPc, and CiscoWorks do fine for large number of ports, or for ongoing monitoring.   Uses Net::SNMP instead of system call to UCD-SNMP's snmpwalk like my earlier efforts.   Mind you, snmpwalk is quite useful - I just wanted to eliminate unecessary dependancies on external libraries.

Thanks to:
tachyon, HamNRye, crazyinsomniac, tilly, lemming, and wog.
Oh yeah, and to some guy named vroom.

As always:
Comments, corrections, and criteque welcome and requested.

Most recent update:
2001-08-22   10:00   Unlink .tmp at end of run, skip Spreadsheet::WriteExcel stuff if module not installed, fix wrong %util calculation, display MBytes transferred, gracefully handle non-responsive (host|OID).

#!/usr/bin/perl -w

# netsnmpiio.pl
# pod at tail


use strict;
use Net::SNMP;


my $target    = shift or Usage();
my $iters     = (shift or 2);
my $delay     = (shift or 30);
my $ifIndex   = (shift or 2);
my $outdir    = (shift or '.');
my $community = (shift or 'public'); 
my $timeout   = 15;
my $port      = 161;
Usage() unless (
   $target    =~ (/\w+/) and
   $iters     =~ (/\d+/) and
   $delay     =~ (/\d+/) and
   $ifIndex   =~ (/\d+/) and
   $outdir    =~ (/.*/) and
   $community =~ (/[\w+\.\/]/)
   );


# ifName, ifSpeed, ifInOctets, ifOutOctets
# ifDescr ".1.3.6.1.2.1.2.2.1.2.$ifIndex",
my @oids = (
   ".1.3.6.1.2.1.31.1.1.1.1.$ifIndex",
   ".1.3.6.1.2.1.2.2.1.5.$ifIndex",
   ".1.3.6.1.2.1.2.2.1.10.$ifIndex",
   ".1.3.6.1.2.1.2.2.1.16.$ifIndex",
   );

my ($mday,$mon,$year) = (localtime)[3..5];
my $ymd = sprintf("%04d%02d%02d",$year+1900,$mon+1,$mday);
my %file = (
   tmp => "$outdir/$target-$ifIndex-$ymd.tmp",
   csv => "$outdir/$target-$ifIndex-$ymd.csv",
   );
$file{xls} = "$outdir/$target-$ifIndex-$ymd.xls" if (my $have_SWE == 1
+);

unless(-d $outdir && -w _) { 
   print "\n\nSorry, $outdir isn't a directory,",
         " or you don't have write perms there.\n\n";
   exit;
   }

my @unlink1 = (
   $file{tmp},
   $file{csv},
   );
push @unlink1, $file{xls} if ($have_SWE == 1);


# Files writable only by this user for security purposes:
# delete pre-existing out file because subsequent writes append:
print "\nStart run of $0\n";
umask oct 133;
for(@unlink1) { Unlink($_, 'verbose'); }


print "  Print csv header...";
open (CSV, ">$file{csv}") or die "Error opening $file{csv}: $!";
print CSV "Date,Time,Target,ifName,Seconds,MBytes,Mbits/s,%Util,\n";
close CSV or die "Error closing $file{csv}: $!";
print "  done!\n";


print "  Query target device...";
# Get down to business:
my $i = 1;
for(1..$iters) {

   open (TMP, ">>$file{tmp}") or die "Error opening $file{tmp}: $!";

   # Localtime for human consumption, epoch for bw calcs.
   # Prepend relevant info to csv line:
   my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
   my $ymdhms = sprintf( "%04d-%02d-%02d,%02d:%02d:%02d",
                 $year+1900,$mon+1,$mday,$hour,$min,$sec);
   my $epochsec = time;
   print(TMP "$ymdhms,$epochsec,$target,$ifIndex,")
      or die "Error printing to $file{tmp}: $!";

   # Here's where da *real* stuf happens: 
   # my ($session, $error) = Net::SNMP->session(
   my $session = Net::SNMP->session(
      -hostname  => $target,
      -community => $community,
      -port      => $port,
      -timeout   => $timeout,
      );
   unless (defined($session)) { print TMP "SNMP SESSION ERROR,"; }
   for my $oid(@oids) {
      if (defined(my $response = $session->get_request($oid))) {
         print(TMP $response->{$oid});
         }
      else { print TMP '0'; }
      print TMP ',';
      }
   $session->close();

   # One line per iteration:
   # End of iteration:
   # delay between iterations, but not after last iteration:
   print TMP "\n";
   close TMP or die "Error closing $file{tmp}: $!";
   sleep $delay unless($i==$iters);
   $i++;
   }
   print "  done!\n";


print "  Munge .tmp to .csv (Mbps and %util)...";
{
   # Contained block to limit 'print csv with $,'.
   # Initialize prior array to 0 to avoid annoying 'not numeric' error
+.
   local $, = ',';
   my @prior = (0) x 9;
   open (TMP, "<$file{tmp}") or die "Error opening $file{tmp}: $!";
   open (CSV, ">>$file{csv}") or die "Error opening $file{csv}: $!";
   # date,time,epochSecs,target,ifIndex,ifName,ifSpeed,ifInOctets,ifOu
+tOctets,
   #  0    1       2       3       4      5       6        7          
+8
   while(<TMP>) {
      chomp;
      my @data = split ',';
      # Subtract epochSecs for deltaTime.
      # Protect against 'divide by zero' error.
      my $dtime = $data[2] - $prior[2];
      next unless $dtime;
      my $inBytes       = $data[7];
      my $outBytes      = $data[8];
      my $priorInBytes  = $prior[7];
      my $priorOutBytes = $prior[8];
      my $speed         = $data[6];
      # Handle counter-roll-over-to-zero in approximate fashion,
      # and prevent 'divide by zero' error if no OID response.
      $priorInBytes  = 1 if(
          ($inBytes  < $priorInBytes) or ($priorInBytes  == 0)
          );
      $priorOutBytes  = 1 if(
          ($outBytes  < $priorOutBytes) or ($priorOutBytes  == 0)
          );
      $speed = 1 if($speed == 0);
      my $recv   = $inBytes - $priorInBytes;
      my $xmit   = $outBytes - $priorOutBytes;
      my $Bytes  = $recv + $xmit;
      my $MBytes = $Bytes / 1048576;
      my $Mbits  = $MBytes * 8;
      my $Mbps   = $Mbits / $dtime;
      # * 100      convert decimal to percent
      # * 1000000  move decimal point to match $speed
      # * 1.048576 bits per Megabit
      my $util   = ($Mbps * 100 * 1000000) / ($speed * 1.048576);
      # Round output, but not @data(next iteration's @prior).
      my $MBytesRound = sprintf("%.3f",$MBytes);
      my $MbpsRound   = sprintf("%.3f",$Mbps);
      my $utilRound   = sprintf("%.3f",$util);
      # Don't output bogus first iteration.
      if($prior[2] > 0) {
         print CSV @data[0,1,3,5,],
        "$dtime,$MBytesRound,$MbpsRound,$utilRound,\n";
         # print CSV @data[0,1,3,5,], "$dtime,$MBytes,$Mbps,$util,\n";
         }
      # Set current values for 'prior' on next iteration.
      @prior = @data;
      }
   close (TMP) or die "Error closing $file{tmp}: $!";
   close (CSV) or die "Error closing $file{csv}: $!";
   }
   print "  done!\n";

# Munge csv to xls *if* Spreadsheet::WriteExcel module installed.
BEGIN {
   $have_SWE = 0;
   eval { require Spreadsheet::WriteExcel };
   unless ($@) {
      Spreadsheet::WriteExcel->import();
      $have_SWE =1;
      }
   }
if ($have_SWE == 1) {
   print "  Create xls outfile from csv...";
   open (CSVFILE, $file{csv}) or die "Error opening $file{csv}: $!";
   my $workbook  = Spreadsheet::WriteExcel -> new($file{xls});
   my $worksheet = $workbook -> addworksheet();
   my $row      = 0;
   while (<CSVFILE>) {
      chomp;
      my @field = split(',', $_);
      my $column = 0;
      foreach my $token (@field) {
         $worksheet -> write($row, $column, $token);
         $column++;
         }
      $row++;
      }
   my $format1 = $workbook -> addformat();
      $format1 -> set_bold();
      $format1 -> set_align('center');
      $format1 -> set_bg_color('tan');
      # $format1 -> set_border();
   $worksheet -> set_row(0, undef, $format1);
   $workbook  -> close() or die "Error closing $workbook: $!";
   print "  done!\n";
   }


# Wrap it all up:
my @unlink2 = (
   $file{tmp},
   );
for(@unlink2) { Unlink($_, 'verbose'); }
print "Completed run of $0.\n";
print "  $file{xls}\n" if ($have_SWE == 1);
print "  $file{csv}\n",
      ;
      # "  $file{tmp}\n",
exit 0;

######################################################################
+#####
# Delete outfiles from prior run (if same name as this run)
sub Unlink {
   my $file = $_[0];
   my $echo = $_[1];
   if (-e $file && -w _) {
      print "  Unlink $file..." if ($echo eq 'verbose');
      unlink $file or die "Error unlinking $file: $!";
      print "  *poof*\n" if ($echo eq 'verbose');
      }
   }
######################################################################
+#####
sub Usage {
   print "


Ooot!
You forgot to enter necessary information, or entered bad information!

Usage:
netsnmpiio.pl target iterations delay ifIndex outdir ROcommunitystring

target:  an IPaddress, DNS name, or FQDN.
iterations: how many queries you wish to run.            (default 2)
delay: seconds to wait between iterations.              (default 30)
ifIndex: SNMP parameter specifying port or interface.    (default 2)
outdir: destination dir for outfile.  No trailing '/'. (default '.')
ROcoumminitystring: SNMP string.                  (default 'public')

Example:
netsnmpiio.pl routerC 30 600 7 MyCommunityString /datadir



";
   exit 1;
   }
######################################################################
+#####


=head1 NAME

 netsnmpiio.pl

=head1 SYNOPSIS

 Query SNMP-enabled devices for interface (in|out)put octets.
 Csv outfile ~ 70KB per iteration.
 "clear counters" before running; CatOSv5 rollover > 4,000,000,000

 Intended for spot-tests of individual interfaces.
 Other tools (MRTG, SNMPc, CWSI) do well for large number of ports.

 netsnmpiio.pl target iterations delay ifIndex outdir ROcommunitystrin
+g

 target:  an IPaddress, DNS name, or FQDN.
 iterations: how many queries you wish to run.         (default of 2)
 delay: seconds to wait between iterations.              (default 30)
 ifIndex: SNMP parameter specifying port or interface. (default of 2)
 outdir: destination dir for outfile.  No trailing '/'. (default '.')
 ROcoumminitystring: SNMP string.                  (default 'public')

 Example:
 netsnmpiio.pl hq5500 30 600 333 MyCommunityString /datadir

 Assorted conversions:
 144 iterations  600 sec delay = 24 hours
 432 iterations  600 sec delay = 3 days
 1008 iterations 600 sec delay = 7 days
 2016 iterations 600 sec delay = 14 days

 100Mbps = 12.5MBps

 1GB     = 8,589,934,592 bits (1,073,741,824 * 8)
 1MB     = 8,388,608 bits     (1,048,576 * 8)
 1KB     = 8,192 bits         (1024 * 8)

 1Gb     = 1,073,741,824 bits (1024-e3)
 1Mb     = 1,048,576 bits     (1024-e2)
 1Kb     = 1,024 bits         (1024-e1)

 ifInOctets+ifOutOctets      = Bytes
 ((Bytes*8)/1024)/seconds    = Kbps
 ((Bytes*8)/1048576)/seconds = Mbps

=head1 TESTED

 Net::SNMP 3.6
   Perl 5.00503      Debian 2.2r3
   Perl 5.00601      Cygwin on Win2kPro
   ActivePerl 5.6.1  Win2kPro

=head1 UPDATED

 2001-08-22   10:00 CDT
   Unlink .tmp at end of run.
   Confirm counter-roll-over-to-zero Does The Right Thing.
 2001-08-17   10:30 CDT
   No xls outfile if Spreadsheet::WriteExcel module not installed.
 2001-08-15   18:10 CDT
   Fix broken %util calculation.
   Correct minor tyops.
 2001-08-14   21:15 CDT
   Display MBytes transferred.
   Gracefully handle non-responsive (host|OID):
     (MBytes|Mbps|%Util) = -0.000
   Snmp query syntax cleaner.
   Don't print snmp query or session errors.
   Fix incorrect Bps calculations.
   Create xls outfile from csv.
   Correct error in Usage and pod.
 2001-08-13   10:35 CDT
   Gracefully handle counter-roll-over-to-zero;
     ~4,000,000,000 on CatOS 5.
   Use ifName instead of ifDescr
     Cisco CatOS 5
     report 'blade #/port #' instead of '10/100 utp ethernet (cat 3/5)
+'.
 2001-08-12   21:20 CDT
   Test with ActivePerl on Win2k.
   Fix sundry tyops.
   Post to PerlMonks networking code (requesting critique).
   Net::SNMP instead of UCD-SNMP system call:
     Advantages:
       Reduce non-Perl requirements.

       Eliminate annoying 'one-less-than-actual-ifIndex'.
       About one second quicker response.
     Drawbacks:
       Must use numeric OIDs.
       UCD SNMP utilities still quite handy.
   Initialize 'prior' array at '0' to
     eliminate annoying 'not numeric' message.
   Fix bad conversion math in pod.
   Add ifDescr.
 2001-08-11   22:50 CDT
   Add example temp+csv output to pod.
   Round Bps to 0 decimal places.
   Round %util to 4 decimal places (100ths of %).
   Utilization percentage calculations, corrected for MegaBytes.
   Bypass bogus first Bps output since no prior epochSec.
   Bps calculations from raw SNMP data.
   Query ifSpeed for %util calculations.
   Defaults for all input values except target.
   Sanity-check $outdir: letters, numbers, dots, dashes, underscores.
   Filetest for write in outdir and outdir *is* directory.
   Simplify iteration localtime syntax.
   Cleaner syntax for no sleep after last iteration.
   Debug errant commas in csv.
   Unsubify snmp query localtime+time since only called once.
   @raw array instead of initial temp file.
   Filename of target-ifIndex-yyyymmdd.csv.
   Add hostname,oid,ifIndex to csv output.
   Use PcTime return values (print the values in main).
   Add $outdir.
 2001-08-10   21:35 CDT
   Sanity-check input for (wordchars|numbers) as appropriate.
   More informative Usage().
   Input from commandline instead of hardcoded.
   Efforts to improve data structure.
   No sleep after last iteration.
 2001-08-09   16:10 CDT
   Leading-zero padded time/date in outfiles.
   yyyymmdd filenames w/sprintf.
 2001-08-08   16:00 CDT
   Initial working code.

=head1 TODOS

 Use hash instead of array for OIDs:
   OID name for key, numeric OID for value.
 Use DBI or MySQL or Postgres instead of csv.
 Use Text::xSV to parse csv.
 Bar graph output:
   GD::Graph + GD.pm + libgd
   Debian stable libgd1g too old for current GD.pm
     but needed by Webalyzer.  8^( 
 Use Pod:Usage to automagically synchronize pod with Usage().

=head1 AUTHOR

 ybiC

=head1 CREDITS

 Thanks to tachyon, HamNRye, tilly, lemming, wog, and crazyinsomniac.
 Oh yeah, and to some guy named vroom.  ;^)

=cut