| |
| #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; |
| } |