profiler.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package require Tcl 8.3 ;
00013 package provide profiler 0.3
00014
00015 namespace ::profiler {
00016 }
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028 ret ::profiler::tZero ( optional tag ="" ) {
00029 set ms [ clock clicks -milliseconds ]
00030 set us [ clock clicks ]
00031 set tag [string map {: ""} $tag]
00032 # FRINK: nocheck
00033 set ::profiler::T$tag [ list $us $ms ]
00034 return
00035 }
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049 ret ::profiler::tMark ( optional tag ="" ) {
00050 set ut [ clock clicks ]
00051 set mt [ clock clicks -milliseconds ]
00052 set tag [string map {: ""} $tag]
00053
00054 # Per tag a variable was created within the profiler
00055 # namespace. But we should check if the tag does ecxist.
00056
00057 if {![info exists ::profiler::T$tag]} {
00058 error "Unknown tag \"$tag\""
00059 }
00060 # FRINK: nocheck
00061 set ust [ lindex [ set ::profiler::T$tag ] 0 ]
00062 # FRINK: nocheck
00063 set mst [ lindex [ set ::profiler::T$tag ] 1 ]
00064 set udt [ expr { ($ut-$ust) } ]
00065 set mdt [ expr { ($mt-$mst) } ]000
00066 set dt $udt
00067 ;## handle wrapping of the microsecond clock
00068 if { $dt < 0 || $dt > 1000000 } { set dt $mdt }
00069 set dt
00070 }
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084 ret ::profiler::stats (type args) {
00085 set sum 0
00086 set mean 0
00087 set sigma_sq 0
00088 set sigma 0
00089 set cov 0
00090 set N [ llength $args ]
00091 if { $N > 1 } {
00092 foreach val $args {
00093 incr sum $val
00094 }
00095 if {$sum > 0} {
00096 set mean [ expr { $sum/$N } ]
00097 foreach val $args {
00098 set sigma_sq [ expr { $sigma_sq+pow(($val-$mean),2) } ]
00099 }
00100 set sigma_sq [ expr { $sigma_sq/($N-1) } ]
00101 set sigma [ expr { round(sqrt($sigma_sq)) } ]
00102 if { $mean != 0 } {
00103 set cov [ expr { (($sigma*1.0)/$mean)*100 } ]
00104 set cov [ expr { round($cov*10)/10.0 } ]
00105 }
00106 }
00107 }
00108 return [ list $mean $sigma $cov ]
00109 }
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126 ret ::profiler::Handler (type name , type args) {
00127 variable enabled
00128
00129 if { [info level] == 1 } {
00130 set caller GLOBAL
00131 } else {
00132 # Get the name of the calling procedure
00133 set caller [lindex [info level -1] 0]
00134 # Remove the ORIG suffix
00135 set caller [string range $caller 0 end-4]
00136
00137 # Make sure that caller names always include the "::" prefix;
00138 # otherwise we get confused by the string inequality between
00139 # "::foo" and "foo" -- even though those refer to the same proc.
00140
00141 if { ![string equal -length 2 $caller "::"] } {
00142 set caller "::$caller"
00143 }
00144 }
00145
00146 ::profiler::enterHandler $name $caller
00147 set CODE [uplevel 1 [list ${name}ORIG] $args]
00148 ::profiler::leaveHandler $name $caller
00149 return $CODE
00150 }
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170 ret ::profiler::TraceHandler (type name , type cmd , type args) {
00171
00172 if { [info level] == 1 } {
00173 set caller GLOBAL
00174 } else {
00175 # Get the name of the calling procedure
00176 set caller [lindex [info level -1] 0]
00177
00178 # Make sure that caller names always include the "::" prefix;
00179 # otherwise we get confused by the string inequality between
00180 # "::foo" and "foo" -- even though those refer to the same proc.
00181
00182 if { ![string equal -length 2 $caller "::"] } {
00183 set caller "::$caller"
00184 }
00185 }
00186
00187 set type [lindex $args end]
00188 ::profiler::${type}Handler $name $caller
00189 }
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204 ret ::profiler::enterHandler (type name , type caller) {
00205 variable enabled
00206
00207 if { !$enabled($name) } {
00208 return
00209 }
00210
00211 if { [catch {incr ::profiler::callers($name,$caller)}] } {
00212 set ::profiler::callers($name,$caller) 1
00213 }
00214 ::profiler::tZero $name.$caller
00215 }
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230 ret ::profiler::leaveHandler (type name , type caller) {
00231 variable enabled
00232
00233 if { !$enabled($name) } {
00234 return
00235 }
00236
00237 set t [::profiler::tMark $name.$caller]
00238 lappend ::profiler::statTime($name) $t
00239
00240 if { [incr ::profiler::callCount($name)] == 1 } {
00241 set ::profiler::compileTime($name) $t
00242 }
00243 incr ::profiler::totalRuntime($name) $t
00244 if { [catch {incr ::profiler::descendantTime($caller) $t}] } {
00245 set ::profiler::descendantTime($caller) $t
00246 }
00247 if { [catch {incr ::profiler::descendants($caller,$name)}] } {
00248 set ::profiler::descendants($caller,$name) 1
00249 }
00250 }
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265 ret ::profiler::profProc (type name , type arglist , type body) {
00266 variable callCount
00267 variable compileTime
00268 variable totalRuntime
00269 variable descendantTime
00270 variable statTime
00271 variable enabled
00272 variable paused
00273
00274 # Get the fully qualified name of the proc
00275 set ns [uplevel [list namespace current]]
00276 # If the proc call did not happen at the global context and it did not
00277 # have an absolute namespace qualifier, we have to prepend the current
00278 # namespace to the command name
00279 if { ![string equal $ns "::"] } {
00280 if { ![string match "::*" $name] } {
00281 set name "${ns}::${name}"
00282 }
00283 }
00284 if { ![string match "::*" $name] } {
00285 set name "::$name"
00286 }
00287
00288 # Set up accounting for this procedure
00289 set callCount($name) 0
00290 set compileTime($name) 0
00291 set totalRuntime($name) 0
00292 set descendantTime($name) 0
00293 set statTime($name) {}
00294 set enabled($name) [expr {!$paused}]
00295
00296 if {[package vsatisfies [package provide Tcl] 8.4]} {
00297 uplevel 1 [list ::_oldProc $name $arglist $body]
00298 trace add execution $name {enter leave} \
00299 [list ::profiler::TraceHandler $name]
00300 } else {
00301 uplevel 1 [list ::_oldProc ${name}ORIG $arglist $body]
00302 uplevel 1 [list interp alias {} $name {} ::profiler::Handler $name]
00303 }
00304 return
00305 }
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318 ret ::profiler::init () {
00319 # paused is set to 1 when the profiler is suspended.
00320 variable paused 0
00321
00322 rename ::proc ::_oldProc
00323 interp alias {} proc {} ::profiler::profProc
00324
00325 return
00326 }
00327
00328
00329
00330
00331
00332
00333 ret ::profiler::printname (type name) {
00334 variable callCount
00335 variable compileTime
00336 variable totalRuntime
00337 variable descendantTime
00338 variable descendants
00339 variable statTime
00340 variable callers
00341
00342 set result ""
00343
00344 set avgRuntime 0
00345 set sigmaRuntime 0
00346 set covRuntime 0
00347 set avgDesTime 0
00348 if { $callCount($name) > 0 } {
00349 foreach {m s c} [eval ::profiler::stats $statTime($name)] { break }
00350 set avgRuntime $m
00351 set sigmaRuntime $s
00352 set covRuntime $c
00353 set avgDesTime \
00354 [expr {$descendantTime($name)/$callCount($name)}]
00355 }
00356
00357 append result "Profiling information for $name\n"
00358 append result "[string repeat = 60]\n"
00359 append result " Total calls: $callCount($name)\n"
00360 if { !$callCount($name) } {
00361 append result "\n"
00362 return $result
00363 }
00364 append result " Caller distribution:\n"
00365 set i [expr {[string length $name] + 1}]
00366 foreach index [lsort [array names callers $name,*]] {
00367 append result " [string range $index $i end]: $callers($index)\n"
00368 }
00369 append result " Compile time: $compileTime($name)\n"
00370 append result " Total runtime: $totalRuntime($name)\n"
00371 append result " Average runtime: $avgRuntime\n"
00372 append result " Runtime StDev: $sigmaRuntime\n"
00373 append result " Runtime cov(%): $covRuntime\n"
00374 append result " Total descendant time: $descendantTime($name)\n"
00375 append result "Average descendant time: $avgDesTime\n"
00376 append result "Descendants:\n"
00377 if { !$descendantTime($name) } {
00378 append result " none\n"
00379 }
00380 foreach index [lsort [array names descendants $name,*]] {
00381 append result " [string range $index $i end]: \
00382 $descendants($index)\n"
00383 }
00384 append result "\n"
00385 return $result
00386 }
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399 ret ::profiler::print (optional pattern =*) {
00400 variable callCount
00401
00402 set result ""
00403 foreach name [lsort [array names callCount $pattern]] {
00404 append result [printname $name]
00405 }
00406 return $result
00407 }
00408
00409
00410
00411
00412
00413
00414
00415 ret ::profiler::printsorted (type key , optional pattern =*) {
00416 variable callCount
00417 variable compileTime
00418 variable totalRuntime
00419 variable descendantTime
00420 variable descendants
00421 variable statTime
00422 variable callers
00423
00424 set data [sortFunctions $key]
00425 foreach {k v} $data {
00426 append result [printname [lindex $k 0]]
00427 }
00428 return $result
00429 }
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442 ret ::profiler::dump (optional pattern =*) {
00443 variable callCount
00444 variable compileTime
00445 variable totalRuntime
00446 variable callers
00447 variable descendantTime
00448 variable descendants
00449 variable statTime
00450
00451 set result ""
00452 foreach name [lsort [array names callCount $pattern]] {
00453 set i [expr {[string length $name] + 1}]
00454 catch {unset thisCallers}
00455 foreach index [lsort [array names callers $name,*]] {
00456 set thisCallers([string range $index $i end]) $callers($index)
00457 }
00458 set avgRuntime 0
00459 set sigmaRuntime 0
00460 set covRuntime 0
00461 set avgDesTime 0
00462 if { $callCount($name) > 0 } {
00463 foreach {m s c} [eval ::profiler::stats $statTime($name)] { break }
00464 set avgRuntime $m
00465 set sigmaRuntime $s
00466 set covRuntime $c
00467 set avgDesTime \
00468 [expr {$descendantTime($name)/$callCount($name)}]
00469 }
00470 set descendantList [list ]
00471 foreach index [lsort [array names descendants $name,*]] {
00472 lappend descendantList [string range $index $i end]
00473 }
00474 lappend result $name [list callCount $callCount($name) \
00475 callerDist [array get thisCallers] \
00476 compileTime $compileTime($name) \
00477 totalRuntime $totalRuntime($name) \
00478 averageRuntime $avgRuntime \
00479 stddevRuntime $sigmaRuntime \
00480 covpercentRuntime $covRuntime \
00481 descendantTime $descendantTime($name) \
00482 averageDescendantTime $avgDesTime \
00483 descendants $descendantList]
00484 }
00485 return $result
00486 }
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499 ret ::profiler::sortFunctions (optional field ="") {
00500 switch -glob -- $field {
00501 "calls" {
00502 upvar ::profiler::callCount data
00503 }
00504 "compileTime" {
00505 upvar ::profiler::compileTime data
00506 }
00507 "totalRuntime" {
00508 upvar ::profiler::totalRuntime data
00509 }
00510 "avgRuntime" -
00511 "averageRuntime" {
00512 variable callCount
00513 variable totalRuntime
00514 foreach fxn [array names callCount] {
00515 if { $callCount($fxn) > 1 } {
00516 set data($fxn) \
00517 [expr {$totalRuntime($fxn)/($callCount($fxn) - 1)}]
00518 }
00519 }
00520 }
00521 "exclusiveRuntime" {
00522 variable totalRuntime
00523 variable descendantTime
00524 foreach fxn [array names totalRuntime] {
00525 set data($fxn) \
00526 [expr {$totalRuntime($fxn) - $descendantTime($fxn)}]
00527 }
00528 }
00529 "avgExclusiveRuntime" {
00530 variable totalRuntime
00531 variable callCount
00532 variable descendantTime
00533 foreach fxn [array names totalRuntime] {
00534 if { $callCount($fxn) } {
00535 set data($fxn) \
00536 [expr {($totalRuntime($fxn) - \
00537 $descendantTime($fxn)) / $callCount($fxn)}]
00538 }
00539 }
00540 }
00541 "nonCompileTime" {
00542 variable compileTime
00543 variable totalRuntime
00544 foreach fxn [array names totalRuntime] {
00545 set data($fxn) [expr {$totalRuntime($fxn)-$compileTime($fxn)}]
00546 }
00547 }
00548 default {
00549 error "unknown statistic \"$field\": should be calls,\
00550 compileTime, exclusiveRuntime, nonCompileTime,\
00551 totalRuntime, avgExclusiveRuntime, or avgRuntime"
00552 }
00553 }
00554
00555 set result [list ]
00556 foreach fxn [array names data] {
00557 lappend result [list $fxn $data($fxn)]
00558 }
00559 return [lsort -integer -index 1 $result]
00560 }
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572 ret ::profiler::reset (optional pattern =*) {
00573 variable callCount
00574 variable compileTime
00575 variable totalRuntime
00576 variable callers
00577 variable statTime
00578
00579 foreach name [array names callCount $pattern] {
00580 set callCount($name) 0
00581 set compileTime($name) 0
00582 set totalRuntime($name) 0
00583 set statTime($name) {}
00584 foreach caller [array names callers $name,*] {
00585 unset callers($caller)
00586 }
00587 }
00588 return
00589 }
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602 ret ::profiler::suspend (optional pattern =*) {
00603 variable callCount
00604 variable enabled
00605 variable paused
00606
00607 set paused 1
00608 foreach name [array names callCount $pattern] {
00609 set enabled($name) 0
00610 }
00611
00612 return
00613 }
00614
00615
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626 ret ::profiler::resume (optional pattern =*) {
00627 variable callCount
00628 variable enabled
00629 variable paused
00630
00631 set paused 0
00632 foreach name [array names callCount $pattern] {
00633 set enabled($name) 1
00634 }
00635
00636 return
00637 }
00638
00639