home *** CD-ROM | disk | FTP | other *** search
/ H4CK3R 5 / hacker05 / 05_HACK_05.ISO / programacao / freewrap / TCLLIBsampleApp.exe / sample / tcllib / tcllib1.0 / profiler / profiler.tcl < prev   
Encoding:
Text File  |  2001-08-17  |  11.4 KB  |  446 lines

  1. # profiler.tcl --
  2. #
  3. #    Tcl code profiler.
  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. # RCS: @(#) $Id: profiler.tcl,v 1.16 2001/08/02 16:38:07 andreas_kupries Exp $
  10.  
  11. package require Tcl 8.3        ;# uses [clock clicks -milliseconds]
  12. package provide profiler 0.1
  13.  
  14. namespace eval ::profiler {
  15.     variable enabled 1
  16. }
  17.  
  18. # ::profiler::tZero --
  19. #
  20. #    Start a named timer instance
  21. #
  22. # Arguments:
  23. #    tag    name for the timer instance; if none is given, defaults to ""
  24. #
  25. # Results:
  26. #    None.
  27.  
  28. proc ::profiler::tZero { { tag "" } } {
  29.     set ms [ clock clicks -milliseconds ]
  30.     set us [ clock clicks ]
  31.     regsub -all -- {:} $tag {} tag
  32.     # FRINK: nocheck
  33.     set ::profiler::T$tag [ list $us $ms ] 
  34.     return
  35. }
  36.  
  37. # ::profiler::tMark --
  38. #
  39. #    Return the delta time since the start of a named timer.
  40. #
  41. # Arguments:
  42. #    tag    Tag for which to return a delta; if none is given, defaults to
  43. #        "" 
  44. #
  45. # Results:
  46. #    dt    Time difference between start of the timer and the current
  47. #        time, in microseconds.
  48.  
  49. proc ::profiler::tMark { { tag "" } } {
  50.      set ut [ clock clicks ]
  51.      set mt [ clock clicks -milliseconds ]
  52.      regsub -all -- {:} $tag {} tag
  53.  
  54.     # Per tag a variable was created within the profiler
  55.     # namespace. But we should check if the tag does ecxist.
  56.  
  57.     if {![info exists ::profiler::T$tag]} {
  58.     error "Unknown tag \"$tag\""
  59.     }
  60.     # FRINK: nocheck
  61.      set ust [ lindex [ set ::profiler::T$tag ] 0 ] 
  62.     # FRINK: nocheck
  63.      set mst [ lindex [ set ::profiler::T$tag ] 1 ]
  64.      set udt [ expr { ($ut-$ust) } ]
  65.      set mdt [ expr { ($mt-$mst) } ]000
  66.      set dt $udt
  67.      ;## handle wrapping of the microsecond clock
  68.      if { $dt < 0 || $dt > 1000000 } { set dt $mdt }
  69.      set dt
  70. }
  71.  
  72. # ::profiler::stats --
  73. #
  74. #    Compute statistical information for a set of values, including
  75. #    the mean, the standard deviation, and the covariance.
  76. #
  77. # Arguments:
  78. #    args    Values for which to compute information.
  79. #
  80. # Results:
  81. #    A list with three elements:  the mean, the standard deviation, and the
  82. #    covariance.
  83.  
  84. proc ::profiler::stats {args} {
  85.      set sum      0
  86.      set mean     0
  87.      set sigma_sq 0
  88.      set sigma    0
  89.      set cov      0
  90.      set N [ llength $args ]
  91.      if { $N > 1 } { 
  92.         foreach val $args {
  93.            set sum [ expr { $sum+$val } ]
  94.         }
  95.         set mean [ expr { $sum/$N } ]
  96.         foreach val $args {
  97.            set sigma_sq [ expr { $sigma_sq+pow(($val-$mean),2) } ]
  98.         }
  99.         set sigma_sq [ expr { $sigma_sq/($N-1) } ] 
  100.         set sigma [ expr { round(sqrt($sigma_sq)) } ]
  101.         set cov [ expr { (($sigma*1.0)/$mean)*100 } ]
  102.         set cov [ expr { round($cov*10)/10.0 } ]
  103.      }   
  104.      return [ list $mean $sigma $cov ]
  105. }
  106.  
  107. # ::profiler::Handler --
  108. #
  109. #    Profile a function.  This function works together with profProc, which
  110. #    replaces the proc command.  When a new procedure is defined, it creates
  111. #    and alias to this function; when that procedure is called, it calls
  112. #    this handler first, which gathers profiling information from the call.
  113. #
  114. # Arguments:
  115. #    name    name of the function to profile.
  116. #    args    arguments to pass to the original function.
  117. #
  118. # Results:
  119. #    res    result from the original function.
  120.  
  121. proc ::profiler::Handler {name args} {
  122.     variable enabled
  123.     if { $enabled } {
  124.     if { [info level] == 1 } {
  125.         set caller GLOBAL
  126.     } else {
  127.         # Get the name of the calling procedure
  128.         set caller [lindex [info level -1] 0]
  129.         # Remove the ORIG suffix
  130.         set caller [string range $caller 0 end-4]
  131.     }
  132.     if { [catch {incr ::profiler::callers($name,$caller)}] } {
  133.         set ::profiler::callers($name,$caller) 1
  134.     }
  135.     ::profiler::tZero $name.$caller
  136.     }
  137.  
  138.     set CODE [uplevel 1 [list ${name}ORIG] $args]
  139.     if { $enabled } {
  140.     set t [::profiler::tMark $name.$caller]
  141.     lappend ::profiler::statTime($name) $t
  142.  
  143.     if { [incr ::profiler::callCount($name)] == 1 } {
  144.         set ::profiler::compileTime($name) $t
  145.     }
  146.     incr ::profiler::totalRuntime($name) $t
  147.     if { [catch {incr ::profiler::descendantTime($caller) $t}] } {
  148.         set ::profiler::descendantTime($caller) $t
  149.     }
  150.     if { [catch {incr ::profiler::descendants($caller,$name)}] } {
  151.         set ::profiler::descendants($caller,$name) 1
  152.     }
  153.     }
  154.     return $CODE
  155. }
  156.  
  157. # ::profiler::profProc --
  158. #
  159. #    Replacement for the proc command that adds rudimentary profiling
  160. #    capabilities to Tcl.
  161. #
  162. # Arguments:
  163. #    name        name of the procedure
  164. #    arglist        list of arguments
  165. #    body        body of the procedure
  166. #
  167. # Results:
  168. #    None.
  169.  
  170. proc ::profiler::profProc {name arglist body} {
  171.     variable callCount
  172.     variable compileTime
  173.     variable totalRuntime
  174.     variable descendantTime
  175.     variable statTime
  176.     
  177.     # Get the fully qualified name of the proc
  178.     set ns [uplevel [list namespace current]]
  179.     # If the proc call did not happen at the global context and it did not
  180.     # have an absolute namespace qualifier, we have to prepend the current
  181.     # namespace to the command name
  182.     if { ![string equal $ns "::"] } {
  183.     if { ![regexp -- "^::" $name] } {
  184.         set name "${ns}::${name}"
  185.     }
  186.     }
  187.     if { ![regexp -- "^::" $name] } {
  188.     set name "::$name"
  189.     }
  190.  
  191.     # Set up accounting for this procedure
  192.     set callCount($name) 0
  193.     set compileTime($name) 0
  194.     set totalRuntime($name) 0
  195.     set descendantTime($name) 0
  196.     set statTime($name) {}
  197.  
  198.     uplevel 1 [list ::_oldProc ${name}ORIG $arglist $body]
  199.     uplevel 1 [list interp alias {} $name {} ::profiler::Handler $name]
  200.     return
  201. }
  202.  
  203. # ::profiler::init --
  204. #
  205. #    Initialize the profiler.
  206. #
  207. # Arguments:
  208. #    None.
  209. #
  210. # Results:
  211. #    None.  Renames proc to _oldProc and sets an alias for proc to 
  212. #        profiler::profProc
  213.  
  214. proc ::profiler::init {} {
  215.     rename ::proc ::_oldProc
  216.     interp alias {} proc {} ::profiler::profProc
  217.  
  218.     return
  219. }
  220.  
  221. # ::profiler::print --
  222. #
  223. #    Print information about a proc.
  224. #
  225. # Arguments:
  226. #    pattern    pattern of the proc's to get info for; default is *.
  227. #
  228. # Results:
  229. #    A human readable printout of info.
  230.  
  231. proc ::profiler::print {{pattern *}} {
  232.     variable callCount
  233.     variable compileTime
  234.     variable totalRuntime
  235.     variable descendantTime
  236.     variable descendants
  237.     variable statTime
  238.     variable callers
  239.     
  240.     set result ""
  241.     foreach name [lsort [array names callCount $pattern]] {
  242.     set avgRuntime 0
  243.     set sigmaRuntime 0
  244.     set covRuntime 0
  245.     set avgDesTime 0
  246.     if { $callCount($name) > 0 } {
  247.         foreach {m s c} [eval ::profiler::stats $statTime($name)] { break }
  248.         set avgRuntime   $m
  249.         set sigmaRuntime $s
  250.         set covRuntime   $c
  251.         set avgDesTime \
  252.             [expr {$descendantTime($name)/$callCount($name)}]
  253.     }
  254.  
  255.     append result "Profiling information for $name\n"
  256.     append result "[string repeat = 60]\n"
  257.     append result "            Total calls:  $callCount($name)\n"
  258.     if { !$callCount($name) } {
  259.         append result "\n"
  260.         continue
  261.     }
  262.     append result "    Caller distribution:\n"
  263.     set i [expr {[string length $name] + 1}]
  264.     foreach index [lsort [array names callers $name,*]] {
  265.         append result "  [string range $index $i end]:  $callers($index)\n"
  266.     }
  267.     append result "           Compile time:  $compileTime($name)\n"
  268.     append result "          Total runtime:  $totalRuntime($name)\n"
  269.     append result "        Average runtime:  $avgRuntime\n"
  270.     append result "          Runtime StDev:  $sigmaRuntime\n"
  271.     append result "         Runtime cov(%):  $covRuntime\n"
  272.     append result "  Total descendant time:  $descendantTime($name)\n"
  273.     append result "Average descendant time:  $avgDesTime\n"
  274.     append result "Descendants:\n"
  275.     if { !$descendantTime($name) } {
  276.         append result "  none\n"
  277.     }
  278.     foreach index [lsort [array names descendants $name,*]] {
  279.         append result "  [string range $index $i end]: \
  280.             $descendants($index)\n"
  281.     }
  282.     append result "\n"
  283.     }
  284.     return $result
  285. }
  286.  
  287. # ::profiler::dump --
  288. #
  289. #    Dump out the information for a proc in a big blob.
  290. #
  291. # Arguments:
  292. #    pattern    pattern of the proc's to lookup; default is *.
  293. #
  294. # Results:
  295. #    data    data about the proc's.
  296.  
  297. proc ::profiler::dump {{pattern *}} {
  298.     variable callCount
  299.     variable compileTime
  300.     variable totalRuntime
  301.     variable callers
  302.     variable descendantTime
  303.     variable descendants
  304.     variable statTime
  305.  
  306.     foreach name [lsort [array names callCount $pattern]] {
  307.     set i [expr {[string length $name] + 1}]
  308.     catch {unset thisCallers}
  309.     foreach index [lsort [array names callers $name,*]] {
  310.         set thisCallers([string range $index $i end]) $callers($index)
  311.     }
  312.     set avgRuntime 0
  313.     set sigmaRuntime 0
  314.     set covRuntime 0
  315.     set avgDesTime 0
  316.     if { $callCount($name) > 0 } {
  317.         foreach {m s c} [eval ::profiler::stats $statTime($name)] { break }
  318.         set avgRuntime   $m
  319.         set sigmaRuntime $s
  320.         set covRuntime   $c
  321.         set avgDesTime \
  322.             [expr {$descendantTime($name)/$callCount($name)}]
  323.     }
  324.     set descendantList [list ]
  325.     foreach index [lsort [array names descendants $name,*]] {
  326.         lappend descendantList [string range $index $i end]
  327.     }
  328.     lappend result $name [list callCount $callCount($name) \
  329.         callerDist [array get thisCallers] \
  330.         compileTime $compileTime($name) \
  331.         totalRuntime $totalRuntime($name) \
  332.         averageRuntime $avgRuntime \
  333.         stddevRuntime  $sigmaRuntime \
  334.         covpercentRuntime $covRuntime \
  335.         descendantTime $descendantTime($name) \
  336.         averageDescendantTime $avgDesTime \
  337.         descendants $descendantList]
  338.     }
  339.     return $result
  340. }
  341.  
  342. # ::profiler::sortFunctions --
  343. #
  344. #    Return a list of functions sorted by a particular field and the
  345. #    value of that field.
  346. #
  347. # Arguments:
  348. #    field    field to sort by
  349. #
  350. # Results:
  351. #    slist    sorted list of lists, sorted by the field in question.
  352.  
  353. proc ::profiler::sortFunctions {{field ""}} {
  354.     switch -glob -- $field {
  355.     "calls" {
  356.         upvar ::profiler::callCount data
  357.     }
  358.     "compileTime" {
  359.         upvar ::profiler::compileTime data
  360.     }
  361.     "totalRuntime" {
  362.         upvar ::profiler::totalRuntime data
  363.     }
  364.     "avgRuntime" -
  365.     "averageRuntime" {
  366.         variable callCount
  367.         variable totalRuntime
  368.         foreach fxn [array names callCount] {
  369.         if { $callCount($fxn) > 1 } {
  370.             set data($fxn) \
  371.                 [expr {$totalRuntime($fxn)/($callCount($fxn) - 1)}]
  372.         }
  373.         }
  374.     }
  375.     "exclusiveRuntime" {
  376.         variable totalRuntime
  377.         variable descendantTime
  378.         foreach fxn [array names totalRuntime] {
  379.         set data($fxn) \
  380.             [expr {$totalRuntime($fxn) - $descendantTime($fxn)}]
  381.         }
  382.     }
  383.     "avgExclusiveRuntime" {
  384.         variable totalRuntime
  385.         variable callCount
  386.         variable descendantTime
  387.         foreach fxn [array names totalRuntime] {
  388.         if { $callCount($fxn) } {
  389.             set data($fxn) \
  390.                 [expr {($totalRuntime($fxn) - \
  391.                 $descendantTime($fxn)) / $callCount($fxn)}]
  392.         }
  393.         }
  394.     }
  395.     "nonCompileTime" {
  396.         variable compileTime
  397.         variable totalRuntime
  398.         foreach fxn [array names totalRuntime] {
  399.         set data($fxn) [expr {$totalRuntime($fxn)-$compileTime($fxn)}]
  400.         }
  401.     }
  402.     default {
  403.         error "unknown statistic \"$field\": should be calls,\
  404.             compileTime, exclusiveRuntime, nonCompileTime,\
  405.             totalRuntime, avgExclusiveRuntime, or avgRuntime"
  406.     }
  407.     }
  408.         
  409.     set result [list ]
  410.     foreach fxn [array names data] {
  411.     lappend result [list $fxn $data($fxn)]
  412.     }
  413.     return [lsort -integer -index 1 $result]
  414. }
  415.  
  416. # ::profiler::reset --
  417. #
  418. #    Reset collected data for functions matching a given pattern.
  419. #
  420. # Arguments:
  421. #    pattern        pattern of functions to reset; default is *.
  422. #
  423. # Results:
  424. #    None.
  425.  
  426. proc ::profiler::reset {{pattern *}} {
  427.     variable callCount
  428.     variable compileTime
  429.     variable totalRuntime
  430.     variable callers
  431.     variable statTime
  432.  
  433.     foreach name [array names callCount $pattern] {
  434.     set callCount($name) 0
  435.     set compileTime($name) 0
  436.     set totalRuntime($name) 0
  437.     set statTime($name) {}
  438.     foreach caller [array names callers $name,*] {
  439.         unset callers($caller)
  440.     }
  441.     }
  442.     return
  443. }
  444.  
  445.