############################################################################### # # stress.eagle -- # # Written by Joe Mistachkin. # Released to the public domain, use at your own risk! # ############################################################################### package require Eagle package require Eagle.Library package require Eagle.Test runTestPrologue ############################################################################### package require System.Data.SQLite.Test runSQLiteTestPrologue ############################################################################### # # NOTE: Make sure that SQLite core library is completely shutdown prior to # starting any of the tests in this file. # shutdownSQLite $test_channel ############################################################################### runTest {test stress-1.1 {multithreaded stress testing} -setup { unset -nocomplain result thread index workload noWorkload srcDb db \ fileName compiled options count times logFileName logListener \ connection indicators iterations ############################################################################# proc expectedError { error } { return [expr {[regexp -- {\sno such table: t1\s} $error] || \ [regexp -- {\sdatabase is locked\s} $error]}] } ############################################################################# proc showTest { indicator } { tputs $::test_channel $indicator append ::indicators $indicator after [expr {int(rand() * 1000 + $::count(2))}] } ############################################################################# proc failTest { error } { set level [expr {[info level] - 1}] tputs $::test_channel [appendArgs \ \n [info level $level] ": " $error \n] exit Failure; # halt all testing now. } ############################################################################# proc setupLogging { fileName } { if {![info exists ::logListener]} then { set ::logListener [object create -alias \ System.Diagnostics.TextWriterTraceListener $fileName] } object invoke System.Diagnostics.Trace.Listeners Add $::logListener object invoke System.Data.SQLite.SQLiteLog Initialize tputs $::test_channel [appendArgs \ "---- enabled SQLite trace logging to file \"" $fileName \"\n] } ############################################################################# proc cleanupLogging { fileName } { if {[info exists ::logListener]} then { object invoke System.Diagnostics.Trace.Listeners Remove $::logListener $::logListener Close } # # NOTE: Copy the trace listener log file to the main test log file. # tlog "---- BEGIN TRACE LISTENER OUTPUT\n" tlog [readFile $fileName] tlog "\n---- END TRACE LISTENER OUTPUT\n" # # NOTE: Delete the trace listener log file because its contents have # been copied to the main test log file. # cleanupFile $fileName tputs $::test_channel [appendArgs \ "---- disabled SQLite trace logging to file \"" $fileName \"\n] } ############################################################################# # # NOTE: The trace listener used with the SQLiteLog class to capture output # from the core SQLite library requires its own log file because the # TextWriterTraceListener class opens and locks the log file it uses # as the basis of the output stream. Before this test is complete, # the entire contents of this trace log file will be copied into the # main test log file and then deleted. # set logFileName [appendArgs $test_log .trace] setupLogging $logFileName ############################################################################# set count(0) 1 set count(1) 5 set count(2) 300 set count(3) 57 set count(4) 10000000 set noWorkload [list] if {[info exists argv] && [llength $argv] > 0} then { parse options -flags \ {-StopOnUnknownOption +IgnoreOnUnknownOption SkipOnUnknownOption} -- \ [list [list null MustHaveIntegerValue -1 -1 -count0 $count(0)] \ [list null MustHaveIntegerValue -1 -1 -count1 $count(1)] \ [list null MustHaveIntegerValue -1 -1 -count2 $count(2)] \ [list null MustHaveIntegerValue -1 -1 -count3 $count(3)] \ [list null MustHaveIntegerValue -1 -1 -count4 $count(4)] \ [list null MustHaveListValue -1 -1 -noWorkload $noWorkload]] $argv set count(0) $options(-count0,value) set count(1) $options(-count1,value) set count(2) $options(-count2,value) set count(3) $options(-count3,value) set count(4) $options(-count4,value) set noWorkload $options(-noWorkload,value) } ############################################################################# tputs $test_channel [appendArgs \ "---- workloads will repeat " $count(0) " time(s)...\n"] tputs $test_channel [appendArgs \ "---- workloads will have " $count(1) " iteration(s)...\n"] tputs $test_channel [appendArgs \ "---- workloads will wait at least " $count(2) " millisecond(s)...\n"] tputs $test_channel [appendArgs \ "---- workload \"small\" chunk size is " $count(3) " byte(s)...\n"] tputs $test_channel [appendArgs \ "---- workload \"big\" chunk size is " $count(4) " byte(s)...\n"] if {[llength $noWorkload] > 0} then { tputs $test_channel [appendArgs \ "---- workloads to be skipped... " $noWorkload \n] } ############################################################################# set compiled(12) "" set compiled(13) "" ############################################################################# # # NOTE: This is an in-memory database with shared cache enabled. # set fileName(1) "file::memory:?cache=shared" ############################################################################# # # NOTE: This is a normal on-disk database. # set fileName(2) [file join [getDatabaseDirectory] [appendArgs \ stress- [pid] - [string trim [clock seconds] -] .db]] ############################################################################# setupDb $fileName(1) "" "" "" "" "" false false true true srcDb setupDb $fileName(2) ############################################################################# set connection [getDbConnection] $connection LogMessage Ok [appendArgs \ "starting stress test using database \"" $fileName(2) "\"..."] ############################################################################# set workload(1) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #1, CREATE TABLE statements. # lappend ::times(1) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 2} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs \ "CREATE TABLE IF NOT EXISTS t" $index "(x PRIMARY KEY, y, z);"] showTest A } error]} then { if {[expectedError $error]} then { showTest a } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(2) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #2, DROP TABLE statements. # lappend ::times(2) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 2} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs \ "DROP TABLE IF EXISTS t" $index \;] showTest B } error]} then { if {[expectedError $error]} then { showTest b } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(3) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #3, "small" SELECT statements. # lappend ::times(3) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute -execute reader $db [appendArgs \ "SELECT x, y FROM " $table " WHERE z = 'small';"] showTest C } error]} then { if {[expectedError $error]} then { showTest c } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(4) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #4, "big" SELECT statements. # lappend ::times(4) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute -execute reader $db [appendArgs \ "SELECT x, y FROM " $table " WHERE z = 'big';"] showTest D } error]} then { if {[expectedError $error]} then { showTest d } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(5) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #5, "small" INSERT statements. # lappend ::times(5) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs \ "INSERT INTO " $table "(x, y, z) VALUES('" \ [format %lX [expr {random()}]] "', '" \ [base64 encode -- [expr {randstr($count2)}]] \ "', 'small');"] showTest E } error]} then { if {[expectedError $error]} then { showTest e } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(6) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #6, "big" INSERT statements. # lappend ::times(6) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs \ "INSERT INTO " $table "(x, y, z) VALUES('" \ [format %lX [expr {random()}]] \ "', RANDOMBLOB(" $count3 "), 'big');"] showTest F } error]} then { if {[expectedError $error]} then { showTest f } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(7) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #7, "small" UPDATE statements. # lappend ::times(7) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs "UPDATE " $table \ " SET y = '" [base64 encode -- [expr {randstr($count2)}]] \ "' WHERE x LIKE '" [format %X $index] "%' AND z = 'small';"] showTest G } error]} then { if {[expectedError $error]} then { showTest g } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(8) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #8, "big" UPDATE statements. # lappend ::times(8) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs "UPDATE " $table \ " SET y = RANDOMBLOB(" $count3 \ ") WHERE x LIKE '" [format %X $index] \ "%' AND z = 'big';"] showTest H } error]} then { if {[expectedError $error]} then { showTest h } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(9) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #9, "small" DELETE statements. # lappend ::times(9) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs "DELETE FROM " $table \ " WHERE x LIKE '" [format %X $index] "%' AND z = 'small';"] showTest I } error]} then { if {[expectedError $error]} then { showTest i } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(10) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #10, "big" DELETE statements. # lappend ::times(10) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db [appendArgs "DELETE FROM " $table \ " WHERE x LIKE '" [format %X $index] "%' AND z = 'big';"] showTest J } error]} then { if {[expectedError $error]} then { showTest j } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(11) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #11, VACUUM statement. # lappend ::times(11) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { sql execute $db "VACUUM;" showTest K } error]} then { if {[expectedError $error]} then { showTest k } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(12) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #12, backup to in-memory database. # lappend ::times(12) [lindex [time { for {set index 1} {$index <= $count1} {incr index} { if {[string is integer -strict $::compiled(12)]} then { set id $::compiled(12); # NOTE: Already compiled. if {[catch { object invoke _Dynamic${id}.Test${id} BackupAndGetData showTest L } error]} then { if {[expectedError $error]} then { showTest l } else { failTest $error } } } else { set id [object invoke Interpreter.GetActive NextId] set code [compileCSharpWith [subst { using System; using System.Data.SQLite; using System.Text; namespace _Dynamic${id} { public static class Test${id} { public static void BackupAndGetData() { using (SQLiteConnection source = new SQLiteConnection( "FullUri=${dstFileName};")) { source.Open(); using (SQLiteConnection destination = new SQLiteConnection( "FullUri=${srcFileName};")) { destination.Open(); source.BackupDatabase( destination, "main", "main", -1, null, 0); } } } /////////////////////////////////////////////////////////////// public static void Main() { // do nothing. } } } }] true true true results errors System.Data.SQLite.dll] if {$code eq "Ok"} then { set ::compiled(12) $id; # NOTE: Compiled OK. if {[catch { object invoke _Dynamic${id}.Test${id} BackupAndGetData showTest L } error]} then { if {[expectedError $error]} then { showTest l } else { failTest $error } } } else { error $errors } } } }] 0] }] ############################################################################# set workload(13) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #13, backup from an in-memory database. # lappend ::times(13) [lindex [time { for {set index 1} {$index <= $count1} {incr index} { if {[string is integer -strict $::compiled(13)]} then { set id $::compiled(13); # NOTE: Already compiled. if {[catch { object invoke _Dynamic${id}.Test${id} BackupAndGetData showTest M } error]} then { if {[expectedError $error]} then { showTest m } else { failTest $error } } } else { set id [object invoke Interpreter.GetActive NextId] set code [compileCSharpWith [subst { using System; using System.Data.SQLite; using System.Text; namespace _Dynamic${id} { public static class Test${id} { public static void BackupAndGetData() { using (SQLiteConnection source = new SQLiteConnection( "FullUri=${srcFileName};")) { source.Open(); using (SQLiteConnection destination = new SQLiteConnection( "FullUri=${dstFileName};")) { destination.Open(); source.BackupDatabase( destination, "main", "main", -1, null, 0); } } } /////////////////////////////////////////////////////////////// public static void Main() { // do nothing. } } } }] true true true results errors System.Data.SQLite.dll] if {$code eq "Ok"} then { set ::compiled(13) $id; # NOTE: Compiled OK. if {[catch { object invoke _Dynamic${id}.Test${id} BackupAndGetData showTest M } error]} then { if {[expectedError $error]} then { showTest m } else { failTest $error } } } else { error $errors } } } }] 0] }] ############################################################################# set workload(14) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #14, PRAGMA integrity check statement. # lappend ::times(14) [lindex [time { setupDb $dstFileName "" "" "" "" "" true false for {set index 1} {$index <= $count1} {incr index} { if {[catch { set result [sql execute -execute scalar $db \ "PRAGMA integrity_check;"] if {$result eq "ok"} then { showTest N } else { error [appendArgs "integrity check failed: " $result] } } error]} then { if {[expectedError $error]} then { showTest n } else { failTest $error } } } cleanupDb $dstFileName db false true false }] 0] }] ############################################################################# set workload(15) [list \ [list srcFileName dstFileName table count1 count2 count3] { # # NOTE: Workload #15, force managed garbage collection # lappend ::times(15) [lindex [time { for {set index 1} {$index <= $count1} {incr index} { if {[catch { collectGarbage $::test_channel showTest O } error]} then { if {[expectedError $error]} then { showTest o } else { failTest $error } } } }] 0] }] } -body { for {set index(0) 0} {$index(0) < $count(0)} {incr index(0)} { sql execute $db "CREATE TABLE IF NOT EXISTS t1(x PRIMARY KEY, y, z);" unset -nocomplain thread foreach index(1) [lsort -integer [array names workload]] { if {[lsearch -exact $noWorkload $index(1)] == -1} then { set thread($index(1)) [object create -alias System.Threading.Thread \ [list apply $workload($index(1)) $fileName(1) $fileName(2) t1 \ $count(1) $count(3) $count(4)] 1048576] } } foreach index(1) [list Start Join] { foreach index(2) [array names thread] { $thread($index(2)) $index(1) } } unset -nocomplain thread } ############################################################################# foreach index(0) [split $indicators ""] { # # NOTE: See if this workload iteration raised an error. If so, the # indicator letter will be in lower case; otherwise, it will # be in upper case. # set index(1) [string is upper -strict $index(0)] set index(2) [expr {[string ordinal $index(0) 0] - \ [string ordinal [expr {$index(1) ? "A" : "a"}] 0] + 1}] set index(3) [expr {$index(1) ? "ok" : "error"}] if {![info exists iterations($index(2),$index(3))]} then { set iterations($index(2),$index(3)) 0 } incr iterations($index(2),$index(3)) } ############################################################################# # # NOTE: Advance output to the next line due to the workload iteration # progress indicators. # tputs $test_channel \n ############################################################################# foreach index(0) [lsort -integer [array names times]] { set times(length) [llength $times($index(0))] if {$times(length) > 0} then { tputs $test_channel [appendArgs \ "---- average time for workload (" $index(0) ") is about " \ [expr int(([join $times($index(0)) +])/$times(length)/1000.0)] \ " milliseconds\n"] } else { tputs $test_channel [appendArgs \ "---- no times for workload (" $index(0) ")\n"] } } ############################################################################# foreach index(0) [lsort -integer [array names workload]] { if {[info exists iterations($index(0),ok)]} then { tputs $test_channel [appendArgs \ "---- workload (" $index(0) ") had " \ $iterations($index(0),ok) \ " iterations that returned \"ok\"\n"] } else { tputs $test_channel [appendArgs \ "---- workload (" $index(0) \ ") had no iterations that returned \"ok\"\n"] } if {[info exists iterations($index(0),error)]} then { tputs $test_channel [appendArgs \ "---- workload (" $index(0) ") had " \ $iterations($index(0),error) \ " iterations that returned \"error\"\n"] } else { tputs $test_channel [appendArgs \ "---- workload (" $index(0) \ ") had no iterations that returned \"error\"\n"] } } ############################################################################# set result [sql execute -execute scalar $srcDb "PRAGMA integrity_check;"] if {$result eq "ok"} then { tputs $test_channel "---- integrity check ok (srcDb)\n" } else { error [appendArgs "integrity check failed (srcDb): " $result] } ############################################################################# set result [sql execute -execute scalar $db "PRAGMA integrity_check;"] if {$result eq "ok"} then { tputs $test_channel "---- integrity check ok (db)\n" } else { error [appendArgs "integrity check failed (db): " $result] } } -cleanup { rename failTest "" rename showTest "" rename expectedError "" cleanupDb $fileName(2) cleanupDb $fileName(1) srcDb foreach index(0) [array names workload] { catch { object removecallback [list apply $workload($index(0)) $fileName(1) \ $fileName(2) t1 $count(1) $count(3) $count(4)] } } freeDbConnection cleanupLogging $logFileName rename cleanupLogging "" rename setupLogging "" unset -nocomplain result thread index workload noWorkload srcDb db \ fileName compiled options count times logFileName logListener \ connection indicators iterations } -constraints \ {eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result {}} ############################################################################### runSQLiteTestEpilogue runTestEpilogue