00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package require Tcl 8.2
00013
00014 namespace ::counter {
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 variable startTime
00025 variable minuteBase
00026 variable hourBase
00027 variable hourEnd
00028 variable dayBase
00029 variable hourIndex
00030 variable dayIndex
00031
00032
00033
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
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
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
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
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
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
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
00418
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430 ret ::counter::exists (type tag) {
00431 upvar #0 counter::T-$tag counter
00432 return [info exists counter]
00433 }
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
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
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
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
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
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
00649
00650
00651
00652
00653
00654
00655
00656
00657
00658
00659
00660
00661
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
00703
00704
00705
00706
00707
00708
00709
00710
00711
00712
00713
00714
00715
00716
00717
00718
00719
00720
00721
00722
00723
00724
00725
00726
00727
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
00737
00738
00739
00740
00741
00742
00743
00744
00745
00746
00747
00748
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
00977
00978
00979
00980
00981
00982
00983
00984
00985
00986
00987
00988
00989
00990
00991
00992
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
01176
01177
01178
01179
01180
01181
01182
01183
01184
01185
01186
01187
01188
01189
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
01201
01202
01203
01204
01205
01206
01207
01208
01209
01210
01211
01212
01213
01214
01215
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
01247
01248
01249
01250
01251
01252
01253
01254
01255
01256
01257
01258
01259
01260
01261 ret ::counter::Identity (type x) {
01262 return $x
01263 }
01264
01265 package provide counter 2.0.4
01266