diff options
Diffstat (limited to 'ext/lsm1/lsm-test/lsmtest_main.c')
-rw-r--r-- | ext/lsm1/lsm-test/lsmtest_main.c | 1548 |
1 files changed, 1548 insertions, 0 deletions
diff --git a/ext/lsm1/lsm-test/lsmtest_main.c b/ext/lsm1/lsm-test/lsmtest_main.c new file mode 100644 index 0000000..f4a3ac0 --- /dev/null +++ b/ext/lsm1/lsm-test/lsmtest_main.c @@ -0,0 +1,1548 @@ + +#include "lsmtest.h" +#include <sqlite3.h> + +void test_failed(){ + assert( 0 ); + return; +} + +#define testSetError(rc) testSetErrorFunc(rc, pRc, __FILE__, __LINE__) +static void testSetErrorFunc(int rc, int *pRc, const char *zFile, int iLine){ + if( rc ){ + *pRc = rc; + fprintf(stderr, "FAILED (%s:%d) rc=%d ", zFile, iLine, rc); + test_failed(); + } +} + +static int lsm_memcmp(u8 *a, u8 *b, int c){ + int i; + for(i=0; i<c; i++){ + if( a[i]!=b[i] ) return a[i] - b[i]; + } + return 0; +} + +/* +** A test utility function. +*/ +void testFetch( + TestDb *pDb, /* Database handle */ + void *pKey, int nKey, /* Key to query database for */ + void *pVal, int nVal, /* Expected value */ + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==0 ){ + void *pDbVal; + int nDbVal; + int rc; + + static int nCall = 0; nCall++; + + rc = tdb_fetch(pDb, pKey, nKey, &pDbVal, &nDbVal); + testSetError(rc); + if( rc==0 && (nVal!=nDbVal || (nVal>0 && lsm_memcmp(pVal, pDbVal, nVal))) ){ + testSetError(1); + } + } +} + +void testWrite( + TestDb *pDb, /* Database handle */ + void *pKey, int nKey, /* Key to query database for */ + void *pVal, int nVal, /* Value to write */ + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==0 ){ + int rc; +static int nCall = 0; +nCall++; + rc = tdb_write(pDb, pKey, nKey, pVal, nVal); + testSetError(rc); + } +} +void testDelete( + TestDb *pDb, /* Database handle */ + void *pKey, int nKey, /* Key to query database for */ + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==0 ){ + int rc; + *pRc = rc = tdb_delete(pDb, pKey, nKey); + testSetError(rc); + } +} +void testDeleteRange( + TestDb *pDb, /* Database handle */ + void *pKey1, int nKey1, + void *pKey2, int nKey2, + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==0 ){ + int rc; + *pRc = rc = tdb_delete_range(pDb, pKey1, nKey1, pKey2, nKey2); + testSetError(rc); + } +} + +void testBegin(TestDb *pDb, int iTrans, int *pRc){ + if( *pRc==0 ){ + int rc; + rc = tdb_begin(pDb, iTrans); + testSetError(rc); + } +} +void testCommit(TestDb *pDb, int iTrans, int *pRc){ + if( *pRc==0 ){ + int rc; + rc = tdb_commit(pDb, iTrans); + testSetError(rc); + } +} +#if 0 /* unused */ +static void testRollback(TestDb *pDb, int iTrans, int *pRc){ + if( *pRc==0 ){ + int rc; + rc = tdb_rollback(pDb, iTrans); + testSetError(rc); + } +} +#endif + +void testWriteStr( + TestDb *pDb, /* Database handle */ + const char *zKey, /* Key to query database for */ + const char *zVal, /* Value to write */ + int *pRc /* IN/OUT: Error code */ +){ + int nVal = (zVal ? strlen(zVal) : 0); + testWrite(pDb, (void *)zKey, strlen(zKey), (void *)zVal, nVal, pRc); +} + +#if 0 /* unused */ +static void testDeleteStr(TestDb *pDb, const char *zKey, int *pRc){ + testDelete(pDb, (void *)zKey, strlen(zKey), pRc); +} +#endif +void testFetchStr( + TestDb *pDb, /* Database handle */ + const char *zKey, /* Key to query database for */ + const char *zVal, /* Value to write */ + int *pRc /* IN/OUT: Error code */ +){ + int nVal = (zVal ? strlen(zVal) : 0); + testFetch(pDb, (void *)zKey, strlen(zKey), (void *)zVal, nVal, pRc); +} + +void testFetchCompare( + TestDb *pControl, + TestDb *pDb, + void *pKey, int nKey, + int *pRc +){ + int rc; + void *pDbVal1; + void *pDbVal2; + int nDbVal1; + int nDbVal2; + + static int nCall = 0; + nCall++; + + rc = tdb_fetch(pControl, pKey, nKey, &pDbVal1, &nDbVal1); + testSetError(rc); + + rc = tdb_fetch(pDb, pKey, nKey, &pDbVal2, &nDbVal2); + testSetError(rc); + + if( *pRc==0 + && (nDbVal1!=nDbVal2 || (nDbVal1>0 && memcmp(pDbVal1, pDbVal2, nDbVal1))) + ){ + testSetError(1); + } +} + +typedef struct ScanResult ScanResult; +struct ScanResult { + TestDb *pDb; + + int nRow; + u32 cksum1; + u32 cksum2; + void *pKey1; int nKey1; + void *pKey2; int nKey2; + + int bReverse; + int nPrevKey; + u8 aPrevKey[256]; +}; + +static int keyCompare(void *pKey1, int nKey1, void *pKey2, int nKey2){ + int res; + res = memcmp(pKey1, pKey2, MIN(nKey1, nKey2)); + if( res==0 ){ + res = nKey1 - nKey2; + } + return res; +} + +int test_scan_debug = 0; + +static void scanCompareCb( + void *pCtx, + void *pKey, int nKey, + void *pVal, int nVal +){ + ScanResult *p = (ScanResult *)pCtx; + u8 *aKey = (u8 *)pKey; + u8 *aVal = (u8 *)pVal; + int i; + + if( test_scan_debug ){ + printf("%d: %.*s\n", p->nRow, nKey, (char *)pKey); + fflush(stdout); + } +#if 0 + if( test_scan_debug ) printf("%.20s\n", (char *)pVal); +#endif + +#if 0 + /* Check tdb_fetch() matches */ + int rc = 0; + testFetch(p->pDb, pKey, nKey, pVal, nVal, &rc); + assert( rc==0 ); +#endif + + /* Update the checksum data */ + p->nRow++; + for(i=0; i<nKey; i++){ + p->cksum1 += ((int)aKey[i] << (i&0x0F)); + p->cksum2 += p->cksum1; + } + for(i=0; i<nVal; i++){ + p->cksum1 += ((int)aVal[i] << (i&0x0F)); + p->cksum2 += p->cksum1; + } + + /* Check that the delivered row is not out of order. */ + if( nKey<(int)sizeof(p->aPrevKey) ){ + if( p->nPrevKey ){ + int res = keyCompare(p->aPrevKey, p->nPrevKey, pKey, nKey); + if( (res<0 && p->bReverse) || (res>0 && p->bReverse==0) ){ + testPrintError("Returned key out of order at %s:%d\n", + __FILE__, __LINE__ + ); + } + } + + p->nPrevKey = nKey; + memcpy(p->aPrevKey, pKey, MIN(p->nPrevKey, nKey)); + } + + /* Check that the delivered row is within range. */ + if( p->pKey1 && ( + (memcmp(p->pKey1, pKey, MIN(p->nKey1, nKey))>0) + || (memcmp(p->pKey1, pKey, MIN(p->nKey1, nKey))==0 && p->nKey1>nKey) + )){ + testPrintError("Returned key too small at %s:%d\n", __FILE__, __LINE__); + } + if( p->pKey2 && ( + (memcmp(p->pKey2, pKey, MIN(p->nKey2, nKey))<0) + || (memcmp(p->pKey2, pKey, MIN(p->nKey2, nKey))==0 && p->nKey2<nKey) + )){ + testPrintError("Returned key too large at %s:%d\n", __FILE__, __LINE__); + } + +} + +/* +** Scan the contents of the two databases. Check that they match. +*/ +void testScanCompare( + TestDb *pDb1, /* Control (trusted) database */ + TestDb *pDb2, /* Database being tested */ + int bReverse, + void *pKey1, int nKey1, + void *pKey2, int nKey2, + int *pRc +){ + static int nCall = 0; nCall++; + if( *pRc==0 ){ + ScanResult res1; + ScanResult res2; + void *pRes1 = (void *)&res1; + void *pRes2 = (void *)&res2; + + memset(&res1, 0, sizeof(ScanResult)); + memset(&res2, 0, sizeof(ScanResult)); + + res1.pDb = pDb1; + res1.nKey1 = nKey1; res1.pKey1 = pKey1; + res1.nKey2 = nKey2; res1.pKey2 = pKey2; + res1.bReverse = bReverse; + res2.pDb = pDb2; + res2.nKey1 = nKey1; res2.pKey1 = pKey1; + res2.nKey2 = nKey2; res2.pKey2 = pKey2; + res2.bReverse = bReverse; + + tdb_scan(pDb1, pRes1, bReverse, pKey1, nKey1, pKey2, nKey2, scanCompareCb); +if( test_scan_debug ) printf("\n\n\n"); + tdb_scan(pDb2, pRes2, bReverse, pKey1, nKey1, pKey2, nKey2, scanCompareCb); +if( test_scan_debug ) printf("\n\n\n"); + + if( res1.nRow!=res2.nRow + || res1.cksum1!=res2.cksum1 + || res1.cksum2!=res2.cksum2 + ){ + printf("expected: %d %X %X\n", res1.nRow, res1.cksum1, res1.cksum2); + printf("got: %d %X %X\n", res2.nRow, res2.cksum1, res2.cksum2); + testSetError(1); + *pRc = 1; + } + } +} + +void testClose(TestDb **ppDb){ + tdb_close(*ppDb); + *ppDb = 0; +} + +TestDb *testOpen(const char *zSystem, int bClear, int *pRc){ + TestDb *pDb = 0; + if( *pRc==0 ){ + int rc; + rc = tdb_open(zSystem, 0, bClear, &pDb); + if( rc!=0 ){ + testSetError(rc); + *pRc = rc; + } + } + return pDb; +} + +void testReopen(TestDb **ppDb, int *pRc){ + if( *pRc==0 ){ + const char *zLib; + zLib = tdb_library_name(*ppDb); + testClose(ppDb); + *pRc = tdb_open(zLib, 0, 0, ppDb); + } +} + + +#if 0 /* unused */ +static void testSystemSelect(const char *zSys, int *piSel, int *pRc){ + if( *pRc==0 ){ + struct SysName { const char *zName; } *aName; + int nSys; + int i; + + for(nSys=0; tdb_system_name(nSys); nSys++); + aName = malloc(sizeof(struct SysName) * (nSys+1)); + for(i=0; i<=nSys; i++){ + aName[i].zName = tdb_system_name(i); + } + + *pRc = testArgSelect(aName, "db", zSys, piSel); + free(aName); + } +} +#endif + +char *testMallocVPrintf(const char *zFormat, va_list ap){ + int nByte; + va_list copy; + char *zRet; + + __va_copy(copy, ap); + nByte = vsnprintf(0, 0, zFormat, copy); + va_end(copy); + + assert( nByte>=0 ); + zRet = (char *)testMalloc(nByte+1); + vsnprintf(zRet, nByte+1, zFormat, ap); + return zRet; +} + +char *testMallocPrintf(const char *zFormat, ...){ + va_list ap; + char *zRet; + + va_start(ap, zFormat); + zRet = testMallocVPrintf(zFormat, ap); + va_end(ap); + + return zRet; +} + + +/* +** A wrapper around malloc(3). +** +** This function should be used for all allocations made by test procedures. +** It has the following properties: +** +** * Test code may assume that allocations may not fail. +** * Returned memory is always zeroed. +** +** Allocations made using testMalloc() should be freed using testFree(). +*/ +void *testMalloc(int n){ + u8 *p = (u8*)malloc(n + 8); + memset(p, 0, n+8); + *(int*)p = n; + return (void*)&p[8]; +} + +void *testMallocCopy(void *pCopy, int nByte){ + void *pRet = testMalloc(nByte); + memcpy(pRet, pCopy, nByte); + return pRet; +} + +void *testRealloc(void *ptr, int n){ + if( ptr ){ + u8 *p = (u8*)ptr - 8; + int nOrig = *(int*)p; + p = (u8*)realloc(p, n+8); + if( nOrig<n ){ + memset(&p[8+nOrig], 0, n-nOrig); + } + *(int*)p = n; + return (void*)&p[8]; + } + return testMalloc(n); +} + +/* +** Free an allocation made by an earlier call to testMalloc(). +*/ +void testFree(void *ptr){ + if( ptr ){ + u8 *p = (u8*)ptr - 8; + memset(p, 0x55, *(int*)p + 8); + free(p); + } +} + +/* +** String zPattern contains a glob pattern. Return true if zStr matches +** the pattern, or false if it does not. +*/ +int testGlobMatch(const char *zPattern, const char *zStr){ + int i = 0; + int j = 0; + + while( zPattern[i] ){ + char p = zPattern[i]; + + if( p=='*' || p=='%' ){ + do { + if( testGlobMatch(&zPattern[i+1], &zStr[j]) ) return 1; + }while( zStr[j++] ); + return 0; + } + + if( zStr[j]==0 || (p!='?' && p!=zStr[j]) ){ + /* Match failed. */ + return 0; + } + + j++; + i++; + } + + return (zPattern[i]==0 && zStr[j]==0); +} + +/* +** End of test utilities +**************************************************************************/ + +int do_test(int nArg, char **azArg){ + int j; + int rc; + int nFail = 0; + const char *zPattern = 0; + + if( nArg>1 ){ + testPrintError("Usage: test ?PATTERN?\n"); + return 1; + } + if( nArg==1 ){ + zPattern = azArg[0]; + } + + for(j=0; tdb_system_name(j); j++){ + rc = 0; + + test_data_1(tdb_system_name(j), zPattern, &rc); + test_data_2(tdb_system_name(j), zPattern, &rc); + test_data_3(tdb_system_name(j), zPattern, &rc); + test_data_4(tdb_system_name(j), zPattern, &rc); + test_rollback(tdb_system_name(j), zPattern, &rc); + test_mc(tdb_system_name(j), zPattern, &rc); + test_mt(tdb_system_name(j), zPattern, &rc); + + if( rc ) nFail++; + } + + rc = 0; + test_oom(zPattern, &rc); + if( rc ) nFail++; + + rc = 0; + test_api(zPattern, &rc); + if( rc ) nFail++; + + rc = 0; + do_crash_test(zPattern, &rc); + if( rc ) nFail++; + + rc = 0; + do_writer_crash_test(zPattern, &rc); + if( rc ) nFail++; + + return (nFail!=0); +} + +static lsm_db *configure_lsm_db(TestDb *pDb){ + lsm_db *pLsm; + pLsm = tdb_lsm(pDb); + if( pLsm ){ + tdb_lsm_config_str(pDb, "mmap=1 autowork=1 automerge=4 worker_automerge=4"); + } + return pLsm; +} + +typedef struct WriteHookEvent WriteHookEvent; +struct WriteHookEvent { + i64 iOff; + int nData; + int nUs; +}; +WriteHookEvent prev = {0, 0, 0}; + +static void flushPrev(FILE *pOut){ + if( prev.nData ){ + fprintf(pOut, "w %s %lld %d %d\n", "d", prev.iOff, prev.nData, prev.nUs); + prev.nData = 0; + } +} + +#if 0 /* unused */ +static void do_speed_write_hook2( + void *pCtx, + int bLog, + i64 iOff, + int nData, + int nUs +){ + FILE *pOut = (FILE *)pCtx; + if( bLog ) return; + + if( prev.nData && nData && iOff==prev.iOff+prev.nData ){ + prev.nData += nData; + prev.nUs += nUs; + }else{ + flushPrev(pOut); + if( nData==0 ){ + fprintf(pOut, "s %s 0 0 %d\n", (bLog ? "l" : "d"), nUs); + }else{ + prev.iOff = iOff; + prev.nData = nData; + prev.nUs = nUs; + } + } +} +#endif + +#define ST_REPEAT 0 +#define ST_WRITE 1 +#define ST_PAUSE 2 +#define ST_FETCH 3 +#define ST_SCAN 4 +#define ST_NSCAN 5 +#define ST_KEYSIZE 6 +#define ST_VALSIZE 7 +#define ST_TRANS 8 + + +static void print_speed_test_help(){ + printf( +"\n" +"Repeat the following $repeat times:\n" +" 1. Insert $write key-value pairs. One transaction for each write op.\n" +" 2. Pause for $pause ms.\n" +" 3. Perform $fetch queries on the database.\n" +"\n" +" Keys are $keysize bytes in size. Values are $valsize bytes in size\n" +" Both keys and values are pseudo-randomly generated\n" +"\n" +"Options are:\n" +" -repeat $repeat (default value 10)\n" +" -write $write (default value 10000)\n" +" -pause $pause (default value 0)\n" +" -fetch $fetch (default value 0)\n" +" -keysize $keysize (default value 12)\n" +" -valsize $valsize (default value 100)\n" +" -system $system (default value \"lsm\")\n" +" -trans $trans (default value 0)\n" +"\n" +); +} + +int do_speed_test2(int nArg, char **azArg){ + struct Option { + const char *zOpt; + int eVal; + int iDefault; + } aOpt[] = { + { "-repeat", ST_REPEAT, 10}, + { "-write", ST_WRITE, 10000}, + { "-pause", ST_PAUSE, 0}, + { "-fetch", ST_FETCH, 0}, + { "-scan", ST_SCAN, 0}, + { "-nscan", ST_NSCAN, 0}, + { "-keysize", ST_KEYSIZE, 12}, + { "-valsize", ST_VALSIZE, 100}, + { "-trans", ST_TRANS, 0}, + { "-system", -1, 0}, + { "help", -2, 0}, + {0, 0, 0} + }; + int i; + int aParam[9]; + int rc = 0; + int bReadonly = 0; + int nContent = 0; + + TestDb *pDb; + Datasource *pData; + DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 0, 0, 0, 0 }; + char *zSystem = ""; + int bLsm = 1; + FILE *pLog = 0; + +#ifdef NDEBUG + /* If NDEBUG is defined, disable the dynamic memory related checks in + ** lsmtest_mem.c. They slow things down. */ + testMallocUninstall(tdb_lsm_env()); +#endif + + /* Initialize aParam[] with default values. */ + for(i=0; i<ArraySize(aOpt); i++){ + if( aOpt[i].zOpt ) aParam[aOpt[i].eVal] = aOpt[i].iDefault; + } + + /* Process the command line switches. */ + for(i=0; i<nArg; i+=2){ + int iSel; + rc = testArgSelect(aOpt, "switch", azArg[i], &iSel); + if( rc ){ + return rc; + } + if( aOpt[iSel].eVal==-2 ){ + print_speed_test_help(); + return 0; + } + if( i+1==nArg ){ + testPrintError("option %s requires an argument\n", aOpt[iSel].zOpt); + return 1; + } + if( aOpt[iSel].eVal>=0 ){ + aParam[aOpt[iSel].eVal] = atoi(azArg[i+1]); + }else{ + zSystem = azArg[i+1]; + bLsm = 0; +#if 0 + for(j=0; zSystem[j]; j++){ + if( zSystem[j]=='=' ) bLsm = 1; + } +#endif + } + } + + printf("#"); + for(i=0; i<ArraySize(aOpt); i++){ + if( aOpt[i].zOpt ){ + if( aOpt[i].eVal>=0 ){ + printf(" %s=%d", &aOpt[i].zOpt[1], aParam[aOpt[i].eVal]); + }else if( aOpt[i].eVal==-1 ){ + printf(" %s=\"%s\"", &aOpt[i].zOpt[1], zSystem); + } + } + } + printf("\n"); + + defn.nMinKey = defn.nMaxKey = aParam[ST_KEYSIZE]; + defn.nMinVal = defn.nMaxVal = aParam[ST_VALSIZE]; + pData = testDatasourceNew(&defn); + + if( aParam[ST_WRITE]==0 ){ + bReadonly = 1; + } + + if( bLsm ){ + rc = tdb_lsm_open(zSystem, "testdb.lsm", !bReadonly, &pDb); + }else{ + pDb = testOpen(zSystem, !bReadonly, &rc); + } + if( rc!=0 ) return rc; + if( bReadonly ){ + nContent = testCountDatabase(pDb); + } + +#if 0 + pLog = fopen("/tmp/speed.log", "w"); + tdb_lsm_write_hook(pDb, do_speed_write_hook2, (void *)pLog); +#endif + + for(i=0; i<aParam[ST_REPEAT] && rc==0; i++){ + int msWrite, msFetch; + int iFetch; + int nWrite = aParam[ST_WRITE]; + + if( bReadonly ){ + msWrite = 0; + }else{ + testTimeInit(); + + if( aParam[ST_TRANS] ) testBegin(pDb, 2, &rc); + testWriteDatasourceRange(pDb, pData, i*nWrite, nWrite, &rc); + if( aParam[ST_TRANS] ) testCommit(pDb, 0, &rc); + + msWrite = testTimeGet(); + nContent += nWrite; + } + + if( aParam[ST_PAUSE] ){ + if( aParam[ST_PAUSE]/1000 ) sleep(aParam[ST_PAUSE]/1000); + if( aParam[ST_PAUSE]%1000 ) usleep(1000 * (aParam[ST_PAUSE]%1000)); + } + + if( aParam[ST_FETCH] ){ + testTimeInit(); + if( aParam[ST_TRANS] ) testBegin(pDb, 1, &rc); + for(iFetch=0; iFetch<aParam[ST_FETCH]; iFetch++){ + int iKey = testPrngValue(i*nWrite+iFetch) % nContent; +#ifndef NDEBUG + testDatasourceFetch(pDb, pData, iKey, &rc); +#else + void *pKey; int nKey; /* Database key to query for */ + void *pVal; int nVal; /* Result of query */ + + testDatasourceEntry(pData, iKey, &pKey, &nKey, 0, 0); + rc = tdb_fetch(pDb, pKey, nKey, &pVal, &nVal); + if( rc==0 && nVal<0 ) rc = 1; + if( rc ) break; +#endif + } + if( aParam[ST_TRANS] ) testCommit(pDb, 0, &rc); + msFetch = testTimeGet(); + }else{ + msFetch = 0; + } + + if( i==(aParam[ST_REPEAT]-1) ){ + testTimeInit(); + testClose(&pDb); + msWrite += testTimeGet(); + } + + printf("%d %d %d\n", i, msWrite, msFetch); + fflush(stdout); + } + + testClose(&pDb); + testDatasourceFree(pData); + + if( pLog ){ + flushPrev(pLog); + fclose(pLog); + } + return rc; +} + +int do_speed_tests(int nArg, char **azArg){ + + struct DbSystem { + const char *zLibrary; + const char *zColor; + } aSys[] = { + { "sqlite3", "black" }, + { "leveldb", "blue" }, + { "lsm", "red" }, + { "lsm_mt2", "orange" }, + { "lsm_mt3", "purple" }, + { "kyotocabinet", "green" }, + {0, 0} + }; + + int i; + int j; + int rc; + int nSleep = 0; /* ms of rest allowed between INSERT tests */ + int nRow = 0; /* Number of rows to insert into database */ + int nStep; /* Measure INSERT time after this many rows */ + int nSelStep; /* Measure SELECT time after this many rows */ + int nSelTest; /* Number of SELECTs to run for timing */ + int doReadTest = 1; + int doWriteTest = 1; + + int *aTime; /* INSERT timing data */ + int *aWrite; /* Writes per nStep inserts */ + int *aSelTime; /* SELECT timing data */ + int isFirst = 1; + int bSleep = 0; + + /* File to write gnuplot script to. */ + const char *zOut = "lsmtest_speed.gnuplot"; + + u32 sys_mask = 0; + + testMallocUninstall(tdb_lsm_env()); + + for(i=0; i<nArg; i++){ + struct Opt { + const char *zOpt; + int isSwitch; + } aOpt[] = { + { "sqlite3" , 0}, + { "leveldb" , 0}, + { "lsm" , 0}, + { "lsm_mt2" , 0}, + { "lsm_mt3" , 0}, + { "kyotocabinet" , 0}, + { "-rows" , 1}, + { "-sleep" , 2}, + { "-testmode" , 3}, + { "-out" , 4}, + { 0, 0} + }; + int iSel; + + rc = testArgSelect(aOpt, "argument", azArg[i], &iSel); + if( rc ) return rc; + + if( aOpt[iSel].isSwitch ){ + i++; + + if( i>=nArg ){ + testPrintError("option %s requires an argument\n", aOpt[iSel].zOpt); + return 1; + } + if( aOpt[iSel].isSwitch==1 ){ + nRow = atoi(azArg[i]); + } + if( aOpt[iSel].isSwitch==2 ){ + nSleep = atoi(azArg[i]); + } + if( aOpt[iSel].isSwitch==3 ){ + struct Mode { + const char *zMode; + int doReadTest; + int doWriteTest; + } aMode[] = {{"ro", 1, 0} , {"rw", 1, 1}, {"wo", 0, 1}, {0, 0, 0}}; + int iMode; + rc = testArgSelect(aMode, "option", azArg[i], &iMode); + if( rc ) return rc; + doReadTest = aMode[iMode].doReadTest; + doWriteTest = aMode[iMode].doWriteTest; + } + if( aOpt[iSel].isSwitch==4 ){ + /* The "-out FILE" switch. This option is used to specify a file to + ** write the gnuplot script to. */ + zOut = azArg[i]; + } + }else{ + /* A db name */ + rc = testArgSelect(aOpt, "system", azArg[i], &iSel); + if( rc ) return rc; + sys_mask |= (1<<iSel); + } + } + + if( sys_mask==0 ) sys_mask = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3); + nRow = MAX(nRow, 100000); + nStep = nRow/100; + nSelStep = nRow/10; + nSelTest = (nSelStep > 100000) ? 100000 : nSelStep; + + aTime = malloc(sizeof(int) * ArraySize(aSys) * nRow/nStep); + aWrite = malloc(sizeof(int) * nRow/nStep); + aSelTime = malloc(sizeof(int) * ArraySize(aSys) * nRow/nSelStep); + + /* This loop collects the INSERT speed data. */ + if( doWriteTest ){ + printf("Writing output to file \"%s\".\n", zOut); + + for(j=0; aSys[j].zLibrary; j++){ + FILE *pLog = 0; + TestDb *pDb; /* Database being tested */ + lsm_db *pLsm; + int iDot = 0; + + if( ((1<<j)&sys_mask)==0 ) continue; + if( bSleep && nSleep ) sqlite3_sleep(nSleep); + bSleep = 1; + + testCaseBegin(&rc, 0, "speed.insert.%s", aSys[j].zLibrary); + + rc = tdb_open(aSys[j].zLibrary, 0, 1, &pDb); + if( rc ) return rc; + + pLsm = configure_lsm_db(pDb); +#if 0 + pLog = fopen("/tmp/speed.log", "w"); + tdb_lsm_write_hook(pDb, do_speed_write_hook2, (void *)pLog); +#endif + + testTimeInit(); + for(i=0; i<nRow; i+=nStep){ + int iStep; + int nWrite1 = 0, nWrite2 = 0; + testCaseProgress(i, nRow, testCaseNDot(), &iDot); + if( pLsm ) lsm_info(pLsm, LSM_INFO_NWRITE, &nWrite1); + for(iStep=0; iStep<nStep; iStep++){ + u32 aKey[4]; /* 16-byte key */ + u32 aVal[25]; /* 100 byte value */ + testPrngArray(i+iStep, aKey, ArraySize(aKey)); + testPrngArray(i+iStep, aVal, ArraySize(aVal)); + rc = tdb_write(pDb, aKey, sizeof(aKey), aVal, sizeof(aVal)); + } + aTime[(j*nRow+i)/nStep] = testTimeGet(); + if( pLsm ) lsm_info(pLsm, LSM_INFO_NWRITE, &nWrite2); + aWrite[i/nStep] = nWrite2 - nWrite1; + } + + tdb_close(pDb); + if( pLog ) fclose(pLog); + testCaseFinish(rc); + } + } + + /* This loop collects the SELECT speed data. */ + if( doReadTest ){ + for(j=0; aSys[j].zLibrary; j++){ + int iDot = 0; + TestDb *pDb; /* Database being tested */ + + if( ((1<<j)&sys_mask)==0 ) continue; + if( bSleep && nSleep ) sqlite3_sleep(nSleep); + bSleep = 1; + + testCaseBegin(&rc, 0, "speed.select.%s", aSys[j].zLibrary); + + if( doWriteTest ){ + rc = tdb_open(aSys[j].zLibrary, 0, 1, &pDb); + if( rc ) return rc; + configure_lsm_db(pDb); + + for(i=0; i<nRow; i+=nSelStep){ + int iStep; + int iSel; + testCaseProgress(i, nRow, testCaseNDot(), &iDot); + for(iStep=0; iStep<nSelStep; iStep++){ + u32 aKey[4]; /* 16-byte key */ + u32 aVal[25]; /* 100 byte value */ + testPrngArray(i+iStep, aKey, ArraySize(aKey)); + testPrngArray(i+iStep, aVal, ArraySize(aVal)); + rc = tdb_write(pDb, aKey, sizeof(aKey), aVal, sizeof(aVal)); + } + + testTimeInit(); + for(iSel=0; iSel<nSelTest; iSel++){ + void *pDummy; + int nDummy; + u32 iKey; + u32 aKey[4]; /* 16-byte key */ + + iKey = testPrngValue(iSel) % (i+nSelStep); + testPrngArray(iKey, aKey, ArraySize(aKey)); + rc = tdb_fetch(pDb, aKey, sizeof(aKey), &pDummy, &nDummy); + } + aSelTime[(j*nRow+i)/nSelStep] = testTimeGet(); + tdb_fetch(pDb, 0, 0, 0, 0); + } + }else{ + int t; + int iSel; + + rc = tdb_open(aSys[j].zLibrary, 0, 0, &pDb); + configure_lsm_db(pDb); + + testTimeInit(); + for(iSel=0; rc==LSM_OK && iSel<nSelTest; iSel++){ + void *pDummy; + int nDummy; + u32 iKey; + u32 aKey[4]; /* 16-byte key */ +#ifndef NDEBUG + u32 aVal[25]; /* 100 byte value */ +#endif + + testCaseProgress(iSel, nSelTest, testCaseNDot(), &iDot); + + iKey = testPrngValue(iSel) % nRow; + testPrngArray(iKey, aKey, ArraySize(aKey)); + rc = tdb_fetch(pDb, aKey, sizeof(aKey), &pDummy, &nDummy); + +#ifndef NDEBUG + testPrngArray(iKey, aVal, ArraySize(aVal)); + assert( nDummy==100 && memcmp(aVal, pDummy, 100)==0 ); +#endif + } + if( rc!=LSM_OK ) return rc; + + t = testTimeGet(); + tdb_fetch(pDb, 0, 0, 0, 0); + + printf("%s: %d selects/second\n", + aSys[j].zLibrary, (int)((double)nSelTest*1000.0/t) + ); + } + + tdb_close(pDb); + testCaseFinish(rc); + } + } + + + if( doWriteTest ){ + FILE *pOut = fopen(zOut, "w"); + if( !pOut ){ + printf("fopen(\"%s\", \"w\"): %s\n", zOut, strerror(errno)); + return 1; + } + + fprintf(pOut, "set xlabel \"Rows Inserted\"\n"); + fprintf(pOut, "set ylabel \"Inserts per second\"\n"); + if( doReadTest ){ + fprintf(pOut, "set y2label \"Selects per second\"\n"); + }else if( sys_mask==(1<<2) ){ + fprintf(pOut, "set y2label \"Page writes per insert\"\n"); + } + fprintf(pOut, "set yrange [0:*]\n"); + fprintf(pOut, "set y2range [0:*]\n"); + fprintf(pOut, "set xrange [%d:*]\n", MAX(nStep, nRow/20) ); + fprintf(pOut, "set ytics nomirror\n"); + fprintf(pOut, "set y2tics nomirror\n"); + fprintf(pOut, "set key box lw 0.01\n"); + fprintf(pOut, "plot "); + + for(j=0; aSys[j].zLibrary; j++){ + if( (1<<j)&sys_mask ){ + const char *zLib = aSys[j].zLibrary; + fprintf(pOut, "%s\"-\" ti \"%s INSERT\" with lines lc rgb \"%s\" ", + (isFirst?"":", "), zLib, aSys[j].zColor + ); + if( doReadTest ){ + fprintf(pOut, ", \"-\" ti \"%s SELECT\" " + "axis x1y2 with points lw 3 lc rgb \"%s\"" + , zLib, aSys[j].zColor + ); + } + isFirst = 0; + } + } + + assert( strcmp(aSys[2].zLibrary, "lsm")==0 ); + if( sys_mask==(1<<2) && !doReadTest ){ + fprintf(pOut, ", \"-\" ti \"lsm pages written\" " + "axis x1y2 with boxes lw 1 lc rgb \"grey\"" + ); + } + + fprintf(pOut, "\n"); + + for(j=0; aSys[j].zLibrary; j++){ + if( ((1<<j)&sys_mask)==0 ) continue; + fprintf(pOut, "# Rows Inserts per second\n"); + for(i=0; i<nRow; i+=nStep){ + int iTime = aTime[(j*nRow+i)/nStep]; + int ips = (int)((i+nStep)*1000.0 / (double)iTime); + fprintf(pOut, "%d %d\n", i+nStep, ips); + } + fprintf(pOut, "end\n"); + + if( doReadTest ){ + fprintf(pOut, "# Rows Selects per second\n"); + for(i=0; i<nRow; i+=nSelStep){ + int sps = (int)(nSelTest*1000.0/(double)aSelTime[(j*nRow+i)/nSelStep]); + fprintf(pOut, "%d %d\n", i+nSelStep, sps); + } + fprintf(pOut, "end\n"); + }else if( sys_mask==(1<<2) ){ + for(i=0; i<(nRow/nStep); i++){ + fprintf(pOut, "%d %f\n", i*nStep, (double)aWrite[i] / (double)nStep); + } + fprintf(pOut, "end\n"); + } + } + + fprintf(pOut, "pause -1\n"); + fclose(pOut); + } + + free(aTime); + free(aSelTime); + free(aWrite); + testMallocInstall(tdb_lsm_env()); + return 0; +} + +/* +** Usage: lsmtest random ?N? +** +** This command prints a sequence of zero or more numbers from the PRNG +** system to stdout. If the "N" argument is missing, values the first 10 +** values (i=0, i=1, ... i=9) are printed. Otherwise, the first N. +** +** This was added to verify that the PRNG values do not change between +** runs of the lsmtest program. +*/ +int do_random_tests(int nArg, char **azArg){ + int i; + int nRand; + if( nArg==0 ){ + nRand = 10; + }else if( nArg==1 ){ + nRand = atoi(azArg[0]); + }else{ + testPrintError("Usage: random ?N?\n"); + return -1; + } + for(i=0; i<nRand; i++){ + printf("0x%x\n", testPrngValue(i)); + } + return 0; +} + +static int testFormatSize(char *aBuf, int nBuf, i64 nByte){ + int res; + if( nByte<(1<<10) ){ + res = snprintf(aBuf, nBuf, "%d byte", (int)nByte); + }else if( nByte<(1<<20) ){ + res = snprintf(aBuf, nBuf, "%dK", (int)(nByte/(1<<10))); + }else{ + res = snprintf(aBuf, nBuf, "%dM", (int)(nByte/(1<<20))); + } + return res; +} + +static i64 testReadSize(char *z){ + int n = strlen(z); + char c = z[n-1]; + i64 nMul = 1; + + switch( c ){ + case 'g': case 'G': + nMul = (1<<30); + break; + + case 'm': case 'M': + nMul = (1<<20); + break; + + case 'k': case 'K': + nMul = (1<<10); + break; + + default: + nMul = 1; + } + + return nMul * (i64)atoi(z); +} + +/* +** Usage: lsmtest writespeed FILESIZE BLOCKSIZE SYNCSIZE +*/ +static int do_writer_test(int nArg, char **azArg){ + int nBlock; + int nSize; + int i; + int fd; + int ms; + char aFilesize[32]; + char aBlockSize[32]; + + char *aPage; + int *aOrder; + int nSync; + + i64 filesize; + i64 blocksize; + i64 syncsize; + int nPage = 4096; + + /* How long to sleep before running a trial (in ms). */ +#if 0 + const int nSleep = 10000; +#endif + const int nSleep = 0; + + if( nArg!=3 ){ + testPrintUsage("FILESIZE BLOCKSIZE SYNCSIZE"); + return -1; + } + + filesize = testReadSize(azArg[0]); + blocksize = testReadSize(azArg[1]); + syncsize = testReadSize(azArg[2]); + + nBlock = (int)(filesize / blocksize); + nSize = (int)blocksize; + nSync = (int)(syncsize / blocksize); + + aPage = (char *)malloc(4096); + aOrder = (int *)malloc(nBlock * sizeof(int)); + for(i=0; i<nBlock; i++) aOrder[i] = i; + for(i=0; i<(nBlock*25); i++){ + int tmp; + u32 a = testPrngValue(i); + u32 b = testPrngValue(a); + a = a % nBlock; + b = b % nBlock; + tmp = aOrder[a]; + aOrder[a] = aOrder[b]; + aOrder[b] = tmp; + } + + testFormatSize(aFilesize, sizeof(aFilesize), (i64)nBlock * (i64)nSize); + testFormatSize(aBlockSize, sizeof(aFilesize), nSize); + + printf("Testing writing a %s file using %s blocks. ", aFilesize, aBlockSize); + if( nSync==1 ){ + printf("Sync after each block.\n"); + }else{ + printf("Sync after each %d blocks.\n", nSync); + } + + printf("Preparing file... "); + fflush(stdout); + unlink("writer.out"); + fd = open("writer.out", O_RDWR|O_CREAT|_O_BINARY, 0664); + if( fd<0 ){ + testPrintError("open(): %d - %s\n", errno, strerror(errno)); + return -1; + } + testTimeInit(); + for(i=0; i<nBlock; i++){ + int iPg; + memset(aPage, i&0xFF, nPage); + for(iPg=0; iPg<(nSize/nPage); iPg++){ + write(fd, aPage, nPage); + } + } + fsync(fd); + printf("ok (%d ms)\n", testTimeGet()); + + for(i=0; i<5; i++){ + int j; + + sqlite3_sleep(nSleep); + printf("Now writing sequentially... "); + fflush(stdout); + + lseek(fd, 0, SEEK_SET); + testTimeInit(); + for(j=0; j<nBlock; j++){ + int iPg; + if( ((j+1)%nSync)==0 ) fdatasync(fd); + memset(aPage, j&0xFF, nPage); + for(iPg=0; iPg<(nSize/nPage); iPg++){ + write(fd, aPage, nPage); + } + } + fdatasync(fd); + ms = testTimeGet(); + printf("%d ms\n", ms); + sqlite3_sleep(nSleep); + printf("Now in an arbitrary order... "); + + fflush(stdout); + testTimeInit(); + for(j=0; j<nBlock; j++){ + int iPg; + if( ((j+1)%nSync)==0 ) fdatasync(fd); + lseek(fd, aOrder[j]*nSize, SEEK_SET); + memset(aPage, j&0xFF, nPage); + for(iPg=0; iPg<(nSize/nPage); iPg++){ + write(fd, aPage, nPage); + } + } + fdatasync(fd); + ms = testTimeGet(); + printf("%d ms\n", ms); + } + + close(fd); + free(aPage); + free(aOrder); + + return 0; +} + +static void do_insert_work_hook(lsm_db *db, void *p){ + char *z = 0; + lsm_info(db, LSM_INFO_DB_STRUCTURE, &z); + if( z ){ + printf("%s\n", z); + fflush(stdout); + lsm_free(lsm_get_env(db), z); + } + + unused_parameter(p); +} + +typedef struct InsertWriteHook InsertWriteHook; +struct InsertWriteHook { + FILE *pOut; + int bLog; + i64 iOff; + int nData; +}; + +static void flushHook(InsertWriteHook *pHook){ + if( pHook->nData ){ + fprintf(pHook->pOut, "write %s %d %d\n", + (pHook->bLog ? "log" : "db"), (int)pHook->iOff, pHook->nData + ); + pHook->nData = 0; + fflush(pHook->pOut); + } +} + +static void do_insert_write_hook( + void *pCtx, + int bLog, + i64 iOff, + int nData, + int nUs +){ + InsertWriteHook *pHook = (InsertWriteHook *)pCtx; + if( bLog ) return; + + if( nData==0 ){ + flushHook(pHook); + fprintf(pHook->pOut, "sync %s\n", (bLog ? "log" : "db")); + }else if( pHook->nData + && bLog==pHook->bLog + && iOff==(pHook->iOff+pHook->nData) + ){ + pHook->nData += nData; + }else{ + flushHook(pHook); + pHook->bLog = bLog; + pHook->iOff = iOff; + pHook->nData = nData; + } +} + +static int do_replay(int nArg, char **azArg){ + char aBuf[4096]; + FILE *pInput; + FILE *pClose = 0; + const char *zDb; + + lsm_env *pEnv; + lsm_file *pOut; + int rc; + + if( nArg!=2 ){ + testPrintError("Usage: replay WRITELOG FILE\n"); + return 1; + } + + if( strcmp(azArg[0], "-")==0 ){ + pInput = stdin; + }else{ + pClose = pInput = fopen(azArg[0], "r"); + } + zDb = azArg[1]; + pEnv = tdb_lsm_env(); + rc = pEnv->xOpen(pEnv, zDb, 0, &pOut); + if( rc!=LSM_OK ) return rc; + + while( feof(pInput)==0 ){ + char zLine[80]; + fgets(zLine, sizeof(zLine)-1, pInput); + zLine[sizeof(zLine)-1] = '\0'; + + if( 0==memcmp("sync db", zLine, 7) ){ + rc = pEnv->xSync(pOut); + if( rc!=0 ) break; + }else{ + int iOff; + int nData; + int nMatch; + nMatch = sscanf(zLine, "write db %d %d", &iOff, &nData); + if( nMatch==2 ){ + int i; + for(i=0; i<nData; i+=sizeof(aBuf)){ + memset(aBuf, i&0xFF, sizeof(aBuf)); + rc = pEnv->xWrite(pOut, iOff+i, aBuf, sizeof(aBuf)); + if( rc!=0 ) break; + } + } + } + } + if( pClose ) fclose(pClose); + pEnv->xClose(pOut); + + return rc; +} + +static int do_insert(int nArg, char **azArg){ + const char *zDb = "lsm"; + TestDb *pDb = 0; + int i; + int rc; + const int nRow = 1 * 1000 * 1000; + + DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 8, 15, 80, 150 }; + Datasource *pData = 0; + + if( nArg>1 ){ + testPrintError("Usage: insert ?DATABASE?\n"); + return 1; + } + if( nArg==1 ){ zDb = azArg[0]; } + + testMallocUninstall(tdb_lsm_env()); + for(i=0; zDb[i] && zDb[i]!='='; i++); + if( zDb[i] ){ + rc = tdb_lsm_open(zDb, "testdb.lsm", 1, &pDb); + }else{ + rc = tdb_open(zDb, 0, 1, &pDb); + } + + if( rc!=0 ){ + testPrintError("Error opening db \"%s\": %d\n", zDb, rc); + }else{ + InsertWriteHook hook; + memset(&hook, 0, sizeof(hook)); + hook.pOut = fopen("writelog.txt", "w"); + + pData = testDatasourceNew(&defn); + tdb_lsm_config_work_hook(pDb, do_insert_work_hook, 0); + tdb_lsm_write_hook(pDb, do_insert_write_hook, (void *)&hook); + + if( rc==0 ){ + for(i=0; i<nRow; i++){ + void *pKey; int nKey; /* Database key to insert */ + void *pVal; int nVal; /* Database value to insert */ + testDatasourceEntry(pData, i, &pKey, &nKey, &pVal, &nVal); + tdb_write(pDb, pKey, nKey, pVal, nVal); + } + } + + testDatasourceFree(pData); + tdb_close(pDb); + flushHook(&hook); + fclose(hook.pOut); + } + testMallocInstall(tdb_lsm_env()); + + return rc; +} + +static int st_do_show(int a, char **b) { return do_show(a, b); } +static int st_do_work(int a, char **b) { return do_work(a, b); } +static int st_do_io(int a, char **b) { return do_io(a, b); } + +#ifdef __linux__ +#include <sys/time.h> +#include <sys/resource.h> + +static void lsmtest_rusage_report(void){ + struct rusage r; + memset(&r, 0, sizeof(r)); + + getrusage(RUSAGE_SELF, &r); + printf("# getrusage: { ru_maxrss %d ru_oublock %d ru_inblock %d }\n", + (int)r.ru_maxrss, (int)r.ru_oublock, (int)r.ru_inblock + ); +} +#else +static void lsmtest_rusage_report(void){ + /* no-op */ +} +#endif + +int main(int argc, char **argv){ + struct TestFunc { + const char *zName; + int bRusageReport; + int (*xFunc)(int, char **); + } aTest[] = { + {"random", 1, do_random_tests}, + {"writespeed", 1, do_writer_test}, + {"io", 1, st_do_io}, + + {"insert", 1, do_insert}, + {"replay", 1, do_replay}, + + {"speed", 1, do_speed_tests}, + {"speed2", 1, do_speed_test2}, + {"show", 0, st_do_show}, + {"work", 1, st_do_work}, + {"test", 1, do_test}, + + {0, 0} + }; + int rc; /* Return Code */ + int iFunc; /* Index into aTest[] */ + + int nLeakAlloc = 0; /* Allocations leaked by lsm */ + int nLeakByte = 0; /* Bytes leaked by lsm */ + +#ifdef LSM_DEBUG_MEM + FILE *pReport = 0; /* lsm malloc() report file */ + const char *zReport = "malloc.txt generated"; +#else + const char *zReport = "malloc.txt NOT generated"; +#endif + + testMallocInstall(tdb_lsm_env()); + + if( argc<2 ){ + testPrintError("Usage: %s sub-command ?args...?\n", argv[0]); + return -1; + } + + /* Initialize error reporting */ + testErrorInit(argc, argv); + + /* Initialize PRNG system */ + testPrngInit(); + + rc = testArgSelect(aTest, "sub-command", argv[1], &iFunc); + if( rc==0 ){ + rc = aTest[iFunc].xFunc(argc-2, &argv[2]); + } + +#ifdef LSM_DEBUG_MEM + pReport = fopen("malloc.txt", "w"); + testMallocCheck(tdb_lsm_env(), &nLeakAlloc, &nLeakByte, pReport); + fclose(pReport); +#else + testMallocCheck(tdb_lsm_env(), &nLeakAlloc, &nLeakByte, 0); +#endif + + if( nLeakAlloc ){ + testPrintError("Leaked %d bytes in %d allocations (%s)\n", + nLeakByte, nLeakAlloc, zReport + ); + if( rc==0 ) rc = -1; + } + testMallocUninstall(tdb_lsm_env()); + + if( aTest[iFunc].bRusageReport ){ + lsmtest_rusage_report(); + } + return rc; +} |