#!/usr/bin/tclsh
# $Id: rrdScan.tcl,v 1.13 2005/08/02 14:15:50 gdmr Exp $

# Copyright (c) 2002 School of Informatics, University of Edinburgh
# 
# Comments are invited.  Please send to gdmr@dcs.ed.ac.uk.

# Scan a switch, writing out rrd files for each interface on it.
# Command-line parameters are: address, community, rrd-file prefix,
# work-directory.

# Tunable variables
set rrdTool "/usr/bin/rrdtool"
set touch "/bin/touch"

set snmpRetries 2
set snmpTimeout 5

# Load Tnm
#puts "Loading Tnm..."
package require Tnm 3.0
namespace import Tnm::snmp
namespace import Tnm::mib

# Grab command-line parameters
set debug 0
set byName 0
set HC 0
set snmpVersion "SNMPv2c"
foreach a $argv {
   switch -glob -- $a {
      -byName  { set byName 1 }
      -debug   { set debug 1 }
      -HC      { set HC 1 }
      -V1      { set snmpVersion "SNMPv1" }
      -V2      { set snmpVersion "SNMPv2c" }
      -A*      { regexp {^-A(.*)$} $a all address }
      -C*      { regexp {^-C(.*)$} $a all community }
      -R*      { regexp {^-R(.*)$} $a all rrdPrefix }
      -W*      { regexp {^-W(.*)$} $a all workDir }
   }
}
if {![info exists address]} {
   puts stderr "Missing address"
   exit 99
}
if {![info exists community]} {
   puts stderr "Missing community"
   exit 99
}
if {![info exists rrdPrefix]} {
   puts stderr "Missing rrdPrefix"
   exit 99
}
if {![info exists workDir]} {
   puts stderr "Missing workDir"
   exit 99
}
if {$debug} {
   puts "<$address> <$community> <$rrdPrefix> <$workDir>"
}

# Handy error-logging procedure, that outputs a datestamp and
# the name of the program,
proc Error {e} {
   global argv0
   puts stderr "[clock format [clock seconds]] [file tail $argv0]: $e"
}

# Initialise SNMP
set snmpS [snmp generator \
                  -version $snmpVersion \
                  -address $address -community $community \
                  -retries $snmpRetries -timeout $snmpTimeout]

# Process one vbl
proc processOne {vbl} {
   global rrdTool
   global rrdPrefix
   global workDir
   global touch
   global upSince
   global byName
   global debug
   # Extract values from vbl
   set ifIndex         [snmp value $vbl 0]
   set ifType          [snmp value $vbl 1]
   set ifOperStatus    [snmp value $vbl 2]
   set ifInOctets      [snmp value $vbl 3]
   set ifInUcastPkts   [snmp value $vbl 4]
   set ifInNUcastPkts  [snmp value $vbl 5]
   set ifInErrors      [snmp value $vbl 6]
   set ifOutOctets     [snmp value $vbl 7]
   set ifOutUcastPkts  [snmp value $vbl 8]
   set ifOutNUcastPkts [snmp value $vbl 9]
   set ifOutErrors     [snmp value $vbl 10]
   set ifDescr         [snmp value $vbl 11]
   #puts "$ifIndex $ifType $ifOperStatus"
   #puts "   $ifInOctets $ifInUcastPkts $ifInNUcastPkts $ifInErrors"
   #puts "   $ifOutOctets $ifOutUcastPkts $ifOutNUcastPkts $ifOutErrors"
   # Determine the base name for our rrd and last-data files
   if {$byName} {
      set baseName [file join $workDir "${rrdPrefix}_$ifDescr"]
   } else {
      set baseName [file join $workDir "${rrdPrefix}_$ifIndex"]
   }
   set rrdName "$baseName.rrd"
   # Create the rrd if necessary
   if {![file exists $rrdName]} {
      # Start an hour before midnight (GMT)
      set startTime [expr ([clock seconds] / 86400) * 86400 - 3600]
      #puts "rrd starts at [clock format $startTime]"
      set rrdCommand [list exec $rrdTool create $rrdName \
                      --start $startTime --step 300 \
                      DS:ifInOctets:COUNTER:500:0:U \
                      DS:ifInUcastPkts:COUNTER:500:0:U \
                      DS:ifInNUcastPkts:COUNTER:500:0:U \
                      DS:ifInErrors:COUNTER:500:0:U \
                      DS:ifOutOctets:COUNTER:500:0:U \
                      DS:ifOutUcastPkts:COUNTER:500:0:U \
                      DS:ifOutNUcastPkts:COUNTER:500:0:U \
                      DS:ifOutErrors:COUNTER:500:0:U \
                      RRA:AVERAGE:0.5:1:600 \
                      RRA:AVERAGE:0.5:6:700 \
                      RRA:AVERAGE:0.5:24:775 \
                      RRA:AVERAGE:0.5:288:797 \
                      RRA:MAX:0.5:1:600 \
                      RRA:MAX:0.5:6:700 \
                      RRA:MAX:0.5:24:775 \
                      RRA:MAX:0.5:288:797 ]
      if {$debug} {
         puts "Command would be $rrdCommand"
      } else {
         if [catch $rrdCommand why] {
            Error "Couldn't create $rrdName: $why"
            return
         }
      }
   }
   # Check the rrd's last-modify stamp.  If it's more recent than the
   # switch's start time then we're probably OK.  If the switch was
   # booted more recently than the last-modify time then skip this
   # update as the counters will have been reset in the meantime.
   set rrdStamp [file mtime $rrdName]
   #puts "$rrdName: [clock format $rrdStamp]"
   if {$rrdStamp > $upSince} {
      # Update the rrd
      set updates "N"
      append updates ":$ifInOctets"
      append updates ":$ifInUcastPkts"
      append updates ":$ifInNUcastPkts"
      append updates ":$ifInErrors"
      append updates ":$ifOutOctets"
      append updates ":$ifOutUcastPkts"
      append updates ":$ifOutNUcastPkts"
      append updates ":$ifOutErrors"
      #puts "Update $rrdName with $updates"
      if {$debug} {
         puts "RRD update: $rrdName $updates"
      } else {
         if [catch {exec $rrdTool update $rrdName $updates} why] {
            Error "Couldn't update $rrdName: $why"
         }
      }
   } else {
      Error "$rrdName: last-modify before switch startup"
      # Touch the file in lieu of writing the rrds, so that next
      # time around we'll start filling in real data.
      catch {exec $touch $rrdName}
   }
}

# Grab the uptime
set vbl "SNMPv2-MIB!sysUpTime.0"
if [catch {$snmpS get $vbl} vbl] {
   Error "Can't read from $address: $vbl"
   exit 98
}
set sysUpTime [snmp value $vbl]
# Divide by 100 by chopping the last two digits off the end of
# the timeticks value...
set upSecs [string range $sysUpTime 0 [expr [string length $sysUpTime] - 3]]
set upSince [expr [clock seconds] - $upSecs]
#puts "Up since [clock format $upSince]"

# Now scan the interfaces on the switch
set ifRoot [mib oid "IF-MIB!ifIndex"]
if {$HC} {
   set vbl "$ifRoot.0
            IF-MIB!ifType.0
            IF-MIB!ifOperStatus.0
            IF-MIB!ifHCInOctets.0
            IF-MIB!ifHCInUcastPkts.0
            IF-MIB!ifHCInMulticastPkts.0
            IF-MIB!ifInErrors.0
            IF-MIB!ifHCOutOctets.0
            IF-MIB!ifHCOutUcastPkts.0
            IF-MIB!ifHCOutMulticastPkts.0
            IF-MIB!ifOutErrors.0
            IF-MIB!ifDescr.0"
} else {
   set vbl "$ifRoot.0
            IF-MIB!ifType.0
            IF-MIB!ifOperStatus.0
            IF-MIB!ifInOctets.0
            IF-MIB!ifInUcastPkts.0
            IF-MIB!ifInNUcastPkts.0
            IF-MIB!ifInErrors.0
            IF-MIB!ifOutOctets.0
            IF-MIB!ifOutUcastPkts.0
            IF-MIB!ifOutNUcastPkts.0
            IF-MIB!ifOutErrors.0
            IF-MIB!ifDescr.0"
}
while (1) {
   if [catch {$snmpS getnext $vbl} vbl] {
      Error "Can't read from $address: $vbl"
      exit 98
   }
   if {![mib subtree $ifRoot [snmp oid $vbl 0]]} break
      set ifType [snmp value $vbl 1]
      if {![string compare $ifType "ethernetCsmacd"] || \
            ![string compare $ifType "ieee8023adLag"] || \
            ![string compare $ifType "propMultiplexor"] || \
            ![string compare $ifType "ieee80211"]} {
      processOne $vbl
   }
}
