#include "lsmtest.h" #include 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; i0 && 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; icksum1 += ((int)aKey[i] << (i&0x0F)); p->cksum2 += p->cksum1; } for(i=0; icksum1 += ((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=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( nOrig1 ){ 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=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=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=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< 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<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; ixWrite(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 #include 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; }