bench.tcl

Go to the documentation of this file.
00001 /*  bench.tcl --*/
00002 /* */
00003 /*  Management of benchmarks.*/
00004 /* */
00005 /*  Copyright (c) 2005-2007 by Andreas Kupries <andreas_kupries@users.sourceforge.net>*/
00006 /*  library derived from runbench.tcl application (C) Jeff Hobbs.*/
00007 /* */
00008 /*  See the file "license.terms" for information on usage and redistribution*/
00009 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00010 /* */
00011 /*  RCS: @(#) $Id: bench.tcl,v 1.12 2007/08/23 17:35:10 andreas_kupries Exp $*/
00012 
00013 /*  ### ### ### ######### ######### ######### ###########################*/
00014 /*  Requisites - Packages and namespace for the commands and data.*/
00015 
00016 package require Tcl 8.2
00017 package require logger
00018 package require csv
00019 package require struct::matrix
00020 package require report
00021 
00022 namespace ::bench      {}
00023 namespace ::bench::out {}
00024 
00025 /*  @mdgen OWNER: libbench.tcl*/
00026 
00027 /*  ### ### ### ######### ######### ######### ###########################*/
00028 /*  Public API - Benchmark execution*/
00029 
00030 /*  ::bench::run --*/
00031 /* */
00032 /*  Run a series of benchmarks.*/
00033 /* */
00034 /*  Arguments:*/
00035 /*  ...*/
00036 /* */
00037 /*  Results:*/
00038 /*  Dictionary.*/
00039 
00040 ret  ::bench::run (type args) {
00041     log::debug [linsert $args 0 ::bench::run]
00042 
00043     # -errors  0|1         default 1, propagate errors in benchmarks
00044     # -threads <num>       default 0, no threads, #threads to use
00045     # -match  <pattern>    only run tests matching this pattern
00046     # -rmatch <pattern>    only run tests matching this pattern
00047     # -iters  <num>        default 1000, max#iterations for any benchmark
00048     # -pkgdir <dir>        Defaults to nothing, regular bench invokation.
00049 
00050     # interps - dict (path -> version)
00051     # files   - list (of files)
00052 
00053     # Process arguments ......................................
00054     # Defaults first, then overides by the user
00055 
00056     set errors  1    ; # Propagate errors
00057     set threads 0    ; # Do not use threads
00058     set match   {}   ; # Do not exclude benchmarks based on glob pattern
00059     set rmatch  {}   ; # Do not exclude benchmarks based on regex pattern
00060     set iters   1000 ; # Limit #iterations for any benchmark
00061     set pkgdirs {}   ; # List of dirs to put in front of auto_path in the
00062                        # bench interpreters. Default: nothing.
00063 
00064     while {[string match "-*" [set opt [lindex $args 0]]]} {
00065     set val [lindex $args 1]
00066     switch -exact -- $opt {
00067         -errors {
00068         if {![string is boolean -strict $val]} {
00069             return -code error "Expected boolean, got \"$val\""
00070         }
00071         set errors $val
00072         }
00073         -threads {
00074         if {![string is int -strict $val] || ($val < 0)} {
00075             return -code error "Expected int >= 0, got \"$val\""
00076         }
00077         set threads [lindex $args 1]
00078         }
00079         -match {
00080         set match [lindex $args 1]
00081         }
00082         -rmatch {
00083         set rmatch [lindex $args 1]
00084         }
00085         -iters {
00086         if {![string is int -strict $val] || ($val <= 0)} {
00087             return -code error "Expected int > 0, got \"$val\""
00088         }
00089         set iters   [lindex $args 1]
00090         }
00091         -pkgdir {
00092         CheckPkgDirArg  $val
00093         lappend pkgdirs $val
00094         }
00095         default {
00096         return -code error "Unknown option \"$opt\", should -errors, -threads, -match, -rmatch, or -iters"
00097         }
00098     }
00099     set args [lrange $args 2 end]
00100     }
00101     if {[llength $args] != 2} {
00102     return -code error "wrong\#args, should be: ?options? interp files"
00103     }
00104     foreach {interps files} $args break
00105 
00106     # Run the benchmarks .....................................
00107 
00108     array set DATA {}
00109 
00110     if {![llength $pkgdirs]} {
00111     # No user specified package directories => Simple run.
00112     foreach {ip ver} $interps {
00113         Invoke $ip $ver {} ;# DATA etc passed via upvar.
00114     }
00115     } else {
00116     # User specified package directories.
00117     foreach {ip ver} $interps {
00118         foreach pkgdir $pkgdirs {
00119         Invoke $ip $ver $pkgdir ;# DATA etc passed via upvar.
00120         }
00121     }
00122     }
00123 
00124     # Benchmark data ... Structure, dict (key -> value)
00125     #
00126     # Key          || Value
00127     # ============ ++ =========================================
00128     # interp IP    -> Version. Shell IP was used to run benchmarks. IP is
00129     #                 the path to the shell.
00130     #
00131     # desc DESC    -> "". DESC is description of an executed benchmark.
00132     #
00133     # usec DESC IP -> Result. Result of benchmark DESC when run by the
00134     #                 shell IP. Usually time in microseconds, but can be
00135     #                 a special code as well (ERR, BAD_RES).
00136     # ============ ++ =========================================
00137 
00138     return [array get DATA]
00139 }
00140 
00141 /*  ::bench::locate --*/
00142 /* */
00143 /*  Locate interpreters on the pathlist, based on a pattern.*/
00144 /* */
00145 /*  Arguments:*/
00146 /*  ...*/
00147 /* */
00148 /*  Results:*/
00149 /*  List of paths.*/
00150 
00151 ret  ::bench::locate (type pattern , type paths) {
00152     # Cache of executables already found.
00153     array set var {}
00154     set res {}
00155 
00156     foreach path $paths {
00157     foreach ip [glob -nocomplain [file join $path $pattern]] {
00158         if {[package vsatisfies [package provide Tcl] 8.4]} {
00159         set ip [file normalize $ip]
00160         }
00161 
00162         # Follow soft-links to the actual executable.
00163         while {[string equal link [file type $ip]]} {
00164         set link [file readlink $ip]
00165         if {[string match relative [file pathtype $link]]} {
00166             set ip [file join [file dirname $ip] $link]
00167         } else {
00168             set ip $link
00169         }
00170         }
00171 
00172         if {
00173         [file executable $ip] && ![info exists var($ip)]
00174         } {
00175         if {[catch {exec $ip << "exit"} dummy]} {
00176             log::debug "$ip: $dummy"
00177             continue
00178         }
00179         set var($ip) .
00180         lappend res $ip
00181         }
00182     }
00183     }
00184 
00185     return $res
00186 }
00187 
00188 /*  ::bench::versions --*/
00189 /* */
00190 /*  Take list of interpreters, find their versions.*/
00191 /*  Removes all interps for which it cannot do so.*/
00192 /* */
00193 /*  Arguments:*/
00194 /*  List of interpreters (paths)*/
00195 /* */
00196 /*  Results:*/
00197 /*  dictionary: interpreter -> version.*/
00198 
00199 ret  ::bench::versions (type interps) {
00200     set res {}
00201     foreach ip $interps {
00202     if {[catch {
00203         exec $ip << {puts [info patchlevel] ; exit}
00204     } patchlevel]} {
00205         log::debug "$ip: $patchlevel"
00206         continue
00207     }
00208 
00209     lappend res [list $patchlevel $ip]
00210     }
00211 
00212     # -uniq 8.4-ism, replaced with use of array.
00213     array set tmp {}
00214     set resx {}
00215     foreach item [lsort -dictionary -decreasing -index 0 $res] {
00216     foreach {p ip} $item break
00217     if {[info exists tmp($p)]} continue
00218     set tmp($p) .
00219     lappend resx $ip $p
00220     }
00221 
00222     return $resx
00223 }
00224 
00225 /*  ::bench::merge --*/
00226 /* */
00227 /*  Take the data of several benchmark runs and merge them into*/
00228 /*  one data set.*/
00229 /* */
00230 /*  Arguments:*/
00231 /*  One or more data sets to merge*/
00232 /* */
00233 /*  Results:*/
00234 /*  The merged data set.*/
00235 
00236 ret  ::bench::merge (type args) {
00237     if {[llength $args] == 1} {
00238     return [lindex $args 0]
00239     }
00240 
00241     array set DATA {}
00242     foreach data $args {
00243     array set DATA $data
00244     }
00245     return [array get DATA]
00246 }
00247 
00248 /*  ::bench::norm --*/
00249 /* */
00250 /*  Normalize the time data in the dataset, using one of the*/
00251 /*  columns as reference.*/
00252 /* */
00253 /*  Arguments:*/
00254 /*  Data to normalize*/
00255 /*  Index of reference column*/
00256 /* */
00257 /*  Results:*/
00258 /*  The normalized data set.*/
00259 
00260 ret  ::bench::norm (type data , type col) {
00261 
00262     if {![string is integer -strict $col]} {
00263     return -code error "Ref.column: Expected integer, but got \"$col\""
00264     }
00265     if {$col < 1} {
00266     return -code error "Ref.column out of bounds"
00267     }
00268 
00269     array set DATA $data
00270     set ipkeys [array names DATA interp*]
00271 
00272     if {$col > [llength $ipkeys]} {
00273     return -code error "Ref.column out of bounds"
00274     }
00275     incr col -1
00276     set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1]
00277 
00278     foreach key [array names DATA] {
00279     if {[string match "desc*"   $key]} continue
00280     if {[string match "interp*" $key]} continue
00281 
00282     foreach {_ desc ip} $key break
00283     if {[string equal $ip $refip]}      continue
00284 
00285     set v $DATA($key)
00286     if {![string is double -strict $v]} continue
00287 
00288     if {![info exists DATA([list usec $desc $refip])]} {
00289         # We cannot normalize, we do not keep the time value.
00290         # The row will be shown, empty.
00291         set DATA($key) ""
00292         continue
00293     }
00294     set vref $DATA([list usec $desc $refip])
00295 
00296     if {![string is double -strict $vref]} continue
00297 
00298     set DATA($key) [expr {$v/double($vref)}]
00299     }
00300 
00301     foreach key [array names DATA [list * $refip]] {
00302     if {![string is double -strict $DATA($key)]} continue
00303     set DATA($key) 1
00304     }
00305 
00306     return [array get DATA]
00307 }
00308 
00309 /*  ::bench::edit --*/
00310 /* */
00311 /*  Change the 'path' of an interp to a user-defined value.*/
00312 /* */
00313 /*  Arguments:*/
00314 /*  Data to edit*/
00315 /*  Index of column to change*/
00316 /*  The value replacing the current path*/
00317 /* */
00318 /*  Results:*/
00319 /*  The changed data set.*/
00320 
00321 ret  ::bench::edit (type data , type col , type new) {
00322 
00323     if {![string is integer -strict $col]} {
00324     return -code error "Ref.column: Expected integer, but got \"$col\""
00325     }
00326     if {$col < 1} {
00327     return -code error "Ref.column out of bounds"
00328     }
00329 
00330     array set DATA $data
00331     set ipkeys [array names DATA interp*]
00332 
00333     if {$col > [llength $ipkeys]} {
00334     return -code error "Ref.column out of bounds"
00335     }
00336     incr col -1
00337     set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1]
00338 
00339     if {[string equal $new $refip]} {
00340     # No change, quick return
00341     return $data
00342     }
00343 
00344     set refkey [list interp $refip]
00345     set DATA([list interp $new]) $DATA($refkey)
00346     unset                         DATA($refkey)
00347 
00348     foreach key [array names DATA [list * $refip]] {
00349     if {![string equal [lindex $key 0] "usec"]} continue
00350     foreach {__ desc ip} $key break
00351     set DATA([list usec $desc $new]) $DATA($key)
00352     unset                             DATA($key)
00353     }
00354 
00355     return [array get DATA]
00356 }
00357 
00358 /*  ::bench::del --*/
00359 /* */
00360 /*  Remove the data for an interp.*/
00361 /* */
00362 /*  Arguments:*/
00363 /*  Data to edit*/
00364 /*  Index of column to remove*/
00365 /* */
00366 /*  Results:*/
00367 /*  The changed data set.*/
00368 
00369 ret  ::bench::del (type data , type col) {
00370 
00371     if {![string is integer -strict $col]} {
00372     return -code error "Ref.column: Expected integer, but got \"$col\""
00373     }
00374     if {$col < 1} {
00375     return -code error "Ref.column out of bounds"
00376     }
00377 
00378     array set DATA $data
00379     set ipkeys [array names DATA interp*]
00380 
00381     if {$col > [llength $ipkeys]} {
00382     return -code error "Ref.column out of bounds"
00383     }
00384     incr col -1
00385     set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1]
00386 
00387     unset DATA([list interp $refip])
00388 
00389     # Do not use 'array unset'. Keep 8.2 clean.
00390     foreach key [array names DATA [list * $refip]] {
00391     if {![string equal [lindex $key 0] "usec"]} continue
00392     unset DATA($key)
00393     }
00394 
00395     return [array get DATA]
00396 }
00397 
00398 /*  ### ### ### ######### ######### ######### ###########################*/
00399 /*  Public API - Result formatting.*/
00400 
00401 /*  ::bench::out::raw --*/
00402 /* */
00403 /*  Format the result of a benchmark run.*/
00404 /*  Style: Raw data.*/
00405 /* */
00406 /*  Arguments:*/
00407 /*  DATA dict*/
00408 /* */
00409 /*  Results:*/
00410 /*  String containing the formatted DATA.*/
00411 
00412 ret  ::bench::out::raw (type data) {
00413     return $data
00414 }
00415 
00416 /*  ### ### ### ######### ######### ######### ###########################*/
00417 /*  Internal commands*/
00418 
00419 ret  ::bench::CheckPkgDirArg (type path , optional expected ={)} {
00420     # Allow empty string, special.
00421     if {![string length $path]} return
00422 
00423     if {![file isdirectory $path]} {
00424     return -code error \
00425         "The path \"$path\" is not a directory."
00426     }
00427     if {![file readable $path]} {
00428     return -code error \
00429         "The path \"$path\" is not readable."
00430     }
00431 }
00432 
00433 ret  ::bench::Invoke (type ip , type ver , type pkgdir) {
00434     variable self
00435     # Import remainder of the current configuration/settings.
00436 
00437     upvar 1 DATA DATA match match rmatch rmatch \
00438     iters iters errors errors threads threads \
00439     files files
00440 
00441     if {[string length $pkgdir]} {
00442     log::info "Benchmark $ver ($pkgdir) $ip"
00443     set idstr "$ip ($pkgdir)"
00444     } else {
00445     log::info "Benchmark $ver $ip"
00446     set idstr $ip
00447     }
00448 
00449     set DATA([list interp $idstr]) $ver
00450 
00451     set cmd [list $ip [file join $self libbench.tcl] \
00452          -match   $match   \
00453          -rmatch  $rmatch  \
00454          -iters   $iters   \
00455          -interp  $ip      \
00456          -errors  $errors  \
00457          -threads $threads \
00458          -pkgdir  $pkgdir  \
00459         ]
00460 
00461     # Determine elapsed time per file, logged.
00462     set start [clock seconds]
00463 
00464     array set tmp {}
00465 
00466     if {$threads} {
00467     if {[catch {
00468         eval exec $cmd $files
00469     } output]} {
00470         if {$errors} {
00471         error $::errorInfo
00472         }
00473     } else {
00474         array set tmp $output
00475     }
00476     } else {
00477     foreach file $files {
00478         log::info [file tail $file]
00479         if {[catch {
00480         eval exec [linsert $cmd end $file]
00481         } output]} {
00482         if {$errors} {
00483             error $::errorInfo
00484         } else {
00485             continue
00486         }
00487         } else {
00488         array set tmp $output
00489         }
00490     }
00491     }
00492 
00493     catch {unset tmp(Sourcing)}
00494     catch {unset tmp(__THREADED)}
00495 
00496     foreach desc [array names tmp] {
00497     set DATA([list desc $desc]) {}
00498     set DATA([list usec $desc $idstr]) $tmp($desc)
00499     }
00500 
00501     unset tmp
00502     set elapsed [expr {[clock seconds] - $start}]
00503 
00504     set hour [expr {$elapsed / 3600}]
00505     set min  [expr {$elapsed / 60}]
00506     set sec  [expr {$elapsed % 60}]
00507     log::info " [format %.2d:%.2d:%.2d $hour $min $sec] elapsed"
00508     return
00509 }
00510 
00511 /*  ### ### ### ######### ######### ######### ###########################*/
00512 /*  Initialize internal data structures.*/
00513 
00514 namespace ::bench {
00515     variable self [file join [pwd] [file dirname [info script]]]
00516 
00517     logger::init bench
00518     logger::import -force -all -namespace log bench
00519 }
00520 
00521 /*  ### ### ### ######### ######### ######### ###########################*/
00522 /*  Ready to run*/
00523 
00524 package provide bench 0.3.1
00525 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1