bench.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
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
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
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
00142
00143
00144
00145
00146
00147
00148
00149
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
00189
00190
00191
00192
00193
00194
00195
00196
00197
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
00226
00227
00228
00229
00230
00231
00232
00233
00234
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
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
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
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
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
00359
00360
00361
00362
00363
00364
00365
00366
00367
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
00400
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412 ret ::bench::out::raw (type data) {
00413 return $data
00414 }
00415
00416
00417
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
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
00523
00524 package provide bench 0.3.1
00525