###############################################################################
#
# 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.
#
if {[haveConstraint SQLite]} then {
object invoke -flags +NonPublic System.Data.SQLite.UnsafeNativeMethods \
sqlite3_shutdown
}
###############################################################################
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
#############################################################################
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
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]]
#############################################################################
#
# NOTE: Make sure to remove any stale database from previous test runs.
#
cleanupFile $fileName(2)
#############################################################################
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 count] {
#
# NOTE: Workload #1, CREATE TABLE statements.
#
lappend ::times(1) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 2} {$index <= $count} {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 count] {
#
# NOTE: Workload #2, DROP TABLE statements.
#
lappend ::times(2) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 2} {$index <= $count} {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 count] {
#
# NOTE: Workload #3, "small" SELECT statements.
#
lappend ::times(3) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #4, "big" SELECT statements.
#
lappend ::times(4) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #5, "small" INSERT statements.
#
lappend ::times(5) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {incr index} {
if {[catch {
sql execute $db [appendArgs \
"INSERT INTO " $table "(x, y, z) VALUES('" \
[format %lX [expr {random()}]] "', '" \
[base64 encode -- [expr {randstr($::count(3))}]] \
"', '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 count] {
#
# NOTE: Workload #6, "big" INSERT statements.
#
lappend ::times(6) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {incr index} {
if {[catch {
sql execute $db [appendArgs \
"INSERT INTO " $table "(x, y, z) VALUES('" \
[format %lX [expr {random()}]] \
"', RANDOMBLOB(" $::count(4) "), '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 count] {
#
# NOTE: Workload #7, "small" UPDATE statements.
#
lappend ::times(7) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {incr index} {
if {[catch {
sql execute $db [appendArgs "UPDATE " $table \
" SET y = '" [base64 encode -- [expr {randstr($::count(3))}]] \
"' 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 count] {
#
# NOTE: Workload #8, "big" UPDATE statements.
#
lappend ::times(8) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {incr index} {
if {[catch {
sql execute $db [appendArgs "UPDATE " $table \
" SET y = RANDOMBLOB(" $::count(4) \
") 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 count] {
#
# NOTE: Workload #9, "small" DELETE statements.
#
lappend ::times(9) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #10, "big" DELETE statements.
#
lappend ::times(10) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #11, VACUUM statement.
#
lappend ::times(11) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #12, backup to in-memory database.
#
lappend ::times(12) [lindex [time {
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #13, backup from an in-memory database.
#
lappend ::times(13) [lindex [time {
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #14, PRAGMA integrity check statement.
#
lappend ::times(14) [lindex [time {
setupDb $dstFileName "" "" "" "" "" true false
for {set index 1} {$index <= $count} {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 count] {
#
# NOTE: Workload #15, force managed garbage collection
#
lappend ::times(15) [lindex [time {
for {set index 1} {$index <= $count} {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)] 1048576]
}
}
foreach index(1) [list Start Join] {
foreach index(2) [array names thread] {
$thread($index(2)) $index(1)
}
}
unset -nocomplain thread
}
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"]
}
}
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)]
}
}
freeDbConnection
cleanupLogging $logFileName
rename cleanupLogging ""
rename setupLogging ""
unset -nocomplain result thread index workload noWorkload srcDb db \
fileName compiled options count times logFileName logListener \
connection
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result {}}
###############################################################################
runSQLiteTestEpilogue
runTestEpilogue