profiler.tcl

Go to the documentation of this file.
00001 /*  profiler.tcl --*/
00002 /* */
00003 /*  Tcl code profiler.*/
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: profiler.tcl,v 1.29 2006/09/19 23:36:17 andreas_kupries Exp $*/
00011 
00012 package require Tcl 8.3     ;/*  uses [clock clicks -milliseconds]*/
00013 package provide profiler 0.3
00014 
00015 namespace ::profiler {
00016 }
00017 
00018 /*  ::profiler::tZero --*/
00019 /* */
00020 /*  Start a named timer instance*/
00021 /* */
00022 /*  Arguments:*/
00023 /*  tag name for the timer instance; if none is given, defaults to ""*/
00024 /* */
00025 /*  Results:*/
00026 /*  None.*/
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 /*  ::profiler::tMark --*/
00038 /* */
00039 /*  Return the delta time since the start of a named timer.*/
00040 /* */
00041 /*  Arguments:*/
00042 /*  tag Tag for which to return a delta; if none is given, defaults to*/
00043 /*      "" */
00044 /* */
00045 /*  Results:*/
00046 /*  dt  Time difference between start of the timer and the current*/
00047 /*      time, in microseconds.*/
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 /*  ::profiler::stats --*/
00073 /* */
00074 /*  Compute statistical information for a set of values, including*/
00075 /*  the mean, the standard deviation, and the covariance.*/
00076 /* */
00077 /*  Arguments:*/
00078 /*  args    Values for which to compute information.*/
00079 /* */
00080 /*  Results:*/
00081 /*  A list with three elements:  the mean, the standard deviation, and the*/
00082 /*  covariance.*/
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 /*  ::profiler::Handler --*/
00112 /* */
00113 /*  Profile a function (tcl8.3).  This function works together with */
00114 /*        profProc, which replaces the proc command.  When a new procedure*/
00115 /*        is defined, it creates and alias to this function; when that*/
00116 /*        procedure is called, it calls this handler first, which gathers*/
00117 /*        profiling information from the call.*/
00118 /* */
00119 /*  Arguments:*/
00120 /*  name    name of the function to profile.*/
00121 /*  args    arguments to pass to the original function.*/
00122 /* */
00123 /*  Results:*/
00124 /*  res result from the original function.*/
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 /*  ::profiler::TraceHandler --*/
00153 /* */
00154 /*  Profile a function (tcl8.4+).  This function works together with*/
00155 /*        profProc, which replaces the proc command.  When a new procedure*/
00156 /*        is defined, it creates an execution trace on the function; when*/
00157 /*        that function is called, 'enter' and 'leave' traces invoke this*/
00158 /*        handler first, which gathers profiling information from the call.*/
00159 /* */
00160 /*  Arguments:*/
00161 /*  name    name of the function to profile.*/
00162 /*  cmd command name and its expanded arguments.*/
00163 /*  args    for 'enter' operation, value of args is "enter"*/
00164 /*          for 'leave' operation, args is list of*/
00165 /*                3 elements: <code> <result> "leave"*/
00166 /* */
00167 /*  Results:*/
00168 /*  None*/
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 /*  ::profiler::enterHandler --*/
00192 /* */
00193 /*  Profile a function.  This function works together with Handler and*/
00194 /*        TraceHandler to collect profiling information just before it invokes*/
00195 /*        the function.*/
00196 /* */
00197 /*  Arguments:*/
00198 /*  name    name of the function to profile.*/
00199 /*  caller  name of the function that calls the profiled function.*/
00200 /* */
00201 /*  Results:*/
00202 /*  None*/
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 /*  ::profiler::leaveHandler --*/
00218 /* */
00219 /*  Profile a function.  This function works together with Handler and*/
00220 /*        TraceHandler to collect profiling information just after it invokes*/
00221 /*        the function.*/
00222 /* */
00223 /*  Arguments:*/
00224 /*  name    name of the function to profile.*/
00225 /*  caller  name of the function that calls the profiled function.*/
00226 /* */
00227 /*  Results:*/
00228 /*  None*/
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 /*  ::profiler::profProc --*/
00253 /* */
00254 /*  Replacement for the proc command that adds rudimentary profiling*/
00255 /*  capabilities to Tcl.*/
00256 /* */
00257 /*  Arguments:*/
00258 /*  name        name of the procedure*/
00259 /*  arglist     list of arguments*/
00260 /*  body        body of the procedure*/
00261 /* */
00262 /*  Results:*/
00263 /*  None.*/
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 /*  ::profiler::init --*/
00308 /* */
00309 /*  Initialize the profiler.*/
00310 /* */
00311 /*  Arguments:*/
00312 /*  None.*/
00313 /* */
00314 /*  Results:*/
00315 /*  None.  Renames proc to _oldProc and sets an alias for proc to */
00316 /*      profiler::profProc*/
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 /*  ::profiler::printname --*/
00329 /* */
00330 /*  Returns a string with some human readable information about*/
00331 /*  the command name that was passed to this procedure.*/
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 /*  ::profiler::print --*/
00390 /* */
00391 /*  Print information about a proc.*/
00392 /* */
00393 /*  Arguments:*/
00394 /*  pattern pattern of the proc's to get info for; default is *.*/
00395 /* */
00396 /*  Results:*/
00397 /*  A human readable printout of info.*/
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 /*  ::profiler::printsorted --*/
00410 /* */
00411 /*  This proc takes a key and a pattern as arguments, and produces*/
00412 /*  human readable results for the procs that match the pattern,*/
00413 /*  sorted by the key.*/
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 /*  ::profiler::dump --*/
00433 /* */
00434 /*  Dump out the information for a proc in a big blob.*/
00435 /* */
00436 /*  Arguments:*/
00437 /*  pattern pattern of the proc's to lookup; default is *.*/
00438 /* */
00439 /*  Results:*/
00440 /*  data    data about the proc's.*/
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 /*  ::profiler::sortFunctions --*/
00489 /* */
00490 /*  Return a list of functions sorted by a particular field and the*/
00491 /*  value of that field.*/
00492 /* */
00493 /*  Arguments:*/
00494 /*  field   field to sort by*/
00495 /* */
00496 /*  Results:*/
00497 /*  slist   sorted list of lists, sorted by the field in question.*/
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 /*  ::profiler::reset --*/
00563 /* */
00564 /*  Reset collected data for functions matching a given pattern.*/
00565 /* */
00566 /*  Arguments:*/
00567 /*  pattern     pattern of functions to reset; default is *.*/
00568 /* */
00569 /*  Results:*/
00570 /*  None.*/
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 /*  ::profiler::suspend --*/
00592 /* */
00593 /*  Suspend the profiler.*/
00594 /* */
00595 /*  Arguments:*/
00596 /*  pattern     pattern of functions to suspend; default is *.*/
00597 /* */
00598 /*  Results:*/
00599 /*  None.  Resets the `enabled($name)' variable to 0*/
00600 /*         to suspend profiling*/
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 /*  ::profiler::resume --*/
00616 /* */
00617 /*  Resume the profiler, after it has been suspended.*/
00618 /* */
00619 /*  Arguments:*/
00620 /*  pattern     pattern of functions to suspend; default is *.*/
00621 /* */
00622 /*  Results:*/
00623 /*  None.  Sets the `enabled($name)' variable to 1*/
00624 /*         so as to enable the profiler.*/
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 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1