##
## Here starts an experimental SNMP interface to the CERN HTTP daemon.
## It is based on the latest draft produced by the HTTP MIB interest 
## group.
##
## Copyright (c) 1995, 1996
##
## J. Schoenwaelder
## TU Braunschweig, Germany
## Institute for Operating Systems and Computer Networks
##
## Permission to use, copy, modify, and distribute this
## software and its documentation for any purpose and without
## fee is hereby granted, provided that this copyright
## notice appears in all copies.  The University of Braunschweig
## makes no representations about the suitability of this
## software for any purpose.  It is provided "as is" without
## express or implied warranty.
##

mib load xxx.mib

## The configuration of the HTTP entities monitored by this agent.
## Note, the AccessLog variable should point to the location of the
## HTTP log files which is expected to be in the HTTP Common Logfile 
## Format. The LogFile variable is used internally.

set httpEntity(1) {
    Description		"NCSA HTTPD 1.5.1"
    ObjectID		"0.0"
    Contact		"nm@cs.utwente.nl"
    Protocol		"0.0"
    ProtocolVersion	"HTTP/1.0"
    Name		"wwwsnmp.cs.utwente.nl"
    Address		"130.89.16.108"
    Port		"80"
    Type		"server"

    AccessLog		~snmp/src/httpd_1.5.1/logs/access_log
    LogFile		""
}

##
## Scan a line from a CERN access logfile (Common Logfile Format). The
## Common Logfile Format is defined at the following URL:
##
## http://www.w3.org/pub/WWW/Daemon/User/Config/Logging.html
##

proc ScanCommonLogfileFormat {line session i interp} {
    set n [scan $line {%s %*s %*s [%d/%[^/]/%d:%d:%d:%d %d] "%s %[^"]" %d %s} \
	    request(host) d mo y h m s gmt \
	    request(method) url request(code) request(size)]
    if {$n != 12} {
	puts stderr "Parse error at position [incr n]:\n$line"
	return
    }
    set request(date) [clock scan "$mo $d $h:$m:$s $y"]
    set request(url) [lindex $url 0]
    set request(version) [lindex $url 1]
    if {$request(code) < 0 || $request(code) > 999} {
	puts stderr "Invalid status code:\n$line"
	return
    }
    $interp eval [list CountRequest $session $i [array get request]]
}


##
## This a simple minded implementation of tail(1).
##

proc TailFile {f s i interp cmd} {
    while {[gets $f line] != -1} {
	if {$line != ""} {
	    $cmd $line $s $i $interp
	    update
	}
    }
    after 2000 [list TailFile $f $s $i $interp $cmd]
}

##
## Restart the HTTP agent. This will re-initialize all variables.
##

proc SNMP_HTTPRestart {interp s} {

    global httpEntity

    foreach i [array names httpEntity] {
	
	array set entity $httpEntity($i)

	# Reset all variables that keep state information.

	catch {close $entity(LogFile)}
	catch {close $entity(ErrFile)}

	# Remove all http instances known by the agent.

	$interp eval {
	    global httpSummary httpRequest httpResponse
	    catch {unset httpSummary}
	    catch {unset httpRequest}
	    catch {unset httpResponse}
	}

	# Open log and error files and start a tail emulation
	# to process log and error messages.

	$interp eval [list HTTP_CreateEntity $s $i $httpEntity($i)]

	if {[catch {open $entity(AccessLog) r} entity(LogFile)]} {
	    puts stderr "Can't open log file: $entity(LogFile)"
	    unset entity(LogFile)
	} else {
	    TailFile $entity(LogFile) $s $i $interp ScanCommonLogfileFormat
	}
	
	set httpEntity($i) [array get entity]
    }
}


##
## Create the SNMP variables that provide access to the statistics.
## See the MIB definition in tubs.mib for more details.
##

proc SNMP_HTTPInit {s} {

    set interp [$s cget -agent]

    $interp eval {

	##
	## Create instances that describe an HTTP entity.
	##

	proc HTTP_CreateEntity {s i entity} {
	    
	    array set ent $entity
	    
	    $s instance xxxEntityDescription.$i \
		    httpEntity(Description.$i) $ent(Description)
	    $s instance xxxEntityObjectID.$i \
		    httpEntity(ObjectID.$i) $ent(ObjectID)
	    $s instance xxxEntityContact.$i \
		    httpEntity(Contact.$i) $ent(Contact)
	    $s instance xxxEntityProtocol.$i \
		    httpEntity(Protocol.$i) $ent(Protocol)
	    $s instance xxxEntityProtocolVersion.$i \
		    httpEntity(ProtocolVersion.$i) $ent(ProtocolVersion)
	    $s instance xxxEntityName.$i \
		    httpEntity(Name.$i) $ent(Name)
	    $s instance xxxEntityAddress.$i \
		    httpEntity(Address.$i) $ent(Address)
	    $s instance xxxEntityPort.$i \
		    httpEntity(Port.$i) $ent(Port)
	    $s instance xxxEntityType.$i \
		    httpEntity(Type.$i) $ent(Type)

	    $s instance xxxSummaryRequests.$i \
		    httpSummary(Requests.$i) 0
	    $s instance xxxSummaryRequestErrors.$i \
		    httpSummary(RequestErrors.$i) 0
	    $s instance xxxSummaryResponses.$i \
		    httpSummary(Responses.$i) 0
	    $s instance xxxSummaryOutBytes.$i \
		    httpSummary(OutBytes.$i) 0
	}

	##
	## Callback to read another log entry from a CERN log file.
	##
	
	proc CountRequest {s i rl} {

	    global httpSummary httpRequest httpResponse tnm_system

	    array set request $rl
	    set code $request(code)
	    set method [mib scan xxxRequestMethodIndex $request(method)]

	    # Update the request counters.

	    if {! [info exists httpRequest(MethodIndex.$i:$method)]} {
		$s instance xxxRequestMethodIndex.$i:$method \
			httpRequest(MethodIndex.$i:$method) $request(method)
	    }
	    if {! [info exists httpRequest(InCount.$i:$method)]} {
		$s instance xxxRequestInCount.$i:$method \
			httpRequest(InCount.$i:$method) 0
	    }
	    incr httpRequest(InCount.$i:$method)
	    if {! [info exists httpRequest(InLastTime.$i:$method)]} {
                $s instance xxxRequestInLastTime.$i:$method \
                        httpRequest(InLastTime.$i:$method)
            }
            set httpRequest(InLastTime.$i:$method) \
		    $tnm_system(sysUpTime)

	    # Update the summary counters.

	    incr httpSummary(Requests.$i)
	    if {$request(code) == 200} {
		catch {incr httpSummary(OutBytes.$i) $request(size)}
	    } else {
		incr httpSummary(RequestErrors.$i)
	    }
	    incr httpSummary(Responses.$i)

	    # Update the response counters.

	    if {! [info exists httpResponse(StatusIndex.$i.$code)]} {
		$s instance xxxResponseStatusIndex.$i.$code \
			httpResponse(StatusIndex.$i.$code) $code
	    }
	    if {! [info exists httpResponse(OutCount.$i.$code)]} {
		$s instance xxxResponseOutCount.$i.$code \
			httpResponse(OutCount.$i.$code) 0
	    }
	    incr httpResponse(OutCount.$i.$code)
	    if {! [info exists httpResponse(OutBytes.$i.$code)]} {
		$s instance xxxResponseOutBytes.$i.$code \
			httpResponse(OutBytes.$i.$code) 0
	    }
	    incr httpResponse(OutBytes.$i.$code)
	    if {! [info exists httpResponse(OutLastTime.$i.$code)]} {
                $s instance xxxResponseOutLastTime.$i.$code \
                        httpResponse(OutLastTime.$i.$code)
            }
            set httpResponse(OutLastTime.$i.$code) \
		    $tnm_system(sysUpTime)
	}
	
    }

    SNMP_HTTPRestart $interp $s
}
