#!/bin/sh
# Tcl sees the next lines as an assignment to variable `kludge'.
# For sh, the two shifts cancel the effect of the set, and then we
# run scotty on this script.

set kludge { $*
    shift
    shift
    if test -f ../scotty ; then
      exec ../scotty -nf $0 $*
    else
      exec scotty -nf $0 $*
    fi
}

##
## A small utility to test if a given ifType is full duplex or not.
## Note, IANAifType (RFC 1573) has somewhat different encodings
## than RFC 1213. We use RFC 1213 style.
##

proc FullDuplex { ifType } {

    set fullDuplex {
	regular1822 hdh1822 ddn-x25 rfc877-x25 lapb sdlc ds1 e1 
	basicISDN primaryISDN propPointToPointSerial ppp slip ds3 sip 
	frame-relay
    }

    return [expr [lsearch $fullDuplex $ifType] < 0]
}

##
## Calculate the interface utilisation. This is done using the formula
##
## util = ( 8 * ( delta (ifInOctets, t1, t0) 
##              + delta (ifOutOctets, t1, t0) ) / (t1 - t0) ) / ifSpeed
##
## This formula returns incorrect results for full-duplex point to point
## links. In this case, the following formula should be used:
##
## util = ( 8 * max ( delta (ifInOctets, t1, t0) ,
##                    delta (ifOutOctets, t1, t0) ) / (t1 - t0) ) / ifSpeed
##
## See Simple Times, 1(5), November/December, 1992 for more details.
##

proc GetIfLoad {} {
    set job [job current]
    array set cx [$job attribute status]

    set ifIndex $cx(ifIndex)
    set vbl [$cx(session) get "sysUpTime.0 ifOperStatus.$ifIndex \
	ifInOctets.$ifIndex ifOutOctets.$ifIndex"]
    
    set sysUpTime    [mib scan sysUpTime [lindex [lindex $vbl 0] 2]]
    set ifOperStatus [lindex [lindex $vbl 1] 2]
    set ifInOctets   [lindex [lindex $vbl 2] 2]
    set ifOutOctets  [lindex [lindex $vbl 3] 2]
    
    # be careful with Tcl's broken arithmetic
    
    if {[catch {expr $ifInOctets - $cx(ifInOctets)} deltaIn]} {
	set deltaIn  [expr double($ifInOctets) - $cx(ifInOctets)]
    }
    if {[catch {expr $ifOutOctets - $cx(ifOutOctets)} deltaOut]} {
	set deltaOut [expr double($ifOutOctets) - $cx(ifOutOctets)]
    }
    
    if {$cx(duplex)} {
	set delta [expr $deltaIn > $deltaOut ? $deltaIn : $deltaOut]
    } else {
	set delta [expr $deltaIn + $deltaOut]
    }
    
    if {$sysUpTime > $cx(sysUpTime)} {
	set secs [expr ($sysUpTime - $cx(sysUpTime)) / 100.0]
	set val  [expr (8.0 * $delta / $secs) / $cx(ifSpeed) * 100]
    } else {
	set val 0
    }
    
    puts $cx(file) [format "%s %3d %5.2f %% %6s (%s)" \
	    [getclock] $ifIndex $val $ifOperStatus $cx(ifDescr)]
    flush $cx(file)
    
    set cx(sysUpTime)   $sysUpTime
    set cx(ifInOctets)  $ifInOctets
    set cx(ifOutOctets) $ifOutOctets
    $job attribute status [array get cx]
}

##
## The following procedure walks the ifTable and starts an interface 
## load monitoring procedure for every interface. We retrieve some 
## initial status information from the agent to initialize the monitor
## jobs. We store the job parameters in a global array named cx.
##

proc MonitorIfLoad { f s interval iterations } {

    global scotty_version

    puts $f "# Interface load (based on scotty $scotty_version)"
    puts $f "#"
    puts $f "# Agent:		[$s cget -address]"
    puts $f "# Start:		[getdate]"
    puts $f "# Interval:		$interval"
    puts $f "# Iterations:		$iterations"
    puts $f "#"
    puts $f "# Description of the columns in this file:"
    puts $f "#"
    puts $f "# 1:	Seconds since 1970."
    puts $f "# 2:	Interface index."
    puts $f "# 3:	Interface load."
    puts $f "# 4:	%"
    puts $f "# 5:	Interface status (either up or down)."
    puts $f "# 6:	Interface description."
    puts $f ""

    $s walk vbl "ifIndex" {
	set ifIndex [lindex [lindex $vbl 0] 2]

	set vbl [$s get [list sysUpTime.0 \
                          ifInOctets.$ifIndex ifOutOctets.$ifIndex \
                          ifSpeed.$ifIndex ifDescr.$ifIndex ifType.$ifIndex ]]

	set job [job create GetIfLoad [expr $interval * 1000] $iterations]

	set cx(session)     $s
	set cx(file)        $f
	set cx(ifIndex)     $ifIndex
	set cx(sysUpTime)   [lindex [lindex $vbl 0] 2]
	set cx(sysUpTime)   [mib scan sysUpTime $cx(sysUpTime)]
	set cx(ifInOctets)  [lindex [lindex $vbl 1] 2]
	set cx(ifOutOctets) [lindex [lindex $vbl 2] 2]
	set cx(ifSpeed)     [lindex [lindex $vbl 3] 2]
	set cx(ifDescr)     [lindex [lindex $vbl 4] 2]
	set cx(duplex)      [FullDuplex [lindex [lindex $vbl 5] 2]]

	$job attribute status [array get cx]
    }

    job wait
}

##
## The following proc takes samples of one day and writes them to
## a file which contains the starting date of the sample.
##

proc MonitorIfLoadDaily { s interval } {

    set clock [getclock]
    set date [getdate $clock]
    set file [format "%s-%s-%02d" \
	    [$s cget -address] [lindex $date 1] [lindex $date 2]]
    set f [open $file w+]
    set secs [expr ((($clock / 86400) + 1) * 86400) - $clock]
    MonitorIfLoad $f $s $interval [expr ($secs / $interval) + 1]
    close $f
}

##
## Some examples to set up monitoring jobs. Make sure to use IP
## addresses as this will make more readable output.
##

mib load rfc1213.mib

if {$argc < 2 || $argc > 3} {
    puts stderr {usage: ifload <ip> <seconds> [<community>]}
    exit 1
}

set host [lindex $argv 0]
set interval [lindex $argv 1]
set community [expr {$argc == 3 ? [lindex $argv 2] : "public"}]

set code [catch {snmp session -address $host -community $community} s]
if $code {
    puts stderr $s
    exit 1
}

while 1 {
    set code [catch {MonitorIfLoadDaily $s $interval} msg]
    if $code {	
	puts stderr $msg
	exit 1
    }
}

