home *** CD-ROM | disk | FTP | other *** search
/ H4CK3R 5 / hacker05 / 05_HACK_05.ISO / programacao / freewrap / TCLLIBsampleApp.exe / sample / tcllib / tcllib1.0 / counter / counter.tcl next >
Encoding:
Text File  |  2001-08-17  |  30.3 KB  |  1,253 lines

  1. # counter.tcl --
  2. #
  3. #    Procedures to manage simple counters and histograms.
  4. #
  5. # Copyright (c) 1998-2000 by Ajuba Solutions.
  6. #
  7. # See the file "license.terms" for information on usage and redistribution
  8. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  9. #
  10. # RCS: @(#) $Id: counter.tcl,v 1.7 2001/08/02 16:38:06 andreas_kupries Exp $
  11.  
  12. package require Tcl 8
  13.  
  14. namespace eval counter:: {
  15.  
  16.     # Variables of name counter::T-$tagname
  17.     # are created as arrays to support each counter.
  18.  
  19.     # Time-based histograms are kept in sync with each other,
  20.     # so these variables are shared among them.
  21.     # These base times record the time corresponding to the first bucket 
  22.     # of the per-minute, per-hour, and per-day time-based histograms.
  23.  
  24.     variable startTime
  25.     variable minuteBase
  26.     variable hourBase
  27.     variable hourEnd
  28.     variable dayBase
  29.     variable hourIndex
  30.     variable dayIndex
  31.  
  32.     # The time-based histogram uses an after event and a list
  33.     # of counters to do mergeing on.
  34.  
  35.     variable tagsToMerge
  36.     if {![info exist tagsToMerge]} {
  37.     set tagsToMerge {}
  38.     }
  39.     variable mergeInterval
  40.  
  41.     namespace export *
  42. }
  43.  
  44. # counter::init --
  45. #
  46. #    Set up a counter.
  47. #
  48. # Arguments:
  49. #    tag    The identifier for the counter.  Pass this to counter::count
  50. #    args    option values pairs that define characteristics of the counter:
  51. #        See the man page for definitons.
  52. #
  53. # Results:
  54. #    None.
  55. #
  56. # Side Effects:
  57. #    Initializes state about a counter.
  58.  
  59. proc counter::init {tag args} {
  60.     upvar #0 counter::T-$tag counter
  61.     if {[info exists counter]} {
  62.     unset counter
  63.     }
  64.     set counter(N) 0    ;# Number of samples
  65.     set counter(total) 0
  66.     set counter(type) {}
  67.  
  68.     # With an empty type the counter is a simple accumulator
  69.     # for which we can compute an average.  Here we loop through
  70.     # the args to determine what additional counter attributes
  71.     # we need to maintain in counter::count
  72.  
  73.     foreach {option value} $args {
  74.     switch -- $option {
  75.         -timehist {
  76.         variable tagsToMerge
  77.         variable secsPerMinute
  78.         variable startTime
  79.         variable minuteBase
  80.         variable hourBase
  81.         variable dayBase
  82.         variable hourIndex
  83.         variable dayIndex
  84.  
  85.         upvar #0 counter::H-$tag histogram
  86.         upvar #0 counter::Hour-$tag hourhist
  87.         upvar #0 counter::Day-$tag dayhist
  88.  
  89.         # Clear the histograms.
  90.  
  91.         for {set i 0} {$i < 60} {incr i} {
  92.             set histogram($i) 0
  93.         }
  94.         for {set i 0} {$i < 24} {incr i} {
  95.             set hourhist($i) 0
  96.         }
  97.         if {[info exist dayhist]} {
  98.             unset dayhist
  99.         }
  100.         set dayhist(0) 0
  101.  
  102.         # Clear all-time high records
  103.  
  104.         set counter(maxPerMinute) 0
  105.         set counter(maxPerHour) 0
  106.         set counter(maxPerDay) 0
  107.  
  108.         # The value associated with -timehist is the number of seconds
  109.         # in each bucket.  Normally this is 60, but for
  110.         # testing, we compress minutes.  The value is limited at
  111.         # 60 because the per-minute buckets are accumulated into
  112.         # per-hour buckets later.
  113.  
  114.         if {$value == "" || $value == 0 || $value > 60} {
  115.             set value 60
  116.         }
  117.  
  118.         # Histogram state variables.
  119.         # All time-base histograms share the same bucket size
  120.         # and starting times to keep them all synchronized.
  121.         # So, we only initialize these parameters once.
  122.  
  123.         if {![info exist secsPerMinute]} {
  124.             set secsPerMinute $value
  125.  
  126.             set startTime [clock seconds]
  127.             set dayIndex 0
  128.  
  129.             set dayStart [clock scan [clock format $startTime \
  130.                 -format 00:00]]
  131.             
  132.             # Figure out what "hour" we are
  133.  
  134.             set delta [expr {$startTime - $dayStart}]
  135.             set hourIndex [expr {$delta / ($secsPerMinute * 60)}]
  136.             set day [expr {$hourIndex / 24}]
  137.             set hourIndex [expr {$hourIndex % 24}]
  138.  
  139.             set hourBase [expr {$dayStart + $day * $secsPerMinute * 60 * 24}]
  140.             set minuteBase [expr {$hourBase + $hourIndex * 60 * $secsPerMinute}]
  141.  
  142.             set partialHour [expr {$startTime -
  143.             ($hourBase + $hourIndex * 60 * $secsPerMinute)}]
  144.             set secs [expr {(60 * $secsPerMinute) - $partialHour}]
  145.             if {$secs <= 0} {
  146.             set secs 1
  147.             }
  148.  
  149.             # After the first timer, the event occurs once each "hour"
  150.  
  151.             set mergeInterval [expr {60 * $secsPerMinute * 1000}]
  152.             after [expr {$secs * 1000}] [list counter::MergeHour $mergeInterval]
  153.         }
  154.         if {[lsearch $tagsToMerge $tag] < 0} {
  155.             lappend tagsToMerge $tag
  156.         }
  157.  
  158.         # This records the last used slots in order to zero-out the
  159.         # buckets that are skipped during idle periods.
  160.  
  161.         set counter(lastMinute) -1
  162.  
  163.         # The following is referenced when bugs cause histogram
  164.         # hits outside the expect range (overflow and underflow)
  165.  
  166.         set counter(bucketsize)     0
  167.         }
  168.         -group {
  169.         # Cluster a set of counters with a single total
  170.  
  171.         upvar #0 counter::H-$tag histogram
  172.         if {[info exist histogram]} {
  173.             unset histogram
  174.         }
  175.         set counter(group) $value
  176.         }
  177.         -lastn {
  178.         # The lastN samples are kept if a vector to form a running average.
  179.  
  180.         upvar #0 counter::V-$tag vector
  181.         set counter(lastn) $value
  182.         set counter(index) 0
  183.         if {[info exist vector]} {
  184.             unset vector
  185.         }
  186.         for {set i 0} {$i < $value} {incr i} {
  187.             set vector($i) 0
  188.         }
  189.         }
  190.         -hist {
  191.         # A value-based histogram with buckets for different values.
  192.  
  193.         upvar #0 counter::H-$tag histogram
  194.         if {[info exist histogram]} {
  195.             unset histogram
  196.         }
  197.         set counter(bucketsize) $value
  198.         set counter(mult) 1
  199.         }
  200.         -hist2x {
  201.         upvar #0 counter::H-$tag histogram
  202.         if {[info exist histogram]} {
  203.             unset histogram
  204.         }
  205.         set counter(bucketsize) $value
  206.         set counter(mult) 2
  207.         }
  208.         -hist10x {
  209.         upvar #0 counter::H-$tag histogram
  210.         if {[info exist histogram]} {
  211.             unset histogram
  212.         }
  213.         set counter(bucketsize) $value
  214.         set counter(mult) 10
  215.         }
  216.         -histlog {
  217.         upvar #0 counter::H-$tag histogram
  218.         if {[info exist histogram]} {
  219.             unset histogram
  220.         }
  221.         set counter(bucketsize) $value
  222.         }
  223.         -simple {
  224.         # Useful when disabling predefined -timehist or -group counter
  225.         }
  226.         default {
  227.         return -code error "Unsupported option $option.\
  228.         Must be -timehist, -group, -lastn, -hist, -hist2x, -hist10x, -histlog, or -simple."
  229.         }
  230.     }
  231.     if {[string length $option]} {
  232.         # In case an option doesn't change the type, but
  233.         # this feature of the interface isn't used, etc.
  234.  
  235.         lappend counter(type) $option
  236.     }
  237.     }
  238.  
  239.     # Instead of supporting a counter that could have multiple attributes,
  240.     # we support a single type to make counting more efficient.
  241.  
  242.     if {[llength $counter(type)] > 1} {
  243.     return -code error "Multiple type attributes not supported.  Use only one of\
  244.         -timehist, -group, -lastn, -hist, -hist2x, -hist10x, -histlog, -disabled."
  245.     }
  246.     return ""
  247. }
  248.  
  249. # counter::reset --
  250. #
  251. #    Reset a counter.
  252. #
  253. # Arguments:
  254. #    tag    The identifier for the counter.
  255. #
  256. # Results:
  257. #    None.
  258. #
  259. # Side Effects:
  260. #    Deletes the counter and calls counter::init again for it.
  261.  
  262. proc counter::reset {tag args} {
  263.     upvar #0 counter::T-$tag counter
  264.  
  265.     # Layer reset on top of init.  Here we figure out what
  266.     # we need to pass into the init procedure to recreate it.
  267.  
  268.     switch -- $counter(type) {
  269.     ""    {
  270.         set args ""
  271.     }
  272.     -group {
  273.         upvar #0 counter::H-$tag histogram
  274.         if {[info exist histogram]} {
  275.         unset histogram
  276.         }
  277.         set args [list -group $counter(group)]
  278.     }
  279.     -lastn {
  280.         upvar #0 counter::V-$tag vector
  281.         if {[info exist vector]} {
  282.         unset vector
  283.         }
  284.         set args [list -lastn $counter(lastn)]
  285.     }
  286.     -hist -
  287.     -hist10x -
  288.     -histlog -
  289.     -hist2x {
  290.         upvar #0 counter::H-$tag histogram
  291.         if {[info exist histogram]} {
  292.         unset histogram
  293.         }
  294.         set args [list $counter(type) $counter(bucketsize)]
  295.     }
  296.     -timehist {
  297.         foreach h [list counter::H-$tag counter::Hour-$tag counter::Day-$tag] {
  298.         upvar #0 $h histogram
  299.         if {[info exist histogram]} {
  300.             unset histogram
  301.         }
  302.         }
  303.         set args [list -timehist $counter::secsPerMinute]
  304.     }
  305.     default {#ignore}
  306.     }
  307.     unset counter
  308.     eval {counter::init $tag} $args
  309.     set counter(resetDate) [clock seconds]
  310.     return ""
  311. }
  312.  
  313. # counter::count --
  314. #
  315. #    Accumulate statistics.
  316. #
  317. # Arguments:
  318. #    tag    The counter identifier.
  319. #    delta    The increment amount.  Defaults to 1.
  320. #    arg    For -group types, this is the histogram index.
  321. #
  322. # Results:
  323. #    None
  324. #
  325. # Side Effects:
  326. #    Accumlate statistics.
  327.  
  328. proc counter::count {tag {delta 1} args} {
  329.     upvar #0 counter::T-$tag counter
  330.     set counter(total) [expr {$counter(total) + $delta}]
  331.     incr counter(N)
  332.  
  333.     # Instead of supporting a counter that could have multiple attributes,
  334.     # we support a single type to make counting a skosh more efficient.
  335.  
  336. #    foreach option $counter(type) {
  337.     switch -- $counter(type) {
  338.         ""    {
  339.         # Simple counter
  340.         return
  341.         }
  342.         -group {
  343.         upvar #0 counter::H-$tag histogram
  344.         set subIndex [lindex $args 0]
  345.         if {![info exists histogram($subIndex)]} {
  346.             set histogram($subIndex) 0
  347.         }
  348.         set histogram($subIndex) [expr {$histogram($subIndex) + $delta}]
  349.         }
  350.         -lastn {
  351.         upvar #0 counter::V-$tag vector
  352.         set vector($counter(index)) $delta
  353.         set counter(index) [expr {($counter(index) +1)%$counter(lastn)}]
  354.         }
  355.         -hist {
  356.         upvar #0 counter::H-$tag histogram
  357.         set bucket [expr {int($delta / $counter(bucketsize))}]
  358.         if {![info exist histogram($bucket)]} {
  359.             set histogram($bucket) 0
  360.         }
  361.         incr histogram($bucket)
  362.         }
  363.         -hist10x -
  364.         -hist2x {
  365.         upvar #0 counter::H-$tag histogram
  366.         set bucket 0
  367.         for {set max $counter(bucketsize)} {$delta > $max} \
  368.             {set max [expr {$max * $counter(mult)}]} {
  369.             incr bucket
  370.         }
  371.         if {![info exist histogram($bucket)]} {
  372.             set histogram($bucket) 0
  373.         }
  374.         incr histogram($bucket)
  375.         }
  376.         -histlog {
  377.         upvar #0 counter::H-$tag histogram
  378.         set bucket [expr {int(log($delta)*$counter(bucketsize))}]
  379.         if {![info exist histogram($bucket)]} {
  380.             set histogram($bucket) 0
  381.         }
  382.         incr histogram($bucket)
  383.         }
  384.         -timehist {
  385.         upvar #0 counter::H-$tag histogram
  386.         variable minuteBase
  387.         variable secsPerMinute
  388.  
  389.         set minute [expr {([clock seconds] - $minuteBase) / $secsPerMinute}]
  390.         if {$minute > 59} {
  391.             # this occurs while debugging if the process is
  392.             # stopped at a breakpoint too long.
  393.             set minute 59
  394.         }
  395.  
  396.         # Initialize the current bucket and 
  397.         # clear any buckets we've skipped since the last sample.
  398.         
  399.         if {$minute != $counter(lastMinute)} {
  400.             set histogram($minute) 0
  401.             for {set i [expr {$counter(lastMinute)+1}]} \
  402.                 {$i < $minute} \
  403.                 {incr i} {
  404.             set histogram($i) 0
  405.             }
  406.             set counter(lastMinute) $minute
  407.         }
  408.         set histogram($minute) [expr {$histogram($minute) + $delta}]
  409.         }
  410.         default {#ignore}
  411.     }
  412. #   }
  413.     return
  414. }
  415.  
  416. # counter::exists --
  417. #
  418. #    Return true if the counter exists.
  419. #
  420. # Arguments:
  421. #    tag    The counter identifier.
  422. #
  423. # Results:
  424. #    1 if it has been defined.
  425. #
  426. # Side Effects:
  427. #    None.
  428.  
  429. proc counter::exists {tag} {
  430.     upvar #0 counter::T-$tag counter
  431.     return [info exists counter]
  432. }
  433.  
  434. # counter::get --
  435. #
  436. #    Return statistics.
  437. #
  438. # Arguments:
  439. #    tag    The counter identifier.
  440. #    option    What statistic to get
  441. #    args    Needed by some options.
  442. #
  443. # Results:
  444. #    With no args, just the counter value.
  445. #
  446. # Side Effects:
  447. #    None.
  448.  
  449. proc counter::get {tag {option -total} args} {
  450.     upvar #0 counter::T-$tag counter
  451.     switch -- $option {
  452.     -total {
  453.         return $counter(total)
  454.     }
  455.     -totalVar {
  456.         return ::counter::T-$tag\(total)
  457.     }
  458.     -N {
  459.         return $counter(N)
  460.     }
  461.     -avg {
  462.         if {$counter(N) == 0} {
  463.         return 0
  464.         } else {
  465.         return [expr {$counter(total) / double($counter(N))}]
  466.         }
  467.     }
  468.     -avgn {
  469.         upvar #0 counter::V-$tag vector
  470.         set sum 0
  471.         for {set i 0} {($i < $counter(N)) && ($i < $counter(lastn))} {incr i} {
  472.         set sum [expr {$sum + $vector($i)}]
  473.         }
  474.         if {$i == 0} {
  475.         return 0
  476.         } else {
  477.         return [expr {$sum / double($i)}]
  478.         }
  479.     }
  480.     -hist {
  481.         upvar #0 counter::H-$tag histogram
  482.         if {[llength $args]} {
  483.         # Return particular bucket
  484.         set bucket [lindex $args 0]
  485.         if {[info exist histogram($bucket)]} {
  486.             return $histogram($bucket)
  487.         } else {
  488.             return 0
  489.         }
  490.         } else {
  491.         # Dump the whole histogram
  492.  
  493.         set result {}
  494.         if {$counter(type) == "-group"} {
  495.             set sort -dictionary
  496.         } else {
  497.             set sort -integer
  498.         }
  499.         foreach x [lsort $sort [array names histogram]] {
  500.             lappend result $x $histogram($x)
  501.         }
  502.         return $result
  503.         }
  504.     }
  505.     -histVar {
  506.         return ::counter::H-$tag
  507.     }
  508.     -histHour {
  509.         upvar #0 counter::Hour-$tag histogram
  510.         set result {}
  511.         foreach x [lsort -integer [array names histogram]] {
  512.         lappend result $x $histogram($x)
  513.         }
  514.         return $result
  515.     }
  516.     -histHourVar {
  517.         return ::counter::Hour-$tag
  518.     }
  519.     -histDay {
  520.         upvar #0 counter::Day-$tag histogram
  521.         set result {}
  522.         foreach x [lsort -integer [array names histogram]] {
  523.         lappend result $x $histogram($x)
  524.         }
  525.         return $result
  526.     }
  527.     -histDayVar {
  528.         return ::counter::Day-$tag
  529.     }
  530.     -maxPerMinute {
  531.         return $counter(maxPerMinute)
  532.     }
  533.     -maxPerHour {
  534.         return $counter(maxPerHour)
  535.     }
  536.     -maxPerDay {
  537.         return $counter(maxPerDay)
  538.     }
  539.     -resetDate {
  540.         if {[info exists counter(resetDate)]} {
  541.         return $counter(resetDate)
  542.         } else {
  543.         return ""
  544.         }
  545.     }
  546.     -all {
  547.         return [array get counter]
  548.     }
  549.     default {
  550.         return -code error "Invalid option $option.\
  551.         Should be -all, -total, -N, -avg, -avgn, -hist, -histHour,\
  552.         -histDay, -totalVar, -histVar, -histHourVar, -histDayVar -resetDate."
  553.     }
  554.     }
  555. }
  556.  
  557. # counter::names --
  558. #
  559. #    Return the list of defined counters.
  560. #
  561. # Arguments:
  562. #    none
  563. #
  564. # Results:
  565. #    A list of counter tags.
  566. #
  567. # Side Effects:
  568. #    None.
  569.  
  570. proc counter::names {} {
  571.     set result {}
  572.     foreach v [info vars ::counter::T-*] {
  573.     if {[info exist $v]} {
  574.         # Declared arrays might not exist, yet
  575.         regsub -- ::counter::T- $v {} v
  576.         lappend result $v
  577.     }
  578.     }
  579.     return $result
  580. }
  581.  
  582. # counter::MergeHour --
  583. #
  584. #    Sum the per-minute histogram into the next hourly bucket.
  585. #    On 24-hour boundaries, sum the hourly buckets into the next day bucket.
  586. #    This operates on all time-based histograms.
  587. #
  588. # Arguments:
  589. #    none
  590. #
  591. # Results:
  592. #    none
  593. #
  594. # Side Effects:
  595. #    See description.
  596.  
  597. proc counter::MergeHour {interval} {
  598.     variable hourIndex
  599.     variable minuteBase
  600.     variable hourBase
  601.     variable tagsToMerge
  602.     variable secsPerMinute
  603.  
  604.     after $interval [list counter::MergeHour $interval]
  605.     if {![info exist hourBase] || $hourIndex == 0} {
  606.     set hourBase $minuteBase
  607.     }
  608.     set minuteBase [clock seconds]
  609.  
  610.     foreach tag $tagsToMerge {
  611.     upvar #0 counter::T-$tag counter
  612.     upvar #0 counter::H-$tag histogram
  613.     upvar #0 counter::Hour-$tag hourhist
  614.  
  615.     # Clear any buckets we've skipped since the last sample.
  616.  
  617.     for {set i [expr {$counter(lastMinute)+1}]} {$i < 60} {incr i} {
  618.         set histogram($i) 0
  619.     }
  620.     set counter(lastMinute) -1
  621.  
  622.     # Accumulate into the next hour bucket.
  623.  
  624.     set hourhist($hourIndex) 0
  625.     set max 0
  626.     foreach i [array names histogram] {
  627.         set hourhist($hourIndex) [expr {$hourhist($hourIndex) + $histogram($i)}]
  628.         if {$histogram($i) > $max} {
  629.         set max $histogram($i)
  630.         }
  631.     }
  632.     set perSec [expr {$max / $secsPerMinute}]
  633.     if {$perSec > $counter(maxPerMinute)} {
  634.         set counter(maxPerMinute) $perSec
  635.     }
  636.     }
  637.     set hourIndex [expr {($hourIndex + 1) % 24}]
  638.     if {$hourIndex == 0} {
  639.     counter::MergeDay
  640.     }
  641.  
  642. }
  643. # counter::MergeDay --
  644. #
  645. #    Sum the per-minute histogram into the next hourly bucket.
  646. #    On 24-hour boundaries, sum the hourly buckets into the next day bucket.
  647. #    This operates on all time-based histograms.
  648. #
  649. # Arguments:
  650. #    none
  651. #
  652. # Results:
  653. #    none
  654. #
  655. # Side Effects:
  656. #    See description.
  657.  
  658. proc counter::MergeDay {} {
  659.     variable dayIndex
  660.     variable dayBase
  661.     variable hourBase
  662.     variable tagsToMerge
  663.     variable secsPerMinute
  664.  
  665.     # Save the hours histogram into a bucket for the last day
  666.     # counter(day,$day) is the starting time for that day bucket
  667.  
  668.     if {![info exist dayBase]} {
  669.     set dayBase $hourBase
  670.     }
  671.     foreach tag $tagsToMerge {
  672.     upvar #0 counter::T-$tag counter
  673.     upvar #0 counter::Day-$tag dayhist
  674.     upvar #0 counter::Hour-$tag hourhist
  675.     set dayhist($dayIndex) 0
  676.     set max 0
  677.     for {set i 0} {$i < 24} {incr i} {
  678.         if {[info exist hourhist($i)]} {
  679.         set dayhist($dayIndex) [expr {$dayhist($dayIndex) + $hourhist($i)}]
  680.         if {$hourhist($i) > $max} { 
  681.             set mx $hourhist($i) 
  682.         }
  683.         }
  684.     }
  685.     set perSec [expr {double($max) / ($secsPerMinute * 60)}]
  686.     if {$perSec > $counter(maxPerHour)} {
  687.         set counter(maxPerHour) $perSec
  688.     }
  689.     }
  690.     set perSec [expr {double($dayhist($dayIndex)) / ($secsPerMinute * 60 * 24)}]
  691.     if {$perSec > $counter(maxPerDay)} {
  692.     set counter(maxPerDay) $perSec
  693.     }
  694.     incr dayIndex
  695. }
  696.  
  697. # counter::histHtmlDisplay --
  698. #
  699. #    Create an html display of the histogram.
  700. #
  701. # Arguments:
  702. #    tag    The counter tag
  703. #    args    option, value pairs that affect the display:
  704. #        -title    Label to display above bar chart
  705. #        -unit    minutes, hours, or days select time-base histograms.
  706. #            Specify anything else for value-based histograms.
  707. #        -images    URL of /images directory.
  708. #        -gif    Image for normal histogram bars
  709. #        -ongif    Image for the active histogram bar
  710. #        -max     Maximum number of value-based buckets to display
  711. #        -height    Pixel height of the highest bar
  712. #        -width    Pixel width of each bar
  713. #        -skip    Buckets to skip when labeling value-based histograms
  714. #        -format Format used to display labels of buckets.
  715. #        -text    If 1, a text version of the histogram is dumped,
  716. #            otherwise a graphical one is generated.
  717. #
  718. # Results:
  719. #    HTML for the display as a complete table.
  720. #
  721. # Side Effects:
  722. #    None.
  723.  
  724. proc counter::histHtmlDisplay {tag args} {
  725.     append result "<p>\n<table border=0 cellpadding=0 cellspacing=0>\n"
  726.     append result [eval {counter::histHtmlDisplayRow $tag} $args]
  727.     append result </table>
  728.     return $result
  729. }
  730.  
  731. # counter::histHtmlDisplayRow --
  732. #
  733. #    Create an html display of the histogram.
  734. #
  735. # Arguments:
  736. #    See counter::histHtmlDisplay
  737. #
  738. # Results:
  739. #    HTML for the display.  Ths is one row of a 2-column table,
  740. #    the calling page must define the <table> tag.
  741. #
  742. # Side Effects:
  743. #    None.
  744.  
  745. proc counter::histHtmlDisplayRow {tag args} {
  746.     upvar #0 counter::T-$tag counter
  747.     variable secsPerMinute
  748.     variable minuteBase
  749.     variable hourBase
  750.     variable dayBase
  751.     variable hourIndex
  752.     variable dayIndex
  753.  
  754.     array set options [list \
  755.     -title    $tag \
  756.     -unit    "" \
  757.     -images    /images \
  758.     -gif    Blue.gif \
  759.     -ongif    Red.gif \
  760.     -max     -1 \
  761.     -height    100 \
  762.     -width    4 \
  763.     -skip    4 \
  764.     -format %.2f \
  765.     -text    0
  766.     ]
  767.     array set options $args
  768.  
  769.     # Support for self-posting pages that can clear counters.
  770.  
  771.     append result "<!-- resetCounter [ncgi::value resetCounter] -->"
  772.     if {[ncgi::value resetCounter] == $tag} {
  773.     counter::reset $tag
  774.     return "<!-- Reset $tag counter -->"
  775.     }
  776.  
  777.     switch -glob -- $options(-unit) {
  778.     min* {
  779.         upvar #0 counter::H-$tag histogram
  780.         set histname counter::H-$tag
  781.         if {![info exist minuteBase]} {
  782.         return "<!-- No time-based histograms defined -->"
  783.         }
  784.         set time $minuteBase
  785.         set secsForMax $secsPerMinute
  786.         set periodMax $counter(maxPerMinute)
  787.         set curIndex [expr {([clock seconds] - $minuteBase) / $secsPerMinute}]
  788.         set options(-max) 60
  789.         set options(-min) 0
  790.     }
  791.     hour* {
  792.         upvar #0 counter::Hour-$tag histogram
  793.         set histname counter::Hour-$tag
  794.         if {![info exist hourBase]} {
  795.         return "<!-- Hour merge has not occurred -->"
  796.         }
  797.         set time $hourBase
  798.         set secsForMax [expr {$secsPerMinute * 60}]
  799.         set periodMax $counter(maxPerHour)
  800.         set curIndex [expr {$hourIndex - 1}]
  801.         if {$curIndex < 0} {
  802.         set curIndex 23
  803.         }
  804.         set options(-max) 24
  805.         set options(-min) 0
  806.     }
  807.     day* {
  808.         upvar #0 counter::Day-$tag histogram
  809.         set histname counter::Day-$tag
  810.         if {![info exist dayBase]} {
  811.         return "<!-- Hour merge has not occurred -->"
  812.         }
  813.         set time $dayBase
  814.         set secsForMax [expr {$secsPerMinute * 60 * 24}]
  815.         set periodMax $counter(maxPerDay)
  816.         set curIndex dayIndex
  817.         set options(-max) $dayIndex
  818.         set options(-min) 0
  819.     }
  820.     default {
  821.         # Value-based histogram with arbitrary units.
  822.  
  823.         upvar #0 counter::H-$tag histogram
  824.         set histname counter::H-$tag
  825.  
  826.         set unit $options(-unit)
  827.         set curIndex ""
  828.         set time ""
  829.     }
  830.     }
  831.     if {! [info exists histogram]} {
  832.     return "<!-- $histname doesn't exist -->\n"
  833.     }
  834.  
  835.     set max 0
  836.     set maxName 0
  837.     foreach {name value} [array get histogram] {
  838.     if {$value > $max} {
  839.         set max $value
  840.         set maxName $name
  841.     }
  842.     }
  843.  
  844.     # Start 2-column HTML display.  A summary table at the left, the histogram on the right.
  845.  
  846.     append result "<tr><td valign=top>\n"
  847.  
  848.     append result "<table bgcolor=#EEEEEE>\n"
  849.     append result "<tr><td colspan=2 align=center>[html::font]<b>$options(-title)</b></font></td></tr>\n"
  850.     append result "<tr><td>[html::font]<b>Total</b></font></td>"
  851.     append result "<td>[html::font][format $options(-format) $counter(total)]</font></td></tr>\n"
  852.  
  853.     if {[info exists secsForMax]} {
  854.  
  855.     # Time-base histogram
  856.  
  857.     set string {}
  858.     set t $secsForMax
  859.     set days [expr {$t / (60 * 60 * 24)}]
  860.     if {$days == 1} {
  861.         append string "1 Day "
  862.     } elseif {$days > 1} {
  863.         append string "$days Days "
  864.     }
  865.     set t [expr {$t - $days * (60 * 60 * 24)}]
  866.     set hours [expr {$t / (60 * 60)}]
  867.     if {$hours == 1} {
  868.         append string "1 Hour "
  869.     } elseif {$hours > 1} {
  870.         append string "$hours Hours "
  871.     }
  872.     set t [expr {$t - $hours * (60 * 60)}]
  873.     set mins [expr {$t / 60}]
  874.     if {$mins == 1} {
  875.         append string "1 Minute "
  876.     } elseif {$mins > 1} {
  877.         append string "$mins Minutes "
  878.     }
  879.     set t [expr {$t - $mins * 60}]
  880.     if {$t == 1} {
  881.         append string "1 Second "
  882.     } elseif {$t > 1} {
  883.         append string "$t Seconds "
  884.     }
  885.     append result "<tr><td>[html::font]<b>Bucket Size</b></font></td>"
  886.     append result "<td>[html::font]$string</font></td></tr>\n"
  887.  
  888.     append result "<tr><td>[html::font]<b>Max Per Sec</b></font></td>"
  889.     append result "<td>[html::font][format %.2f [expr {$max/double($secsForMax)}]]</font></td></tr>\n"
  890.  
  891.     if {$periodMax > 0} {
  892.         append result "<tr><td>[html::font]<b>Best Per Sec</b></font></td>"
  893.         append result "<td>[html::font][format %.2f $periodMax]</font></td></tr>\n"
  894.     }
  895.     append result "<tr><td>[html::font]<b>Starting Time</b></font></td>"
  896.     switch -glob -- $options(-unit) {
  897.         min* {
  898.         append result "<td>[html::font][clock format $time \
  899.             -format %k:%M:%S]</font></td></tr>\n"
  900.         }
  901.         hour* {
  902.         append result "<td>[html::font][clock format $time \
  903.             -format %k:%M:%S]</font></td></tr>\n"
  904.         }
  905.         day* {
  906.         append result "<td>[html::font][clock format $time \
  907.             -format "%b %d %k:%M"]</font></td></tr>\n"
  908.         }
  909.         default {#ignore}
  910.     }
  911.  
  912.     } else {
  913.  
  914.     # Value-base histogram
  915.  
  916.     set ix [lsort -integer [array names histogram]]
  917.  
  918.     set mode [expr {$counter(bucketsize) * $maxName}]
  919.     set first [expr {$counter(bucketsize) * [lindex $ix 0]}]
  920.     set last [expr {$counter(bucketsize) * [lindex $ix end]}]
  921.  
  922.     append result "<tr><td>[html::font]<b>Average</b></font></td>"
  923.     append result "<td>[html::font][format $options(-format) [counter::get $tag -avg]]</font></td></tr>\n"
  924.  
  925.     append result "<tr><td>[html::font]<b>Mode</b></font></td>"
  926.     append result "<td>[html::font]$mode</font></td></tr>\n"
  927.  
  928.     append result "<tr><td>[html::font]<b>Minimum</b></font></td>"
  929.     append result "<td>[html::font]$first</font></td></tr>\n"
  930.  
  931.     append result "<tr><td>[html::font]<b>Maxmum</b></font></td>"
  932.     append result "<td>[html::font]$last</font></td></tr>\n"
  933.  
  934.     append result "<tr><td>[html::font]<b>Unit</b></font></td>"
  935.     append result "<td>[html::font]$unit</font></td></tr>\n"
  936.  
  937.     append result "<tr><td colspan=2 align=center>[html::font]<b>"
  938.     append result "<a href=[ncgi::urlStub]?resetCounter=$tag>Reset</a></td></tr>\n"
  939.  
  940.     if {$options(-max) < 0} {
  941.         set options(-max) [lindex $ix end]
  942.     }
  943.     if {![info exist options(-min)]} {
  944.         set options(-min) [lindex $ix 0]
  945.     }
  946.     }
  947.  
  948.     # End table nested inside left-hand column
  949.  
  950.     append result </table>\n
  951.     append result </td>\n
  952.     append result "<td valign=bottom>\n"
  953.  
  954.  
  955.     # Display the histogram
  956.  
  957.     if {$options(-text)} {
  958.     } else {
  959.     append result [eval \
  960.         {counter::histHtmlDisplayBarChart $tag histogram $max $curIndex $time} \
  961.         [array get options]]
  962.     }
  963.  
  964.     # Close the right hand column, but leave our caller's table open.
  965.  
  966.     append result </td></tr>\n
  967.  
  968.     return $result
  969. }
  970.  
  971. # counter::histHtmlDisplayBarChart --
  972. #
  973. #    Create an html display of the histogram.
  974. #
  975. # Arguments:
  976. #    tag        The counter tag.
  977. #    histVar        The name of the histogram array
  978. #    max        The maximum counter value in a histogram bucket.
  979. #    curIndex    The "current" histogram index, for time-base histograms.
  980. #    time        The base, or starting time, for the time-based histograms.
  981. #    args        The array get of the options passed into histHtmlDisplay
  982. #
  983. # Results:
  984. #    HTML for the bar chart.
  985. #
  986. # Side Effects:
  987. #    See description.
  988.  
  989. proc counter::histHtmlDisplayBarChart {tag histVar max curIndex time args} {
  990.     upvar #0 counter::T-$tag counter
  991.     upvar 1 $histVar histogram
  992.     variable secsPerMinute
  993.     array set options $args
  994.  
  995.     append result "<table cellpadding=0 cellspacing=0 bgcolor=#eeeeee><tr>\n"
  996.  
  997.     set ix [lsort -integer [array names histogram]]
  998.  
  999.     for {set t $options(-min)} {$t < $options(-max)} {incr t} {
  1000.     if {![info exist histogram($t)]} {
  1001.         set value 0
  1002.     } else {
  1003.         set value $histogram($t)
  1004.     }
  1005.     if {$max == 0 || $value == 0} {
  1006.         set height 1
  1007.     } else {
  1008.         set percent [expr {round($value * 100.0 / $max)}]
  1009.         set height [expr {$percent * $options(-height) / 100}]
  1010.     }
  1011.     if {$t == $curIndex} {
  1012.         set img src=$options(-images)/$options(-ongif)
  1013.     } else {
  1014.         set img src=$options(-images)/$options(-gif)
  1015.     }
  1016.     append result "<td valign=bottom><img $img height=$height\
  1017.         width=$options(-width) alt=$value></td>\n"
  1018.     }
  1019.     append result "</tr>"
  1020.  
  1021.     # Count buckets outside the range requested
  1022.  
  1023.     set overflow 0
  1024.     set underflow 0
  1025.     foreach t [lsort -integer [array names histogram]] {
  1026.     if {($options(-max) > 0) && ($t > $options(-max))} {
  1027.         incr overflow
  1028.     }
  1029.     if {($options(-min) >= 0) && ($t < $options(-min))} {
  1030.         incr underflow
  1031.     }
  1032.     }
  1033.  
  1034.     # Append a row of labels at the bottom.
  1035.  
  1036.     set colors {black #CCCCCC}
  1037.     set bgcolors {#CCCCCC black}
  1038.     set colori 0
  1039.     if {$counter(type) != "-timehist"} {
  1040.  
  1041.     # Label each bucket with its value
  1042.     # This is probably wrong for hist2x and hist10x
  1043.  
  1044.     append result "<tr>"
  1045.     set skip $options(-skip)
  1046.     if {![info exists counter(mult)]} {
  1047.         set counter(mult) 1
  1048.     }
  1049.  
  1050.     # These are tick marks
  1051.  
  1052.     set img src=$options(-images)/$options(-gif)
  1053.     append result "<tr>"
  1054.     for {set i $options(-min)} {$i < $options(-max)} {incr i} {
  1055.         if {(($i % $skip) == 0)} {
  1056.         append result "<td valign=bottom><img $img height=3 \
  1057.             width=1></td>\n"
  1058.         } else {
  1059.         append result "<td valign=bottom></td>"
  1060.         }
  1061.     }
  1062.     append result </tr>
  1063.  
  1064.     # These are the labels
  1065.  
  1066.     append result "<tr>"
  1067.     for {set i $options(-min)} {$i < $options(-max)} {incr i} {
  1068.         if {$counter(type) == "-histlog"} {
  1069.         if {[catch {expr {int(log($i) * $counter(bucketsize))}} x]} {
  1070.             # Out-of-bounds
  1071.             break
  1072.         }
  1073.         } else {
  1074.         set x [expr {$i * $counter(bucketsize) * $counter(mult)}]
  1075.         }
  1076.         set label [format $options(-format) $x]
  1077.         if {(($i % $skip) == 0)} {
  1078.         set color [lindex $colors $colori]
  1079.         set bg [lindex $bgcolors $colori]
  1080.         set colori [expr {($colori+1) % 2}]
  1081.         append result "<td colspan=$skip><font size=1 color=$color>$label</font></td>"
  1082.         }
  1083.     }
  1084.     append result </tr>
  1085.     } else {
  1086.     switch -glob -- $options(-unit) {
  1087.         min*    {
  1088.         if {$secsPerMinute != 60} {
  1089.             set format %k:%M:%S
  1090.             set skip 12
  1091.         } else {
  1092.             set format %k:%M
  1093.             set skip 4
  1094.         }
  1095.         set deltaT $secsPerMinute
  1096.         set wrapDeltaT [expr {$secsPerMinute * -59}]
  1097.         }
  1098.         hour*    {
  1099.         if {$secsPerMinute != 60} {
  1100.             set format %k:%M
  1101.             set skip 4
  1102.         } else {
  1103.             set format %k
  1104.             set skip 2
  1105.         }
  1106.         set deltaT [expr {$secsPerMinute * 60}]
  1107.         set wrapDeltaT [expr {$secsPerMinute * 60 * -23}]
  1108.         }
  1109.         day* {
  1110.         if {$secsPerMinute != 60} {
  1111.             set format "%m/%d %k:%M"
  1112.             set skip 10
  1113.         } else {
  1114.             set format %k
  1115.             set skip $options(-skip)
  1116.         }
  1117.         set deltaT [expr {$secsPerMinute * 60 * 24}]
  1118.         set wrapDeltaT 0
  1119.         }
  1120.         default {#ignore}
  1121.     }
  1122.     # These are tick marks
  1123.  
  1124.     set img src=$options(-images)/$options(-gif)
  1125.     append result "<tr>"
  1126.     foreach t [lsort -integer [array names histogram]] {
  1127.         if {(($t % $skip) == 0)} {
  1128.         append result "<td valign=bottom><img $img height=3 \
  1129.             width=1></td>\n"
  1130.         } else {
  1131.         append result "<td valign=bottom></td>"
  1132.         }
  1133.     }
  1134.     append result </tr>
  1135.  
  1136.     set lastLabel ""
  1137.     append result "<tr>"
  1138.     foreach t [lsort -integer [array names histogram]] {
  1139.  
  1140.         # Label each bucket with its time
  1141.  
  1142.         set label [clock format $time -format $format]
  1143.         if {(($t % $skip) == 0) && ($label != $lastLabel)} {
  1144.         set color [lindex $colors $colori]
  1145.         set bg [lindex $bgcolors $colori]
  1146.         set colori [expr {($colori+1) % 2}]
  1147.         append result "<td colspan=$skip><font size=1 color=$color>$label</font></td>"
  1148.         set lastLabel $label
  1149.         }
  1150.         if {$t == $curIndex} {
  1151.         incr time $wrapDeltaT
  1152.         } else {
  1153.         incr time $deltaT
  1154.         }
  1155.     }
  1156.     append result </tr>\n
  1157.     }
  1158.     append result "</table>"
  1159.     if {$underflow > 0} {
  1160.     append result "<br>Skipped $underflow samples <\
  1161.         [expr {$options(-min) * $counter(bucketsize)}]\n"
  1162.     }
  1163.     if {$overflow > 0} {
  1164.     append result "<br>Skipped $overflow samples >\
  1165.         [expr {$options(-max) * $counter(bucketsize)}]\n"
  1166.     }
  1167.     return $result
  1168. }
  1169.  
  1170. # counter::start --
  1171. #
  1172. #    Start an interval timer.  This should be pre-declared with
  1173. #    type either -hist, -hist2x, or -hist20x
  1174. #
  1175. # Arguments:
  1176. #    tag        The counter identifier.
  1177. #    instance    There may be multiple intervals outstanding
  1178. #            at any time.  This serves to distinquish them.
  1179. #
  1180. # Results:
  1181. #    None
  1182. #
  1183. # Side Effects:
  1184. #    Records the starting time for the instance of this interval.
  1185.  
  1186. proc counter::start {tag instance} {
  1187.     upvar #0 counter::Time-$tag time
  1188.     set time($instance) [list [clock clicks] \
  1189.         [clock seconds]]
  1190. }
  1191.  
  1192. # counter::stop --
  1193. #
  1194. #    Record an interval timer.
  1195. #
  1196. # Arguments:
  1197. #    tag        The counter identifier.
  1198. #    instance    There may be multiple intervals outstanding
  1199. #            at any time.  This serves to distinquish them.
  1200. #    func        An optional function used to massage the time
  1201. #            stamp before putting into the histogram.
  1202. #
  1203. # Results:
  1204. #    None
  1205. #
  1206. # Side Effects:
  1207. #    Computes the current interval and adds it to the histogram.
  1208.  
  1209. proc counter::stop {tag instance {func ::counter::Identity}} {
  1210.     upvar #0 counter::Time-$tag time
  1211.  
  1212.     if {![info exist time($instance)]} {
  1213.     # Extra call. Ignore so we can debug error cases.
  1214.     return
  1215.     }
  1216.     set now [list [clock clicks] \
  1217.         [clock seconds]]
  1218.     set delMicros [expr {[lindex $now 0] - [lindex $time($instance) 0]}]
  1219.     set delSecond [expr {[lindex $now 1] - [lindex $time($instance) 1]}]
  1220.     unset time($instance)
  1221.  
  1222.     if {$delMicros < 0} {
  1223.     set delMicros [expr {1000000 + $delMicros}]
  1224.     incr delSecond -1
  1225.     if {$delSecond < 0} {
  1226.         set delSecond 0
  1227.     }
  1228.     }
  1229.     counter::count $tag [$func $delSecond.[format %06d $delMicros]]
  1230. }
  1231.  
  1232. # counter::Identity --
  1233. #
  1234. #    Return its argument.  This is used as the default function
  1235. #    to apply to an interval timer.
  1236. #
  1237. # Arguments:
  1238. #    x        Some value.
  1239. #
  1240. # Results:
  1241. #    $x
  1242. #
  1243. # Side Effects:
  1244. #    None
  1245.  
  1246.  
  1247. proc counter::Identity {x} {
  1248.     return $x
  1249. }
  1250.  
  1251. package provide counter 2.0
  1252.  
  1253.