summaryrefslogtreecommitdiffstats
path: root/ext/lsm1/lsm-test/lsmtest_main.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:28:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:28:19 +0000
commit18657a960e125336f704ea058e25c27bd3900dcb (patch)
tree17b438b680ed45a996d7b59951e6aa34023783f2 /ext/lsm1/lsm-test/lsmtest_main.c
parentInitial commit. (diff)
downloadsqlite3-18657a960e125336f704ea058e25c27bd3900dcb.tar.xz
sqlite3-18657a960e125336f704ea058e25c27bd3900dcb.zip
Adding upstream version 3.40.1.upstream/3.40.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ext/lsm1/lsm-test/lsmtest_main.c')
-rw-r--r--ext/lsm1/lsm-test/lsmtest_main.c1548
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;
+}