home *** CD-ROM | disk | FTP | other *** search
Text File | 2001-08-17 | 30.3 KB | 1,253 lines |
- # counter.tcl --
- #
- # Procedures to manage simple counters and histograms.
- #
- # Copyright (c) 1998-2000 by Ajuba Solutions.
- #
- # See the file "license.terms" for information on usage and redistribution
- # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
- #
- # RCS: @(#) $Id: counter.tcl,v 1.7 2001/08/02 16:38:06 andreas_kupries Exp $
-
- package require Tcl 8
-
- namespace eval counter:: {
-
- # Variables of name counter::T-$tagname
- # are created as arrays to support each counter.
-
- # Time-based histograms are kept in sync with each other,
- # so these variables are shared among them.
- # These base times record the time corresponding to the first bucket
- # of the per-minute, per-hour, and per-day time-based histograms.
-
- variable startTime
- variable minuteBase
- variable hourBase
- variable hourEnd
- variable dayBase
- variable hourIndex
- variable dayIndex
-
- # The time-based histogram uses an after event and a list
- # of counters to do mergeing on.
-
- variable tagsToMerge
- if {![info exist tagsToMerge]} {
- set tagsToMerge {}
- }
- variable mergeInterval
-
- namespace export *
- }
-
- # counter::init --
- #
- # Set up a counter.
- #
- # Arguments:
- # tag The identifier for the counter. Pass this to counter::count
- # args option values pairs that define characteristics of the counter:
- # See the man page for definitons.
- #
- # Results:
- # None.
- #
- # Side Effects:
- # Initializes state about a counter.
-
- proc counter::init {tag args} {
- upvar #0 counter::T-$tag counter
- if {[info exists counter]} {
- unset counter
- }
- set counter(N) 0 ;# Number of samples
- set counter(total) 0
- set counter(type) {}
-
- # With an empty type the counter is a simple accumulator
- # for which we can compute an average. Here we loop through
- # the args to determine what additional counter attributes
- # we need to maintain in counter::count
-
- foreach {option value} $args {
- switch -- $option {
- -timehist {
- variable tagsToMerge
- variable secsPerMinute
- variable startTime
- variable minuteBase
- variable hourBase
- variable dayBase
- variable hourIndex
- variable dayIndex
-
- upvar #0 counter::H-$tag histogram
- upvar #0 counter::Hour-$tag hourhist
- upvar #0 counter::Day-$tag dayhist
-
- # Clear the histograms.
-
- for {set i 0} {$i < 60} {incr i} {
- set histogram($i) 0
- }
- for {set i 0} {$i < 24} {incr i} {
- set hourhist($i) 0
- }
- if {[info exist dayhist]} {
- unset dayhist
- }
- set dayhist(0) 0
-
- # Clear all-time high records
-
- set counter(maxPerMinute) 0
- set counter(maxPerHour) 0
- set counter(maxPerDay) 0
-
- # The value associated with -timehist is the number of seconds
- # in each bucket. Normally this is 60, but for
- # testing, we compress minutes. The value is limited at
- # 60 because the per-minute buckets are accumulated into
- # per-hour buckets later.
-
- if {$value == "" || $value == 0 || $value > 60} {
- set value 60
- }
-
- # Histogram state variables.
- # All time-base histograms share the same bucket size
- # and starting times to keep them all synchronized.
- # So, we only initialize these parameters once.
-
- if {![info exist secsPerMinute]} {
- set secsPerMinute $value
-
- set startTime [clock seconds]
- set dayIndex 0
-
- set dayStart [clock scan [clock format $startTime \
- -format 00:00]]
-
- # Figure out what "hour" we are
-
- set delta [expr {$startTime - $dayStart}]
- set hourIndex [expr {$delta / ($secsPerMinute * 60)}]
- set day [expr {$hourIndex / 24}]
- set hourIndex [expr {$hourIndex % 24}]
-
- set hourBase [expr {$dayStart + $day * $secsPerMinute * 60 * 24}]
- set minuteBase [expr {$hourBase + $hourIndex * 60 * $secsPerMinute}]
-
- set partialHour [expr {$startTime -
- ($hourBase + $hourIndex * 60 * $secsPerMinute)}]
- set secs [expr {(60 * $secsPerMinute) - $partialHour}]
- if {$secs <= 0} {
- set secs 1
- }
-
- # After the first timer, the event occurs once each "hour"
-
- set mergeInterval [expr {60 * $secsPerMinute * 1000}]
- after [expr {$secs * 1000}] [list counter::MergeHour $mergeInterval]
- }
- if {[lsearch $tagsToMerge $tag] < 0} {
- lappend tagsToMerge $tag
- }
-
- # This records the last used slots in order to zero-out the
- # buckets that are skipped during idle periods.
-
- set counter(lastMinute) -1
-
- # The following is referenced when bugs cause histogram
- # hits outside the expect range (overflow and underflow)
-
- set counter(bucketsize) 0
- }
- -group {
- # Cluster a set of counters with a single total
-
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set counter(group) $value
- }
- -lastn {
- # The lastN samples are kept if a vector to form a running average.
-
- upvar #0 counter::V-$tag vector
- set counter(lastn) $value
- set counter(index) 0
- if {[info exist vector]} {
- unset vector
- }
- for {set i 0} {$i < $value} {incr i} {
- set vector($i) 0
- }
- }
- -hist {
- # A value-based histogram with buckets for different values.
-
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set counter(bucketsize) $value
- set counter(mult) 1
- }
- -hist2x {
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set counter(bucketsize) $value
- set counter(mult) 2
- }
- -hist10x {
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set counter(bucketsize) $value
- set counter(mult) 10
- }
- -histlog {
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set counter(bucketsize) $value
- }
- -simple {
- # Useful when disabling predefined -timehist or -group counter
- }
- default {
- return -code error "Unsupported option $option.\
- Must be -timehist, -group, -lastn, -hist, -hist2x, -hist10x, -histlog, or -simple."
- }
- }
- if {[string length $option]} {
- # In case an option doesn't change the type, but
- # this feature of the interface isn't used, etc.
-
- lappend counter(type) $option
- }
- }
-
- # Instead of supporting a counter that could have multiple attributes,
- # we support a single type to make counting more efficient.
-
- if {[llength $counter(type)] > 1} {
- return -code error "Multiple type attributes not supported. Use only one of\
- -timehist, -group, -lastn, -hist, -hist2x, -hist10x, -histlog, -disabled."
- }
- return ""
- }
-
- # counter::reset --
- #
- # Reset a counter.
- #
- # Arguments:
- # tag The identifier for the counter.
- #
- # Results:
- # None.
- #
- # Side Effects:
- # Deletes the counter and calls counter::init again for it.
-
- proc counter::reset {tag args} {
- upvar #0 counter::T-$tag counter
-
- # Layer reset on top of init. Here we figure out what
- # we need to pass into the init procedure to recreate it.
-
- switch -- $counter(type) {
- "" {
- set args ""
- }
- -group {
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set args [list -group $counter(group)]
- }
- -lastn {
- upvar #0 counter::V-$tag vector
- if {[info exist vector]} {
- unset vector
- }
- set args [list -lastn $counter(lastn)]
- }
- -hist -
- -hist10x -
- -histlog -
- -hist2x {
- upvar #0 counter::H-$tag histogram
- if {[info exist histogram]} {
- unset histogram
- }
- set args [list $counter(type) $counter(bucketsize)]
- }
- -timehist {
- foreach h [list counter::H-$tag counter::Hour-$tag counter::Day-$tag] {
- upvar #0 $h histogram
- if {[info exist histogram]} {
- unset histogram
- }
- }
- set args [list -timehist $counter::secsPerMinute]
- }
- default {#ignore}
- }
- unset counter
- eval {counter::init $tag} $args
- set counter(resetDate) [clock seconds]
- return ""
- }
-
- # counter::count --
- #
- # Accumulate statistics.
- #
- # Arguments:
- # tag The counter identifier.
- # delta The increment amount. Defaults to 1.
- # arg For -group types, this is the histogram index.
- #
- # Results:
- # None
- #
- # Side Effects:
- # Accumlate statistics.
-
- proc counter::count {tag {delta 1} args} {
- upvar #0 counter::T-$tag counter
- set counter(total) [expr {$counter(total) + $delta}]
- incr counter(N)
-
- # Instead of supporting a counter that could have multiple attributes,
- # we support a single type to make counting a skosh more efficient.
-
- # foreach option $counter(type) {
- switch -- $counter(type) {
- "" {
- # Simple counter
- return
- }
- -group {
- upvar #0 counter::H-$tag histogram
- set subIndex [lindex $args 0]
- if {![info exists histogram($subIndex)]} {
- set histogram($subIndex) 0
- }
- set histogram($subIndex) [expr {$histogram($subIndex) + $delta}]
- }
- -lastn {
- upvar #0 counter::V-$tag vector
- set vector($counter(index)) $delta
- set counter(index) [expr {($counter(index) +1)%$counter(lastn)}]
- }
- -hist {
- upvar #0 counter::H-$tag histogram
- set bucket [expr {int($delta / $counter(bucketsize))}]
- if {![info exist histogram($bucket)]} {
- set histogram($bucket) 0
- }
- incr histogram($bucket)
- }
- -hist10x -
- -hist2x {
- upvar #0 counter::H-$tag histogram
- set bucket 0
- for {set max $counter(bucketsize)} {$delta > $max} \
- {set max [expr {$max * $counter(mult)}]} {
- incr bucket
- }
- if {![info exist histogram($bucket)]} {
- set histogram($bucket) 0
- }
- incr histogram($bucket)
- }
- -histlog {
- upvar #0 counter::H-$tag histogram
- set bucket [expr {int(log($delta)*$counter(bucketsize))}]
- if {![info exist histogram($bucket)]} {
- set histogram($bucket) 0
- }
- incr histogram($bucket)
- }
- -timehist {
- upvar #0 counter::H-$tag histogram
- variable minuteBase
- variable secsPerMinute
-
- set minute [expr {([clock seconds] - $minuteBase) / $secsPerMinute}]
- if {$minute > 59} {
- # this occurs while debugging if the process is
- # stopped at a breakpoint too long.
- set minute 59
- }
-
- # Initialize the current bucket and
- # clear any buckets we've skipped since the last sample.
-
- if {$minute != $counter(lastMinute)} {
- set histogram($minute) 0
- for {set i [expr {$counter(lastMinute)+1}]} \
- {$i < $minute} \
- {incr i} {
- set histogram($i) 0
- }
- set counter(lastMinute) $minute
- }
- set histogram($minute) [expr {$histogram($minute) + $delta}]
- }
- default {#ignore}
- }
- # }
- return
- }
-
- # counter::exists --
- #
- # Return true if the counter exists.
- #
- # Arguments:
- # tag The counter identifier.
- #
- # Results:
- # 1 if it has been defined.
- #
- # Side Effects:
- # None.
-
- proc counter::exists {tag} {
- upvar #0 counter::T-$tag counter
- return [info exists counter]
- }
-
- # counter::get --
- #
- # Return statistics.
- #
- # Arguments:
- # tag The counter identifier.
- # option What statistic to get
- # args Needed by some options.
- #
- # Results:
- # With no args, just the counter value.
- #
- # Side Effects:
- # None.
-
- proc counter::get {tag {option -total} args} {
- upvar #0 counter::T-$tag counter
- switch -- $option {
- -total {
- return $counter(total)
- }
- -totalVar {
- return ::counter::T-$tag\(total)
- }
- -N {
- return $counter(N)
- }
- -avg {
- if {$counter(N) == 0} {
- return 0
- } else {
- return [expr {$counter(total) / double($counter(N))}]
- }
- }
- -avgn {
- upvar #0 counter::V-$tag vector
- set sum 0
- for {set i 0} {($i < $counter(N)) && ($i < $counter(lastn))} {incr i} {
- set sum [expr {$sum + $vector($i)}]
- }
- if {$i == 0} {
- return 0
- } else {
- return [expr {$sum / double($i)}]
- }
- }
- -hist {
- upvar #0 counter::H-$tag histogram
- if {[llength $args]} {
- # Return particular bucket
- set bucket [lindex $args 0]
- if {[info exist histogram($bucket)]} {
- return $histogram($bucket)
- } else {
- return 0
- }
- } else {
- # Dump the whole histogram
-
- set result {}
- if {$counter(type) == "-group"} {
- set sort -dictionary
- } else {
- set sort -integer
- }
- foreach x [lsort $sort [array names histogram]] {
- lappend result $x $histogram($x)
- }
- return $result
- }
- }
- -histVar {
- return ::counter::H-$tag
- }
- -histHour {
- upvar #0 counter::Hour-$tag histogram
- set result {}
- foreach x [lsort -integer [array names histogram]] {
- lappend result $x $histogram($x)
- }
- return $result
- }
- -histHourVar {
- return ::counter::Hour-$tag
- }
- -histDay {
- upvar #0 counter::Day-$tag histogram
- set result {}
- foreach x [lsort -integer [array names histogram]] {
- lappend result $x $histogram($x)
- }
- return $result
- }
- -histDayVar {
- return ::counter::Day-$tag
- }
- -maxPerMinute {
- return $counter(maxPerMinute)
- }
- -maxPerHour {
- return $counter(maxPerHour)
- }
- -maxPerDay {
- return $counter(maxPerDay)
- }
- -resetDate {
- if {[info exists counter(resetDate)]} {
- return $counter(resetDate)
- } else {
- return ""
- }
- }
- -all {
- return [array get counter]
- }
- default {
- return -code error "Invalid option $option.\
- Should be -all, -total, -N, -avg, -avgn, -hist, -histHour,\
- -histDay, -totalVar, -histVar, -histHourVar, -histDayVar -resetDate."
- }
- }
- }
-
- # counter::names --
- #
- # Return the list of defined counters.
- #
- # Arguments:
- # none
- #
- # Results:
- # A list of counter tags.
- #
- # Side Effects:
- # None.
-
- proc counter::names {} {
- set result {}
- foreach v [info vars ::counter::T-*] {
- if {[info exist $v]} {
- # Declared arrays might not exist, yet
- regsub -- ::counter::T- $v {} v
- lappend result $v
- }
- }
- return $result
- }
-
- # counter::MergeHour --
- #
- # Sum the per-minute histogram into the next hourly bucket.
- # On 24-hour boundaries, sum the hourly buckets into the next day bucket.
- # This operates on all time-based histograms.
- #
- # Arguments:
- # none
- #
- # Results:
- # none
- #
- # Side Effects:
- # See description.
-
- proc counter::MergeHour {interval} {
- variable hourIndex
- variable minuteBase
- variable hourBase
- variable tagsToMerge
- variable secsPerMinute
-
- after $interval [list counter::MergeHour $interval]
- if {![info exist hourBase] || $hourIndex == 0} {
- set hourBase $minuteBase
- }
- set minuteBase [clock seconds]
-
- foreach tag $tagsToMerge {
- upvar #0 counter::T-$tag counter
- upvar #0 counter::H-$tag histogram
- upvar #0 counter::Hour-$tag hourhist
-
- # Clear any buckets we've skipped since the last sample.
-
- for {set i [expr {$counter(lastMinute)+1}]} {$i < 60} {incr i} {
- set histogram($i) 0
- }
- set counter(lastMinute) -1
-
- # Accumulate into the next hour bucket.
-
- set hourhist($hourIndex) 0
- set max 0
- foreach i [array names histogram] {
- set hourhist($hourIndex) [expr {$hourhist($hourIndex) + $histogram($i)}]
- if {$histogram($i) > $max} {
- set max $histogram($i)
- }
- }
- set perSec [expr {$max / $secsPerMinute}]
- if {$perSec > $counter(maxPerMinute)} {
- set counter(maxPerMinute) $perSec
- }
- }
- set hourIndex [expr {($hourIndex + 1) % 24}]
- if {$hourIndex == 0} {
- counter::MergeDay
- }
-
- }
- # counter::MergeDay --
- #
- # Sum the per-minute histogram into the next hourly bucket.
- # On 24-hour boundaries, sum the hourly buckets into the next day bucket.
- # This operates on all time-based histograms.
- #
- # Arguments:
- # none
- #
- # Results:
- # none
- #
- # Side Effects:
- # See description.
-
- proc counter::MergeDay {} {
- variable dayIndex
- variable dayBase
- variable hourBase
- variable tagsToMerge
- variable secsPerMinute
-
- # Save the hours histogram into a bucket for the last day
- # counter(day,$day) is the starting time for that day bucket
-
- if {![info exist dayBase]} {
- set dayBase $hourBase
- }
- foreach tag $tagsToMerge {
- upvar #0 counter::T-$tag counter
- upvar #0 counter::Day-$tag dayhist
- upvar #0 counter::Hour-$tag hourhist
- set dayhist($dayIndex) 0
- set max 0
- for {set i 0} {$i < 24} {incr i} {
- if {[info exist hourhist($i)]} {
- set dayhist($dayIndex) [expr {$dayhist($dayIndex) + $hourhist($i)}]
- if {$hourhist($i) > $max} {
- set mx $hourhist($i)
- }
- }
- }
- set perSec [expr {double($max) / ($secsPerMinute * 60)}]
- if {$perSec > $counter(maxPerHour)} {
- set counter(maxPerHour) $perSec
- }
- }
- set perSec [expr {double($dayhist($dayIndex)) / ($secsPerMinute * 60 * 24)}]
- if {$perSec > $counter(maxPerDay)} {
- set counter(maxPerDay) $perSec
- }
- incr dayIndex
- }
-
- # counter::histHtmlDisplay --
- #
- # Create an html display of the histogram.
- #
- # Arguments:
- # tag The counter tag
- # args option, value pairs that affect the display:
- # -title Label to display above bar chart
- # -unit minutes, hours, or days select time-base histograms.
- # Specify anything else for value-based histograms.
- # -images URL of /images directory.
- # -gif Image for normal histogram bars
- # -ongif Image for the active histogram bar
- # -max Maximum number of value-based buckets to display
- # -height Pixel height of the highest bar
- # -width Pixel width of each bar
- # -skip Buckets to skip when labeling value-based histograms
- # -format Format used to display labels of buckets.
- # -text If 1, a text version of the histogram is dumped,
- # otherwise a graphical one is generated.
- #
- # Results:
- # HTML for the display as a complete table.
- #
- # Side Effects:
- # None.
-
- proc counter::histHtmlDisplay {tag args} {
- append result "<p>\n<table border=0 cellpadding=0 cellspacing=0>\n"
- append result [eval {counter::histHtmlDisplayRow $tag} $args]
- append result </table>
- return $result
- }
-
- # counter::histHtmlDisplayRow --
- #
- # Create an html display of the histogram.
- #
- # Arguments:
- # See counter::histHtmlDisplay
- #
- # Results:
- # HTML for the display. Ths is one row of a 2-column table,
- # the calling page must define the <table> tag.
- #
- # Side Effects:
- # None.
-
- proc counter::histHtmlDisplayRow {tag args} {
- upvar #0 counter::T-$tag counter
- variable secsPerMinute
- variable minuteBase
- variable hourBase
- variable dayBase
- variable hourIndex
- variable dayIndex
-
- array set options [list \
- -title $tag \
- -unit "" \
- -images /images \
- -gif Blue.gif \
- -ongif Red.gif \
- -max -1 \
- -height 100 \
- -width 4 \
- -skip 4 \
- -format %.2f \
- -text 0
- ]
- array set options $args
-
- # Support for self-posting pages that can clear counters.
-
- append result "<!-- resetCounter [ncgi::value resetCounter] -->"
- if {[ncgi::value resetCounter] == $tag} {
- counter::reset $tag
- return "<!-- Reset $tag counter -->"
- }
-
- switch -glob -- $options(-unit) {
- min* {
- upvar #0 counter::H-$tag histogram
- set histname counter::H-$tag
- if {![info exist minuteBase]} {
- return "<!-- No time-based histograms defined -->"
- }
- set time $minuteBase
- set secsForMax $secsPerMinute
- set periodMax $counter(maxPerMinute)
- set curIndex [expr {([clock seconds] - $minuteBase) / $secsPerMinute}]
- set options(-max) 60
- set options(-min) 0
- }
- hour* {
- upvar #0 counter::Hour-$tag histogram
- set histname counter::Hour-$tag
- if {![info exist hourBase]} {
- return "<!-- Hour merge has not occurred -->"
- }
- set time $hourBase
- set secsForMax [expr {$secsPerMinute * 60}]
- set periodMax $counter(maxPerHour)
- set curIndex [expr {$hourIndex - 1}]
- if {$curIndex < 0} {
- set curIndex 23
- }
- set options(-max) 24
- set options(-min) 0
- }
- day* {
- upvar #0 counter::Day-$tag histogram
- set histname counter::Day-$tag
- if {![info exist dayBase]} {
- return "<!-- Hour merge has not occurred -->"
- }
- set time $dayBase
- set secsForMax [expr {$secsPerMinute * 60 * 24}]
- set periodMax $counter(maxPerDay)
- set curIndex dayIndex
- set options(-max) $dayIndex
- set options(-min) 0
- }
- default {
- # Value-based histogram with arbitrary units.
-
- upvar #0 counter::H-$tag histogram
- set histname counter::H-$tag
-
- set unit $options(-unit)
- set curIndex ""
- set time ""
- }
- }
- if {! [info exists histogram]} {
- return "<!-- $histname doesn't exist -->\n"
- }
-
- set max 0
- set maxName 0
- foreach {name value} [array get histogram] {
- if {$value > $max} {
- set max $value
- set maxName $name
- }
- }
-
- # Start 2-column HTML display. A summary table at the left, the histogram on the right.
-
- append result "<tr><td valign=top>\n"
-
- append result "<table bgcolor=#EEEEEE>\n"
- append result "<tr><td colspan=2 align=center>[html::font]<b>$options(-title)</b></font></td></tr>\n"
- append result "<tr><td>[html::font]<b>Total</b></font></td>"
- append result "<td>[html::font][format $options(-format) $counter(total)]</font></td></tr>\n"
-
- if {[info exists secsForMax]} {
-
- # Time-base histogram
-
- set string {}
- set t $secsForMax
- set days [expr {$t / (60 * 60 * 24)}]
- if {$days == 1} {
- append string "1 Day "
- } elseif {$days > 1} {
- append string "$days Days "
- }
- set t [expr {$t - $days * (60 * 60 * 24)}]
- set hours [expr {$t / (60 * 60)}]
- if {$hours == 1} {
- append string "1 Hour "
- } elseif {$hours > 1} {
- append string "$hours Hours "
- }
- set t [expr {$t - $hours * (60 * 60)}]
- set mins [expr {$t / 60}]
- if {$mins == 1} {
- append string "1 Minute "
- } elseif {$mins > 1} {
- append string "$mins Minutes "
- }
- set t [expr {$t - $mins * 60}]
- if {$t == 1} {
- append string "1 Second "
- } elseif {$t > 1} {
- append string "$t Seconds "
- }
- append result "<tr><td>[html::font]<b>Bucket Size</b></font></td>"
- append result "<td>[html::font]$string</font></td></tr>\n"
-
- append result "<tr><td>[html::font]<b>Max Per Sec</b></font></td>"
- append result "<td>[html::font][format %.2f [expr {$max/double($secsForMax)}]]</font></td></tr>\n"
-
- if {$periodMax > 0} {
- append result "<tr><td>[html::font]<b>Best Per Sec</b></font></td>"
- append result "<td>[html::font][format %.2f $periodMax]</font></td></tr>\n"
- }
- append result "<tr><td>[html::font]<b>Starting Time</b></font></td>"
- switch -glob -- $options(-unit) {
- min* {
- append result "<td>[html::font][clock format $time \
- -format %k:%M:%S]</font></td></tr>\n"
- }
- hour* {
- append result "<td>[html::font][clock format $time \
- -format %k:%M:%S]</font></td></tr>\n"
- }
- day* {
- append result "<td>[html::font][clock format $time \
- -format "%b %d %k:%M"]</font></td></tr>\n"
- }
- default {#ignore}
- }
-
- } else {
-
- # Value-base histogram
-
- set ix [lsort -integer [array names histogram]]
-
- set mode [expr {$counter(bucketsize) * $maxName}]
- set first [expr {$counter(bucketsize) * [lindex $ix 0]}]
- set last [expr {$counter(bucketsize) * [lindex $ix end]}]
-
- append result "<tr><td>[html::font]<b>Average</b></font></td>"
- append result "<td>[html::font][format $options(-format) [counter::get $tag -avg]]</font></td></tr>\n"
-
- append result "<tr><td>[html::font]<b>Mode</b></font></td>"
- append result "<td>[html::font]$mode</font></td></tr>\n"
-
- append result "<tr><td>[html::font]<b>Minimum</b></font></td>"
- append result "<td>[html::font]$first</font></td></tr>\n"
-
- append result "<tr><td>[html::font]<b>Maxmum</b></font></td>"
- append result "<td>[html::font]$last</font></td></tr>\n"
-
- append result "<tr><td>[html::font]<b>Unit</b></font></td>"
- append result "<td>[html::font]$unit</font></td></tr>\n"
-
- append result "<tr><td colspan=2 align=center>[html::font]<b>"
- append result "<a href=[ncgi::urlStub]?resetCounter=$tag>Reset</a></td></tr>\n"
-
- if {$options(-max) < 0} {
- set options(-max) [lindex $ix end]
- }
- if {![info exist options(-min)]} {
- set options(-min) [lindex $ix 0]
- }
- }
-
- # End table nested inside left-hand column
-
- append result </table>\n
- append result </td>\n
- append result "<td valign=bottom>\n"
-
-
- # Display the histogram
-
- if {$options(-text)} {
- } else {
- append result [eval \
- {counter::histHtmlDisplayBarChart $tag histogram $max $curIndex $time} \
- [array get options]]
- }
-
- # Close the right hand column, but leave our caller's table open.
-
- append result </td></tr>\n
-
- return $result
- }
-
- # counter::histHtmlDisplayBarChart --
- #
- # Create an html display of the histogram.
- #
- # Arguments:
- # tag The counter tag.
- # histVar The name of the histogram array
- # max The maximum counter value in a histogram bucket.
- # curIndex The "current" histogram index, for time-base histograms.
- # time The base, or starting time, for the time-based histograms.
- # args The array get of the options passed into histHtmlDisplay
- #
- # Results:
- # HTML for the bar chart.
- #
- # Side Effects:
- # See description.
-
- proc counter::histHtmlDisplayBarChart {tag histVar max curIndex time args} {
- upvar #0 counter::T-$tag counter
- upvar 1 $histVar histogram
- variable secsPerMinute
- array set options $args
-
- append result "<table cellpadding=0 cellspacing=0 bgcolor=#eeeeee><tr>\n"
-
- set ix [lsort -integer [array names histogram]]
-
- for {set t $options(-min)} {$t < $options(-max)} {incr t} {
- if {![info exist histogram($t)]} {
- set value 0
- } else {
- set value $histogram($t)
- }
- if {$max == 0 || $value == 0} {
- set height 1
- } else {
- set percent [expr {round($value * 100.0 / $max)}]
- set height [expr {$percent * $options(-height) / 100}]
- }
- if {$t == $curIndex} {
- set img src=$options(-images)/$options(-ongif)
- } else {
- set img src=$options(-images)/$options(-gif)
- }
- append result "<td valign=bottom><img $img height=$height\
- width=$options(-width) alt=$value></td>\n"
- }
- append result "</tr>"
-
- # Count buckets outside the range requested
-
- set overflow 0
- set underflow 0
- foreach t [lsort -integer [array names histogram]] {
- if {($options(-max) > 0) && ($t > $options(-max))} {
- incr overflow
- }
- if {($options(-min) >= 0) && ($t < $options(-min))} {
- incr underflow
- }
- }
-
- # Append a row of labels at the bottom.
-
- set colors {black #CCCCCC}
- set bgcolors {#CCCCCC black}
- set colori 0
- if {$counter(type) != "-timehist"} {
-
- # Label each bucket with its value
- # This is probably wrong for hist2x and hist10x
-
- append result "<tr>"
- set skip $options(-skip)
- if {![info exists counter(mult)]} {
- set counter(mult) 1
- }
-
- # These are tick marks
-
- set img src=$options(-images)/$options(-gif)
- append result "<tr>"
- for {set i $options(-min)} {$i < $options(-max)} {incr i} {
- if {(($i % $skip) == 0)} {
- append result "<td valign=bottom><img $img height=3 \
- width=1></td>\n"
- } else {
- append result "<td valign=bottom></td>"
- }
- }
- append result </tr>
-
- # These are the labels
-
- append result "<tr>"
- for {set i $options(-min)} {$i < $options(-max)} {incr i} {
- if {$counter(type) == "-histlog"} {
- if {[catch {expr {int(log($i) * $counter(bucketsize))}} x]} {
- # Out-of-bounds
- break
- }
- } else {
- set x [expr {$i * $counter(bucketsize) * $counter(mult)}]
- }
- set label [format $options(-format) $x]
- if {(($i % $skip) == 0)} {
- set color [lindex $colors $colori]
- set bg [lindex $bgcolors $colori]
- set colori [expr {($colori+1) % 2}]
- append result "<td colspan=$skip><font size=1 color=$color>$label</font></td>"
- }
- }
- append result </tr>
- } else {
- switch -glob -- $options(-unit) {
- min* {
- if {$secsPerMinute != 60} {
- set format %k:%M:%S
- set skip 12
- } else {
- set format %k:%M
- set skip 4
- }
- set deltaT $secsPerMinute
- set wrapDeltaT [expr {$secsPerMinute * -59}]
- }
- hour* {
- if {$secsPerMinute != 60} {
- set format %k:%M
- set skip 4
- } else {
- set format %k
- set skip 2
- }
- set deltaT [expr {$secsPerMinute * 60}]
- set wrapDeltaT [expr {$secsPerMinute * 60 * -23}]
- }
- day* {
- if {$secsPerMinute != 60} {
- set format "%m/%d %k:%M"
- set skip 10
- } else {
- set format %k
- set skip $options(-skip)
- }
- set deltaT [expr {$secsPerMinute * 60 * 24}]
- set wrapDeltaT 0
- }
- default {#ignore}
- }
- # These are tick marks
-
- set img src=$options(-images)/$options(-gif)
- append result "<tr>"
- foreach t [lsort -integer [array names histogram]] {
- if {(($t % $skip) == 0)} {
- append result "<td valign=bottom><img $img height=3 \
- width=1></td>\n"
- } else {
- append result "<td valign=bottom></td>"
- }
- }
- append result </tr>
-
- set lastLabel ""
- append result "<tr>"
- foreach t [lsort -integer [array names histogram]] {
-
- # Label each bucket with its time
-
- set label [clock format $time -format $format]
- if {(($t % $skip) == 0) && ($label != $lastLabel)} {
- set color [lindex $colors $colori]
- set bg [lindex $bgcolors $colori]
- set colori [expr {($colori+1) % 2}]
- append result "<td colspan=$skip><font size=1 color=$color>$label</font></td>"
- set lastLabel $label
- }
- if {$t == $curIndex} {
- incr time $wrapDeltaT
- } else {
- incr time $deltaT
- }
- }
- append result </tr>\n
- }
- append result "</table>"
- if {$underflow > 0} {
- append result "<br>Skipped $underflow samples <\
- [expr {$options(-min) * $counter(bucketsize)}]\n"
- }
- if {$overflow > 0} {
- append result "<br>Skipped $overflow samples >\
- [expr {$options(-max) * $counter(bucketsize)}]\n"
- }
- return $result
- }
-
- # counter::start --
- #
- # Start an interval timer. This should be pre-declared with
- # type either -hist, -hist2x, or -hist20x
- #
- # Arguments:
- # tag The counter identifier.
- # instance There may be multiple intervals outstanding
- # at any time. This serves to distinquish them.
- #
- # Results:
- # None
- #
- # Side Effects:
- # Records the starting time for the instance of this interval.
-
- proc counter::start {tag instance} {
- upvar #0 counter::Time-$tag time
- set time($instance) [list [clock clicks] \
- [clock seconds]]
- }
-
- # counter::stop --
- #
- # Record an interval timer.
- #
- # Arguments:
- # tag The counter identifier.
- # instance There may be multiple intervals outstanding
- # at any time. This serves to distinquish them.
- # func An optional function used to massage the time
- # stamp before putting into the histogram.
- #
- # Results:
- # None
- #
- # Side Effects:
- # Computes the current interval and adds it to the histogram.
-
- proc counter::stop {tag instance {func ::counter::Identity}} {
- upvar #0 counter::Time-$tag time
-
- if {![info exist time($instance)]} {
- # Extra call. Ignore so we can debug error cases.
- return
- }
- set now [list [clock clicks] \
- [clock seconds]]
- set delMicros [expr {[lindex $now 0] - [lindex $time($instance) 0]}]
- set delSecond [expr {[lindex $now 1] - [lindex $time($instance) 1]}]
- unset time($instance)
-
- if {$delMicros < 0} {
- set delMicros [expr {1000000 + $delMicros}]
- incr delSecond -1
- if {$delSecond < 0} {
- set delSecond 0
- }
- }
- counter::count $tag [$func $delSecond.[format %06d $delMicros]]
- }
-
- # counter::Identity --
- #
- # Return its argument. This is used as the default function
- # to apply to an interval timer.
- #
- # Arguments:
- # x Some value.
- #
- # Results:
- # $x
- #
- # Side Effects:
- # None
-
-
- proc counter::Identity {x} {
- return $x
- }
-
- package provide counter 2.0
-
-