counter.tcl

Go to the documentation of this file.
00001 /*  counter.tcl --*/
00002 /* */
00003 /*    Procedures to manage simple counters and histograms.*/
00004 /* */
00005 /*  Copyright (c) 1998-2000 by Ajuba Solutions.*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /* */
00010 /*  RCS: @(#) $Id: counter.tcl,v 1.23 2005/09/30 05:36:38 andreas_kupries Exp $*/
00011 
00012 package require Tcl 8.2
00013 
00014 namespace ::counter {
00015 
00016     /*  Variables of name counter::T-$tagname*/
00017     /*  are created as arrays to support each counter.*/
00018 
00019     /*  Time-based histograms are kept in sync with each other,*/
00020     /*  so these variables are shared among them.*/
00021     /*  These base times record the time corresponding to the first bucket */
00022     /*  of the per-minute, per-hour, and per-day time-based histograms.*/
00023 
00024     variable startTime
00025     variable minuteBase
00026     variable hourBase
00027     variable hourEnd
00028     variable dayBase
00029     variable hourIndex
00030     variable dayIndex
00031 
00032     /*  The time-based histogram uses an after event and a list*/
00033     /*  of counters to do mergeing on.*/
00034 
00035     variable tagsToMerge
00036     if {![info exists tagsToMerge]} {
00037      tagsToMerge =  {}
00038     }
00039     variable mergeInterval
00040 
00041     namespace export init re count =  exists get names start stop
00042     namespace export histHtmlDisplay histHtmlDisplayRow histHtmlDisplayBarChart
00043 }
00044 
00045 /*  ::counter::init --*/
00046 /* */
00047 /*    Set up a counter.*/
00048 /* */
00049 /*  Arguments:*/
00050 /*    tag The identifier for the counter.  Pass this to counter::count*/
00051 /*    args    option values pairs that define characteristics of the counter:*/
00052 /*        See the man page for definitons.*/
00053 /* */
00054 /*  Results:*/
00055 /*    None.*/
00056 /* */
00057 /*  Side Effects:*/
00058 /*    Initializes state about a counter.*/
00059 
00060 ret  ::counter::init (type tag , type args) {
00061     upvar #0 counter::T-$tag counter
00062     if {[info exists counter]} {
00063     unset counter
00064     }
00065     set counter(N) 0    ;# Number of samples
00066     set counter(total) 0
00067     set counter(type) {}
00068 
00069     # With an empty type the counter is a simple accumulator
00070     # for which we can compute an average.  Here we loop through
00071     # the args to determine what additional counter attributes
00072     # we need to maintain in counter::count
00073 
00074     foreach {option value} $args {
00075     switch -- $option {
00076         -timehist {
00077         variable tagsToMerge
00078         variable secsPerMinute
00079         variable startTime
00080         variable minuteBase
00081         variable hourBase
00082         variable dayBase
00083         variable hourIndex
00084         variable dayIndex
00085 
00086         upvar #0 counter::H-$tag histogram
00087         upvar #0 counter::Hour-$tag hourhist
00088         upvar #0 counter::Day-$tag dayhist
00089 
00090         # Clear the histograms.
00091 
00092         for {set i 0} {$i < 60} {incr i} {
00093             set histogram($i) 0
00094         }
00095         for {set i 0} {$i < 24} {incr i} {
00096             set hourhist($i) 0
00097         }
00098         if {[info exists dayhist]} {
00099             unset dayhist
00100         }
00101         set dayhist(0) 0
00102 
00103         # Clear all-time high records
00104 
00105         set counter(maxPerMinute) 0
00106         set counter(maxPerHour) 0
00107         set counter(maxPerDay) 0
00108 
00109         # The value associated with -timehist is the number of seconds
00110         # in each bucket.  Normally this is 60, but for
00111         # testing, we compress minutes.  The value is limited at
00112         # 60 because the per-minute buckets are accumulated into
00113         # per-hour buckets later.
00114 
00115         if {$value == "" || $value == 0 || $value > 60} {
00116             set value 60
00117         }
00118 
00119         # Histogram state variables.
00120         # All time-base histograms share the same bucket size
00121         # and starting times to keep them all synchronized.
00122         # So, we only initialize these parameters once.
00123 
00124         if {![info exists secsPerMinute]} {
00125             set secsPerMinute $value
00126 
00127             set startTime [clock seconds]
00128             set dayIndex 0
00129 
00130             set dayStart [clock scan [clock format $startTime \
00131                 -format 00:00]]
00132             
00133             # Figure out what "hour" we are
00134 
00135             set delta [expr {$startTime - $dayStart}]
00136             set hourIndex [expr {$delta / ($secsPerMinute * 60)}]
00137             set day [expr {$hourIndex / 24}]
00138             set hourIndex [expr {$hourIndex % 24}]
00139 
00140             set hourBase [expr {$dayStart + $day * $secsPerMinute * 60 * 24}]
00141             set minuteBase [expr {$hourBase + $hourIndex * 60 * $secsPerMinute}]
00142 
00143             set partialHour [expr {$startTime -
00144             ($hourBase + $hourIndex * 60 * $secsPerMinute)}]
00145             set secs [expr {(60 * $secsPerMinute) - $partialHour}]
00146             if {$secs <= 0} {
00147             set secs 1
00148             }
00149 
00150             # After the first timer, the event occurs once each "hour"
00151 
00152             set mergeInterval [expr {60 * $secsPerMinute * 1000}]
00153             after [expr {$secs * 1000}] [list counter::MergeHour $mergeInterval]
00154         }
00155         if {[lsearch $tagsToMerge $tag] < 0} {
00156             lappend tagsToMerge $tag
00157         }
00158 
00159         # This records the last used slots in order to zero-out the
00160         # buckets that are skipped during idle periods.
00161 
00162         set counter(lastMinute) -1
00163 
00164         # The following is referenced when bugs cause histogram
00165         # hits outside the expect range (overflow and underflow)
00166 
00167         set counter(bucketsize)  0
00168         }
00169         -group {
00170         # Cluster a set of counters with a single total
00171 
00172         upvar #0 counter::H-$tag histogram
00173         if {[info exists histogram]} {
00174             unset histogram
00175         }
00176         set counter(group) $value
00177         }
00178         -lastn {
00179         # The lastN samples are kept if a vector to form a running average.
00180 
00181         upvar #0 counter::V-$tag vector
00182         set counter(lastn) $value
00183         set counter(index) 0
00184         if {[info exists vector]} {
00185             unset vector
00186         }
00187         for {set i 0} {$i < $value} {incr i} {
00188             set vector($i) 0
00189         }
00190         }
00191         -hist {
00192         # A value-based histogram with buckets for different values.
00193 
00194         upvar #0 counter::H-$tag histogram
00195         if {[info exists histogram]} {
00196             unset histogram
00197         }
00198         set counter(bucketsize) $value
00199         set counter(mult) 1
00200         }
00201         -hist2x {
00202         upvar #0 counter::H-$tag histogram
00203         if {[info exists histogram]} {
00204             unset histogram
00205         }
00206         set counter(bucketsize) $value
00207         set counter(mult) 2
00208         }
00209         -hist10x {
00210         upvar #0 counter::H-$tag histogram
00211         if {[info exists histogram]} {
00212             unset histogram
00213         }
00214         set counter(bucketsize) $value
00215         set counter(mult) 10
00216         }
00217         -histlog {
00218         upvar #0 counter::H-$tag histogram
00219         if {[info exists histogram]} {
00220             unset histogram
00221         }
00222         set counter(bucketsize) $value
00223         }
00224         -simple {
00225         # Useful when disabling predefined -timehist or -group counter
00226         }
00227         default {
00228         return -code error "Unsupported option $option.\
00229         Must be -timehist, -group, -lastn, -hist, -hist2x, -hist10x, -histlog, or -simple."
00230         }
00231     }
00232     if {[string length $option]} {
00233         # In case an option doesn't change the type, but
00234         # this feature of the interface isn't used, etc.
00235 
00236         lappend counter(type) $option
00237     }
00238     }
00239 
00240     # Instead of supporting a counter that could have multiple attributes,
00241     # we support a single type to make counting more efficient.
00242 
00243     if {[llength $counter(type)] > 1} {
00244     return -code error "Multiple type attributes not supported.  Use only one of\
00245         -timehist, -group, -lastn, -hist, -hist2x, -hist10x, -histlog, -disabled."
00246     }
00247     return ""
00248 }
00249 
00250 /*  ::counter::reset --*/
00251 /* */
00252 /*    Reset a counter.*/
00253 /* */
00254 /*  Arguments:*/
00255 /*    tag The identifier for the counter.*/
00256 /* */
00257 /*  Results:*/
00258 /*    None.*/
00259 /* */
00260 /*  Side Effects:*/
00261 /*    Deletes the counter and calls counter::init again for it.*/
00262 
00263 ret  ::counter::reset (type tag , type args) {
00264     upvar #0 counter::T-$tag counter
00265 
00266     # Layer reset on top of init.  Here we figure out what
00267     # we need to pass into the init procedure to recreate it.
00268 
00269     switch -- $counter(type) {
00270     ""  {
00271         set args ""
00272     }
00273     -group {
00274         upvar #0 counter::H-$tag histogram
00275         if {[info exists histogram]} {
00276         unset histogram
00277         }
00278         set args [list -group $counter(group)]
00279     }
00280     -lastn {
00281         upvar #0 counter::V-$tag vector
00282         if {[info exists vector]} {
00283         unset vector
00284         }
00285         set args [list -lastn $counter(lastn)]
00286     }
00287     -hist -
00288     -hist10x -
00289     -histlog -
00290     -hist2x {
00291         upvar #0 counter::H-$tag histogram
00292         if {[info exists histogram]} {
00293         unset histogram
00294         }
00295         set args [list $counter(type) $counter(bucketsize)]
00296     }
00297     -timehist {
00298         foreach h [list counter::H-$tag counter::Hour-$tag counter::Day-$tag] {
00299         upvar #0 $h histogram
00300         if {[info exists histogram]} {
00301             unset histogram
00302         }
00303         }
00304         set args [list -timehist $counter::secsPerMinute]
00305     }
00306     default {#ignore}
00307     }
00308     unset counter
00309     eval {counter::init $tag} $args
00310     set counter(resetDate) [clock seconds]
00311     return ""
00312 }
00313 
00314 /*  ::counter::count --*/
00315 /* */
00316 /*    Accumulate statistics.*/
00317 /* */
00318 /*  Arguments:*/
00319 /*    tag The counter identifier.*/
00320 /*    delta   The increment amount.  Defaults to 1.*/
00321 /*    arg For -group types, this is the histogram index.*/
00322 /* */
00323 /*  Results:*/
00324 /*    None*/
00325 /* */
00326 /*  Side Effects:*/
00327 /*    Accumlate statistics.*/
00328 
00329 ret  ::counter::count (type tag , optional delta =1 , type args) {
00330     upvar #0 counter::T-$tag counter
00331     set counter(total) [expr {$counter(total) + $delta}]
00332     incr counter(N)
00333 
00334     # Instead of supporting a counter that could have multiple attributes,
00335     # we support a single type to make counting a skosh more efficient.
00336 
00337 #    foreach option $counter(type) {
00338     switch -- $counter(type) {
00339         ""  {
00340         # Simple counter
00341         return
00342         }
00343         -group {
00344         upvar #0 counter::H-$tag histogram
00345         set subIndex [lindex $args 0]
00346         if {![info exists histogram($subIndex)]} {
00347             set histogram($subIndex) 0
00348         }
00349         set histogram($subIndex) [expr {$histogram($subIndex) + $delta}]
00350         }
00351         -lastn {
00352         upvar #0 counter::V-$tag vector
00353         set vector($counter(index)) $delta
00354         set counter(index) [expr {($counter(index) +1)%$counter(lastn)}]
00355         }
00356         -hist {
00357         upvar #0 counter::H-$tag histogram
00358         set bucket [expr {int($delta / $counter(bucketsize))}]
00359         if {![info exists histogram($bucket)]} {
00360             set histogram($bucket) 0
00361         }
00362         incr histogram($bucket)
00363         }
00364         -hist10x -
00365         -hist2x {
00366         upvar #0 counter::H-$tag histogram
00367         set bucket 0
00368         for {set max $counter(bucketsize)} {$delta > $max} \
00369             {set max [expr {$max * $counter(mult)}]} {
00370             incr bucket
00371         }
00372         if {![info exists histogram($bucket)]} {
00373             set histogram($bucket) 0
00374         }
00375         incr histogram($bucket)
00376         }
00377         -histlog {
00378         upvar #0 counter::H-$tag histogram
00379         set bucket [expr {int(log($delta)*$counter(bucketsize))}]
00380         if {![info exists histogram($bucket)]} {
00381             set histogram($bucket) 0
00382         }
00383         incr histogram($bucket)
00384         }
00385         -timehist {
00386         upvar #0 counter::H-$tag histogram
00387         variable minuteBase
00388         variable secsPerMinute
00389 
00390         set minute [expr {([clock seconds] - $minuteBase) / $secsPerMinute}]
00391         if {$minute > 59} {
00392             # this occurs while debugging if the process is
00393             # stopped at a breakpoint too long.
00394             set minute 59
00395         }
00396 
00397         # Initialize the current bucket and 
00398         # clear any buckets we've skipped since the last sample.
00399         
00400         if {$minute != $counter(lastMinute)} {
00401             set histogram($minute) 0
00402             for {set i [expr {$counter(lastMinute)+1}]} \
00403                 {$i < $minute} \
00404                 {incr i} {
00405             set histogram($i) 0
00406             }
00407             set counter(lastMinute) $minute
00408         }
00409         set histogram($minute) [expr {$histogram($minute) + $delta}]
00410         }
00411         default {#ignore}
00412     }
00413 #   }
00414     return
00415 }
00416 
00417 /*  ::counter::exists --*/
00418 /* */
00419 /*    Return true if the counter exists.*/
00420 /* */
00421 /*  Arguments:*/
00422 /*    tag The counter identifier.*/
00423 /* */
00424 /*  Results:*/
00425 /*    1 if it has been defined.*/
00426 /* */
00427 /*  Side Effects:*/
00428 /*    None.*/
00429 
00430 ret  ::counter::exists (type tag) {
00431     upvar #0 counter::T-$tag counter
00432     return [info exists counter]
00433 }
00434 
00435 /*  ::counter::get --*/
00436 /* */
00437 /*    Return statistics.*/
00438 /* */
00439 /*  Arguments:*/
00440 /*    tag The counter identifier.*/
00441 /*    option  What statistic to get*/
00442 /*    args    Needed by some options.*/
00443 /* */
00444 /*  Results:*/
00445 /*    With no args, just the counter value.*/
00446 /* */
00447 /*  Side Effects:*/
00448 /*    None.*/
00449 
00450 ret  ::counter::get (type tag , optional option =-total , type args) {
00451     upvar #0 counter::T-$tag counter
00452     switch -- $option {
00453     -total {
00454         return $counter(total)
00455     }
00456     -totalVar {
00457         return ::counter::T-$tag\(total)
00458     }
00459     -N {
00460         return $counter(N)
00461     }
00462     -avg {
00463         if {$counter(N) == 0} {
00464         return 0
00465         } else {
00466         return [expr {$counter(total) / double($counter(N))}]
00467         }
00468     }
00469     -avgn {
00470         if {$counter(type) != "-lastn"} {
00471         return -code error "The -avgn option is only supported for -lastn counters."
00472         }
00473         upvar #0 counter::V-$tag vector
00474         set sum 0
00475         for {set i 0} {($i < $counter(N)) && ($i < $counter(lastn))} {incr i} {
00476         set sum [expr {$sum + $vector($i)}]
00477         }
00478         if {$i == 0} {
00479         return 0
00480         } else {
00481         return [expr {$sum / double($i)}]
00482         }
00483     }
00484     -hist {
00485         upvar #0 counter::H-$tag histogram
00486         if {[llength $args]} {
00487         # Return particular bucket
00488         set bucket [lindex $args 0]
00489         if {[info exists histogram($bucket)]} {
00490             return $histogram($bucket)
00491         } else {
00492             return 0
00493         }
00494         } else {
00495         # Dump the whole histogram
00496 
00497         set result {}
00498         if {$counter(type) == "-group"} {
00499             set sort -dictionary
00500         } else {
00501             set sort -integer
00502         }
00503         foreach x [lsort $sort [array names histogram]] {
00504             lappend result $x $histogram($x)
00505         }
00506         return $result
00507         }
00508     }
00509     -histVar {
00510         return ::counter::H-$tag
00511     }
00512     -histHour {
00513         upvar #0 counter::Hour-$tag histogram
00514         set result {}
00515         foreach x [lsort -integer [array names histogram]] {
00516         lappend result $x $histogram($x)
00517         }
00518         return $result
00519     }
00520     -histHourVar {
00521         return ::counter::Hour-$tag
00522     }
00523     -histDay {
00524         upvar #0 counter::Day-$tag histogram
00525         set result {}
00526         foreach x [lsort -integer [array names histogram]] {
00527         lappend result $x $histogram($x)
00528         }
00529         return $result
00530     }
00531     -histDayVar {
00532         return ::counter::Day-$tag
00533     }
00534     -maxPerMinute {
00535         return $counter(maxPerMinute)
00536     }
00537     -maxPerHour {
00538         return $counter(maxPerHour)
00539     }
00540     -maxPerDay {
00541         return $counter(maxPerDay)
00542     }
00543     -resetDate {
00544         if {[info exists counter(resetDate)]} {
00545         return $counter(resetDate)
00546         } else {
00547         return ""
00548         }
00549     }
00550     -all {
00551         return [array get counter]
00552     }
00553     default {
00554         return -code error "Invalid option $option.\
00555         Should be -all, -total, -N, -avg, -avgn, -hist, -histHour,\
00556         -histDay, -totalVar, -histVar, -histHourVar, -histDayVar -resetDate."
00557     }
00558     }
00559 }
00560 
00561 /*  ::counter::names --*/
00562 /* */
00563 /*    Return the list of defined counters.*/
00564 /* */
00565 /*  Arguments:*/
00566 /*    none*/
00567 /* */
00568 /*  Results:*/
00569 /*    A list of counter tags.*/
00570 /* */
00571 /*  Side Effects:*/
00572 /*    None.*/
00573 
00574 ret  ::counter::names () {
00575     set result {}
00576     foreach v [info vars ::counter::T-*] {
00577     if {[info exists $v]} {
00578         # Declared arrays might not exist, yet
00579         # strip prefix from name
00580         set v [string range $v [string length "::counter::T-"] end]
00581         lappend result $v
00582     }
00583     }
00584     return $result
00585 }
00586 
00587 /*  ::counter::MergeHour --*/
00588 /* */
00589 /*    Sum the per-minute histogram into the next hourly bucket.*/
00590 /*    On 24-hour boundaries, sum the hourly buckets into the next day bucket.*/
00591 /*    This operates on all time-based histograms.*/
00592 /* */
00593 /*  Arguments:*/
00594 /*    none*/
00595 /* */
00596 /*  Results:*/
00597 /*    none*/
00598 /* */
00599 /*  Side Effects:*/
00600 /*    See description.*/
00601 
00602 ret  ::counter::MergeHour (type interval) {
00603     variable hourIndex
00604     variable minuteBase
00605     variable hourBase
00606     variable tagsToMerge
00607     variable secsPerMinute
00608 
00609     after $interval [list counter::MergeHour $interval]
00610     if {![info exists hourBase] || $hourIndex == 0} {
00611     set hourBase $minuteBase
00612     }
00613     set minuteBase [clock seconds]
00614 
00615     foreach tag $tagsToMerge {
00616     upvar #0 counter::T-$tag counter
00617     upvar #0 counter::H-$tag histogram
00618     upvar #0 counter::Hour-$tag hourhist
00619 
00620     # Clear any buckets we've skipped since the last sample.
00621 
00622     for {set i [expr {$counter(lastMinute)+1}]} {$i < 60} {incr i} {
00623         set histogram($i) 0
00624     }
00625     set counter(lastMinute) -1
00626 
00627     # Accumulate into the next hour bucket.
00628 
00629     set hourhist($hourIndex) 0
00630     set max 0
00631     foreach i [array names histogram] {
00632         set hourhist($hourIndex) [expr {$hourhist($hourIndex) + $histogram($i)}]
00633         if {$histogram($i) > $max} {
00634         set max $histogram($i)
00635         }
00636     }
00637     set perSec [expr {$max / $secsPerMinute}]
00638     if {$perSec > $counter(maxPerMinute)} {
00639         set counter(maxPerMinute) $perSec
00640     }
00641     }
00642     set hourIndex [expr {($hourIndex + 1) % 24}]
00643     if {$hourIndex == 0} {
00644     counter::MergeDay
00645     }
00646 
00647 }
00648 /*  ::counter::MergeDay --*/
00649 /* */
00650 /*    Sum the per-minute histogram into the next hourly bucket.*/
00651 /*    On 24-hour boundaries, sum the hourly buckets into the next day bucket.*/
00652 /*    This operates on all time-based histograms.*/
00653 /* */
00654 /*  Arguments:*/
00655 /*    none*/
00656 /* */
00657 /*  Results:*/
00658 /*    none*/
00659 /* */
00660 /*  Side Effects:*/
00661 /*    See description.*/
00662 
00663 ret  ::counter::MergeDay () {
00664     variable dayIndex
00665     variable dayBase
00666     variable hourBase
00667     variable tagsToMerge
00668     variable secsPerMinute
00669 
00670     # Save the hours histogram into a bucket for the last day
00671     # counter(day,$day) is the starting time for that day bucket
00672 
00673     if {![info exists dayBase]} {
00674     set dayBase $hourBase
00675     }
00676     foreach tag $tagsToMerge {
00677     upvar #0 counter::T-$tag counter
00678     upvar #0 counter::Day-$tag dayhist
00679     upvar #0 counter::Hour-$tag hourhist
00680     set dayhist($dayIndex) 0
00681     set max 0
00682     for {set i 0} {$i < 24} {incr i} {
00683         if {[info exists hourhist($i)]} {
00684         set dayhist($dayIndex) [expr {$dayhist($dayIndex) + $hourhist($i)}]
00685         if {$hourhist($i) > $max} { 
00686             set max $hourhist($i) 
00687         }
00688         }
00689     }
00690     set perSec [expr {double($max) / ($secsPerMinute * 60)}]
00691     if {$perSec > $counter(maxPerHour)} {
00692         set counter(maxPerHour) $perSec
00693     }
00694     }
00695     set perSec [expr {double($dayhist($dayIndex)) / ($secsPerMinute * 60 * 24)}]
00696     if {$perSec > $counter(maxPerDay)} {
00697     set counter(maxPerDay) $perSec
00698     }
00699     incr dayIndex
00700 }
00701 
00702 /*  ::counter::histHtmlDisplay --*/
00703 /* */
00704 /*    Create an html display of the histogram.*/
00705 /* */
00706 /*  Arguments:*/
00707 /*    tag The counter tag*/
00708 /*    args    option, value pairs that affect the display:*/
00709 /*        -title  Label to display above bar chart*/
00710 /*        -unit   minutes, hours, or days select time-base histograms.*/
00711 /*            Specify anything else for value-based histograms.*/
00712 /*        -images URL of /images directory.*/
00713 /*        -gif    Image for normal histogram bars*/
00714 /*        -ongif  Image for the active histogram bar*/
00715 /*        -max    Maximum number of value-based buckets to display*/
00716 /*        -height Pixel height of the highest bar*/
00717 /*        -width  Pixel width of each bar*/
00718 /*        -skip   Buckets to skip when labeling value-based histograms*/
00719 /*        -format Format used to display labels of buckets.*/
00720 /*        -text   If 1, a text version of the histogram is dumped,*/
00721 /*            otherwise a graphical one is generated.*/
00722 /* */
00723 /*  Results:*/
00724 /*    HTML for the display as a complete table.*/
00725 /* */
00726 /*  Side Effects:*/
00727 /*    None.*/
00728 
00729 ret  ::counter::histHtmlDisplay (type tag , type args) {
00730     append result "<p>\n<table border=0 cellpadding=0 cellspacing=0>\n"
00731     append result [eval {counter::histHtmlDisplayRow $tag} $args]
00732     append result </table>
00733     return $result
00734 }
00735 
00736 /*  ::counter::histHtmlDisplayRow --*/
00737 /* */
00738 /*    Create an html display of the histogram.*/
00739 /* */
00740 /*  Arguments:*/
00741 /*    See counter::histHtmlDisplay*/
00742 /* */
00743 /*  Results:*/
00744 /*    HTML for the display.  Ths is one row of a 2-column table,*/
00745 /*    the calling page must define the <table> tag.*/
00746 /* */
00747 /*  Side Effects:*/
00748 /*    None.*/
00749 
00750 ret  ::counter::histHtmlDisplayRow (type tag , type args) {
00751     upvar #0 counter::T-$tag counter
00752     variable secsPerMinute
00753     variable minuteBase
00754     variable hourBase
00755     variable dayBase
00756     variable hourIndex
00757     variable dayIndex
00758 
00759     array set options [list \
00760     -title  $tag \
00761     -unit   "" \
00762     -images /images \
00763     -gif    Blue.gif \
00764     -ongif  Red.gif \
00765     -max    -1 \
00766     -height 100 \
00767     -width  4 \
00768     -skip   4 \
00769     -format %.2f \
00770     -text   0
00771     ]
00772     array set options $args
00773 
00774     # Support for self-posting pages that can clear counters.
00775 
00776     append result "<!-- resetCounter [ncgi::value resetCounter] -->"
00777     if {[ncgi::value resetCounter] == $tag} {
00778     counter::reset $tag
00779     return "<!-- Reset $tag counter -->"
00780     }
00781 
00782     switch -glob -- $options(-unit) {
00783     min* {
00784         upvar #0 counter::H-$tag histogram
00785         set histname counter::H-$tag
00786         if {![info exists minuteBase]} {
00787         return "<!-- No time-based histograms defined -->"
00788         }
00789         set time $minuteBase
00790         set secsForMax $secsPerMinute
00791         set periodMax $counter(maxPerMinute)
00792         set curIndex [expr {([clock seconds] - $minuteBase) / $secsPerMinute}]
00793         set options(-max) 60
00794         set options(-min) 0
00795     }
00796     hour* {
00797         upvar #0 counter::Hour-$tag histogram
00798         set histname counter::Hour-$tag
00799         if {![info exists hourBase]} {
00800         return "<!-- Hour merge has not occurred -->"
00801         }
00802         set time $hourBase
00803         set secsForMax [expr {$secsPerMinute * 60}]
00804         set periodMax $counter(maxPerHour)
00805         set curIndex [expr {$hourIndex - 1}]
00806         if {$curIndex < 0} {
00807         set curIndex 23
00808         }
00809         set options(-max) 24
00810         set options(-min) 0
00811     }
00812     day* {
00813         upvar #0 counter::Day-$tag histogram
00814         set histname counter::Day-$tag
00815         if {![info exists dayBase]} {
00816         return "<!-- Hour merge has not occurred -->"
00817         }
00818         set time $dayBase
00819         set secsForMax [expr {$secsPerMinute * 60 * 24}]
00820         set periodMax $counter(maxPerDay)
00821         set curIndex dayIndex
00822         set options(-max) $dayIndex
00823         set options(-min) 0
00824     }
00825     default {
00826         # Value-based histogram with arbitrary units.
00827 
00828         upvar #0 counter::H-$tag histogram
00829         set histname counter::H-$tag
00830 
00831         set unit $options(-unit)
00832         set curIndex ""
00833         set time ""
00834     }
00835     }
00836     if {! [info exists histogram]} {
00837     return "<!-- $histname doesn't exist -->\n"
00838     }
00839 
00840     set max 0
00841     set maxName 0
00842     foreach {name value} [array get histogram] {
00843     if {$value > $max} {
00844         set max $value
00845         set maxName $name
00846     }
00847     }
00848 
00849     # Start 2-column HTML display.  A summary table at the left, the histogram on the right.
00850 
00851     append result "<tr><td valign=top>\n"
00852 
00853     append result "<table bgcolor=#EEEEEE>\n"
00854     append result "<tr><td colspan=2 align=center>[html::font]<b>$options(-title)</b></font></td></tr>\n"
00855     append result "<tr><td>[html::font]<b>Total</b></font></td>"
00856     append result "<td>[html::font][format $options(-format) $counter(total)]</font></td></tr>\n"
00857 
00858     if {[info exists secsForMax]} {
00859 
00860     # Time-base histogram
00861 
00862     set string {}
00863     set t $secsForMax
00864     set days [expr {$t / (60 * 60 * 24)}]
00865     if {$days == 1} {
00866         append string "1 Day "
00867     } elseif {$days > 1} {
00868         append string "$days Days "
00869     }
00870     set t [expr {$t - $days * (60 * 60 * 24)}]
00871     set hours [expr {$t / (60 * 60)}]
00872     if {$hours == 1} {
00873         append string "1 Hour "
00874     } elseif {$hours > 1} {
00875         append string "$hours Hours "
00876     }
00877     set t [expr {$t - $hours * (60 * 60)}]
00878     set mins [expr {$t / 60}]
00879     if {$mins == 1} {
00880         append string "1 Minute "
00881     } elseif {$mins > 1} {
00882         append string "$mins Minutes "
00883     }
00884     set t [expr {$t - $mins * 60}]
00885     if {$t == 1} {
00886         append string "1 Second "
00887     } elseif {$t > 1} {
00888         append string "$t Seconds "
00889     }
00890     append result "<tr><td>[html::font]<b>Bucket Size</b></font></td>"
00891     append result "<td>[html::font]$string</font></td></tr>\n"
00892 
00893     append result "<tr><td>[html::font]<b>Max Per Sec</b></font></td>"
00894     append result "<td>[html::font][format %.2f [expr {$max/double($secsForMax)}]]</font></td></tr>\n"
00895 
00896     if {$periodMax > 0} {
00897         append result "<tr><td>[html::font]<b>Best Per Sec</b></font></td>"
00898         append result "<td>[html::font][format %.2f $periodMax]</font></td></tr>\n"
00899     }
00900     append result "<tr><td>[html::font]<b>Starting Time</b></font></td>"
00901     switch -glob -- $options(-unit) {
00902         min* {
00903         append result "<td>[html::font][clock format $time \
00904             -format %k:%M:%S]</font></td></tr>\n"
00905         }
00906         hour* {
00907         append result "<td>[html::font][clock format $time \
00908             -format %k:%M:%S]</font></td></tr>\n"
00909         }
00910         day* {
00911         append result "<td>[html::font][clock format $time \
00912             -format "%b %d %k:%M"]</font></td></tr>\n"
00913         }
00914         default {#ignore}
00915     }
00916 
00917     } else {
00918 
00919     # Value-base histogram
00920 
00921     set ix [lsort -integer [array names histogram]]
00922 
00923     set mode [expr {$counter(bucketsize) * $maxName}]
00924     set first [expr {$counter(bucketsize) * [lindex $ix 0]}]
00925     set last [expr {$counter(bucketsize) * [lindex $ix end]}]
00926 
00927     append result "<tr><td>[html::font]<b>Average</b></font></td>"
00928     append result "<td>[html::font][format $options(-format) [counter::get $tag -avg]]</font></td></tr>\n"
00929 
00930     append result "<tr><td>[html::font]<b>Mode</b></font></td>"
00931     append result "<td>[html::font]$mode</font></td></tr>\n"
00932 
00933     append result "<tr><td>[html::font]<b>Minimum</b></font></td>"
00934     append result "<td>[html::font]$first</font></td></tr>\n"
00935 
00936     append result "<tr><td>[html::font]<b>Maximum</b></font></td>"
00937     append result "<td>[html::font]$last</font></td></tr>\n"
00938 
00939     append result "<tr><td>[html::font]<b>Unit</b></font></td>"
00940     append result "<td>[html::font]$unit</font></td></tr>\n"
00941 
00942     append result "<tr><td colspan=2 align=center>[html::font]<b>"
00943     append result "<a href=[ncgi::urlStub]?resetCounter=$tag>Reset</a></td></tr>\n"
00944 
00945     if {$options(-max) < 0} {
00946         set options(-max) [lindex $ix end]
00947     }
00948     if {![info exists options(-min)]} {
00949         set options(-min) [lindex $ix 0]
00950     }
00951     }
00952 
00953     # End table nested inside left-hand column
00954 
00955     append result </table>\n
00956     append result </td>\n
00957     append result "<td valign=bottom>\n"
00958 
00959 
00960     # Display the histogram
00961 
00962     if {$options(-text)} {
00963     } else {
00964     append result [eval \
00965         {counter::histHtmlDisplayBarChart $tag histogram $max $curIndex $time} \
00966         [array get options]]
00967     }
00968 
00969     # Close the right hand column, but leave our caller's table open.
00970 
00971     append result </td></tr>\n
00972 
00973     return $result
00974 }
00975 
00976 /*  ::counter::histHtmlDisplayBarChart --*/
00977 /* */
00978 /*    Create an html display of the histogram.*/
00979 /* */
00980 /*  Arguments:*/
00981 /*    tag     The counter tag.*/
00982 /*    histVar     The name of the histogram array*/
00983 /*    max     The maximum counter value in a histogram bucket.*/
00984 /*    curIndex    The "current" histogram index, for time-base histograms.*/
00985 /*    time        The base, or starting time, for the time-based histograms.*/
00986 /*    args        The array get of the options passed into histHtmlDisplay*/
00987 /* */
00988 /*  Results:*/
00989 /*    HTML for the bar chart.*/
00990 /* */
00991 /*  Side Effects:*/
00992 /*    See description.*/
00993 
00994 ret  ::counter::histHtmlDisplayBarChart (type tag , type histVar , type max , type curIndex , type time , type args) {
00995     upvar #0 counter::T-$tag counter
00996     upvar 1 $histVar histogram
00997     variable secsPerMinute
00998     array set options $args
00999 
01000     append result "<table cellpadding=0 cellspacing=0 bgcolor=#eeeeee><tr>\n"
01001 
01002     set ix [lsort -integer [array names histogram]]
01003 
01004     for {set t $options(-min)} {$t < $options(-max)} {incr t} {
01005     if {![info exists histogram($t)]} {
01006         set value 0
01007     } else {
01008         set value $histogram($t)
01009     }
01010     if {$max == 0 || $value == 0} {
01011         set height 1
01012     } else {
01013         set percent [expr {round($value * 100.0 / $max)}]
01014         set height [expr {$percent * $options(-height) / 100}]
01015     }
01016     if {$t == $curIndex} {
01017         set img src=$options(-images)/$options(-ongif)
01018     } else {
01019         set img src=$options(-images)/$options(-gif)
01020     }
01021     append result "<td valign=bottom><img $img height=$height\
01022         width=$options(-width) title=$value alt=$value></td>\n"
01023     }
01024     append result "</tr>"
01025 
01026     # Count buckets outside the range requested
01027 
01028     set overflow 0
01029     set underflow 0
01030     foreach t [lsort -integer [array names histogram]] {
01031     if {($options(-max) > 0) && ($t > $options(-max))} {
01032         incr overflow
01033     }
01034     if {($options(-min) >= 0) && ($t < $options(-min))} {
01035         incr underflow
01036     }
01037     }
01038 
01039     # Append a row of labels at the bottom.
01040 
01041     set colors {black #CCCCCC}
01042     set bgcolors {#CCCCCC black}
01043     set colori 0
01044     if {$counter(type) != "-timehist"} {
01045 
01046     # Label each bucket with its value
01047     # This is probably wrong for hist2x and hist10x
01048 
01049     append result "<tr>"
01050     set skip $options(-skip)
01051     if {![info exists counter(mult)]} {
01052         set counter(mult) 1
01053     }
01054 
01055     # These are tick marks
01056 
01057     set img src=$options(-images)/$options(-gif)
01058     append result "<tr>"
01059     for {set i $options(-min)} {$i < $options(-max)} {incr i} {
01060         if {(($i % $skip) == 0)} {
01061         append result "<td valign=bottom><img $img height=3 \
01062             width=1></td>\n"
01063         } else {
01064         append result "<td valign=bottom></td>"
01065         }
01066     }
01067     append result </tr>
01068 
01069     # These are the labels
01070 
01071     append result "<tr>"
01072     for {set i $options(-min)} {$i < $options(-max)} {incr i} {
01073         if {$counter(type) == "-histlog"} {
01074         if {[catch {expr {int(log($i) * $counter(bucketsize))}} x]} {
01075             # Out-of-bounds
01076             break
01077         }
01078         } else {
01079         set x [expr {$i * $counter(bucketsize) * $counter(mult)}]
01080         }
01081         set label [format $options(-format) $x]
01082         if {(($i % $skip) == 0)} {
01083         set color [lindex $colors $colori]
01084         set bg [lindex $bgcolors $colori]
01085         set colori [expr {($colori+1) % 2}]
01086         append result "<td colspan=$skip><font size=1 color=$color>$label</font></td>"
01087         }
01088     }
01089     append result </tr>
01090     } else {
01091     switch -glob -- $options(-unit) {
01092         min*    {
01093         if {$secsPerMinute != 60} {
01094             set format %k:%M:%S
01095             set skip 12
01096         } else {
01097             set format %k:%M
01098             set skip 4
01099         }
01100         set deltaT $secsPerMinute
01101         set wrapDeltaT [expr {$secsPerMinute * -59}]
01102         }
01103         hour*   {
01104         if {$secsPerMinute != 60} {
01105             set format %k:%M
01106             set skip 4
01107         } else {
01108             set format %k
01109             set skip 2
01110         }
01111         set deltaT [expr {$secsPerMinute * 60}]
01112         set wrapDeltaT [expr {$secsPerMinute * 60 * -23}]
01113         }
01114         day* {
01115         if {$secsPerMinute != 60} {
01116             set format "%m/%d %k:%M"
01117             set skip 10
01118         } else {
01119             set format %k
01120             set skip $options(-skip)
01121         }
01122         set deltaT [expr {$secsPerMinute * 60 * 24}]
01123         set wrapDeltaT 0
01124         }
01125         default {#ignore}
01126     }
01127     # These are tick marks
01128 
01129     set img src=$options(-images)/$options(-gif)
01130     append result "<tr>"
01131     foreach t [lsort -integer [array names histogram]] {
01132         if {(($t % $skip) == 0)} {
01133         append result "<td valign=bottom><img $img height=3 \
01134             width=1></td>\n"
01135         } else {
01136         append result "<td valign=bottom></td>"
01137         }
01138     }
01139     append result </tr>
01140 
01141     set lastLabel ""
01142     append result "<tr>"
01143     foreach t [lsort -integer [array names histogram]] {
01144 
01145         # Label each bucket with its time
01146 
01147         set label [clock format $time -format $format]
01148         if {(($t % $skip) == 0) && ($label != $lastLabel)} {
01149         set color [lindex $colors $colori]
01150         set bg [lindex $bgcolors $colori]
01151         set colori [expr {($colori+1) % 2}]
01152         append result "<td colspan=$skip><font size=1 color=$color>$label</font></td>"
01153         set lastLabel $label
01154         }
01155         if {$t == $curIndex} {
01156         incr time $wrapDeltaT
01157         } else {
01158         incr time $deltaT
01159         }
01160     }
01161     append result </tr>\n
01162     }
01163     append result "</table>"
01164     if {$underflow > 0} {
01165     append result "<br>Skipped $underflow samples <\
01166         [expr {$options(-min) * $counter(bucketsize)}]\n"
01167     }
01168     if {$overflow > 0} {
01169     append result "<br>Skipped $overflow samples >\
01170         [expr {$options(-max) * $counter(bucketsize)}]\n"
01171     }
01172     return $result
01173 }
01174 
01175 /*  ::counter::start --*/
01176 /* */
01177 /*    Start an interval timer.  This should be pre-declared with*/
01178 /*    type either -hist, -hist2x, or -hist20x*/
01179 /* */
01180 /*  Arguments:*/
01181 /*    tag     The counter identifier.*/
01182 /*    instance    There may be multiple intervals outstanding*/
01183 /*            at any time.  This serves to distinquish them.*/
01184 /* */
01185 /*  Results:*/
01186 /*    None*/
01187 /* */
01188 /*  Side Effects:*/
01189 /*    Records the starting time for the instance of this interval.*/
01190 
01191 ret  ::counter::start (type tag , type instance) {
01192     upvar #0 counter::Time-$tag time
01193     # clock clicks can return negative values if the sign bit is set
01194     # Here we turn it into a 31-bit counter because we only want
01195     # relative differences
01196     set msec [expr {[clock clicks -milliseconds] & 0x7FFFFFFF}]
01197     set time($instance) [list $msec [clock seconds]]
01198 }
01199 
01200 /*  ::counter::stop --*/
01201 /* */
01202 /*    Record an interval timer.*/
01203 /* */
01204 /*  Arguments:*/
01205 /*    tag     The counter identifier.*/
01206 /*    instance    There may be multiple intervals outstanding*/
01207 /*            at any time.  This serves to distinquish them.*/
01208 /*    func        An optional function used to massage the time*/
01209 /*            stamp before putting into the histogram.*/
01210 /* */
01211 /*  Results:*/
01212 /*    None*/
01213 /* */
01214 /*  Side Effects:*/
01215 /*    Computes the current interval and adds it to the histogram.*/
01216 
01217 ret  ::counter::stop (type tag , type instance , optional func =::counter::Identity) {
01218     upvar #0 counter::Time-$tag time
01219 
01220     if {![info exists time($instance)]} {
01221     # Extra call. Ignore so we can debug error cases.
01222     return
01223     }
01224     set msec [expr {[clock clicks -milliseconds] & 0x7FFFFFFF}]
01225     set now [list $msec [clock seconds]]
01226     set delMicros [expr {[lindex $now 0] - [lindex $time($instance) 0]}]
01227     if {$delMicros < 0} {
01228       # Microsecond counter wrapped.
01229       set delMicros [expr {0x7FFFFFFF - [lindex $time($instance) 0] +
01230                             [lindex $now 0]}]
01231     }
01232     set delSecond [expr {[lindex $now 1] - [lindex $time($instance) 1]}]
01233     unset time($instance)
01234 
01235     # It is quite possible that the millisecond counter is much
01236     # larger than 1000, so we just use it unless our microsecond
01237     # calculation is screwed up.
01238 
01239     if {$delMicros >= 0} {
01240       counter::count $tag [$func [expr {$delMicros / 1000.0}]]
01241     } else {
01242       counter::count $tag [$func $delSecond]
01243     }
01244 }
01245 
01246 /*  ::counter::Identity --*/
01247 /* */
01248 /*    Return its argument.  This is used as the default function*/
01249 /*    to apply to an interval timer.*/
01250 /* */
01251 /*  Arguments:*/
01252 /*    x       Some value.*/
01253 /* */
01254 /*  Results:*/
01255 /*    $x*/
01256 /* */
01257 /*  Side Effects:*/
01258 /*    None*/
01259 
01260 
01261 ret  ::counter::Identity (type x) {
01262     return $x
01263 }
01264 
01265 package provide counter 2.0.4
01266 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1