/* ** 2016-12-28 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This file implements "key-value" performance test for SQLite. The ** purpose is to compare the speed of SQLite for accessing large BLOBs ** versus reading those same BLOB values out of individual files in the ** filesystem. ** ** Run "kvtest" with no arguments for on-line help, or see comments below. ** ** HOW TO COMPILE: ** ** (1) Gather this source file and a recent SQLite3 amalgamation with its ** header into the working directory. You should have: ** ** kvtest.c >--- this file ** sqlite3.c \___ SQLite ** sqlite3.h / amlagamation & header ** ** (2) Run you compiler against the two C source code files. ** ** (a) On linux or mac: ** ** OPTS="-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION" ** gcc -Os -I. $OPTS kvtest.c sqlite3.c -o kvtest ** ** The $OPTS options can be omitted. The $OPTS merely omit ** the need to link against -ldl and -lpthread, or whatever ** the equivalent libraries are called on your system. ** ** (b) Windows with MSVC: ** ** cl -I. kvtest.c sqlite3.c ** ** USAGE: ** ** (1) Create a test database by running "kvtest init" with appropriate ** options. See the help message for available options. ** ** (2) Construct the corresponding pile-of-files database on disk using ** the "kvtest export" command. ** ** (3) Run tests using "kvtest run" against either the SQLite database or ** the pile-of-files database and with appropriate options. ** ** For example: ** ** ./kvtest init x1.db --count 100000 --size 10000 ** mkdir x1 ** ./kvtest export x1.db x1 ** ./kvtest run x1.db --count 10000 --max-id 1000000 ** ./kvtest run x1 --count 10000 --max-id 1000000 */ static const char zHelp[] = "Usage: kvtest COMMAND ARGS...\n" "\n" " kvtest init DBFILE --count N --size M --pagesize X\n" "\n" " Generate a new test database file named DBFILE containing N\n" " BLOBs each of size M bytes. The page size of the new database\n" " file will be X. Additional options:\n" "\n" " --variance V Randomly vary M by plus or minus V\n" "\n" " kvtest export DBFILE DIRECTORY [--tree]\n" "\n" " Export all the blobs in the kv table of DBFILE into separate\n" " files in DIRECTORY. DIRECTORY is created if it does not previously\n" " exist. If the --tree option is used, then the blobs are written\n" " into a hierarchy of directories, using names like 00/00/00,\n" " 00/00/01, 00/00/02, and so forth. Without the --tree option, all\n" " files are in the top-level directory with names like 000000, 000001,\n" " 000002, and so forth.\n" "\n" " kvtest stat DBFILE [options]\n" "\n" " Display summary information about DBFILE. Options:\n" "\n" " --vacuum Run VACUUM on the database file\n" "\n" " kvtest run DBFILE [options]\n" "\n" " Run a performance test. DBFILE can be either the name of a\n" " database or a directory containing sample files. Options:\n" "\n" " --asc Read blobs in ascending order\n" " --blob-api Use the BLOB API\n" " --cache-size N Database cache size\n" " --count N Read N blobs\n" " --desc Read blobs in descending order\n" " --fsync Synchronous file writes\n" " --integrity-check Run \"PRAGMA integrity_check\" after test\n" " --max-id N Maximum blob key to use\n" " --mmap N Mmap as much as N bytes of DBFILE\n" " --multitrans Each read or write in its own transaction\n" " --nocheckpoint Omit the checkpoint on WAL mode writes\n" " --nosync Set \"PRAGMA synchronous=OFF\"\n" " --jmode MODE Set MODE journal mode prior to starting\n" " --random Read blobs in a random order\n" " --start N Start reading with this blob key\n" " --stats Output operating stats before exiting\n" " --update Do an overwrite test\n" ; /* Reference resources used */ #include #include #include #include #include #include #include "sqlite3.h" #ifndef _WIN32 # include #else /* Provide Windows equivalent for the needed parts of unistd.h */ # include # include # define R_OK 2 # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) # define access _access #endif #if !defined(_MSC_VER) # include #endif /* ** The following macros are used to cast pointers to integers and ** integers to pointers. The way you do this varies from one compiler ** to the next, so we have developed the following set of #if statements ** to generate appropriate macros for a wide range of compilers. ** ** The correct "ANSI" way to do this is to use the intptr_t type. ** Unfortunately, that typedef is not available on all compilers, or ** if it is available, it requires an #include of specific headers ** that vary from one machine to the next. ** ** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on ** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)). ** So we have to define the macros in different ways depending on the ** compiler. */ #if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ # define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) # define SQLITE_PTR_TO_INT(X) ((sqlite3_int64)(__PTRDIFF_TYPE__)(X)) #else # define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) # define SQLITE_PTR_TO_INT(X) ((sqlite3_int64)(intptr_t)(X)) #endif /* ** Show thqe help text and quit. */ static void showHelp(void){ fprintf(stdout, "%s", zHelp); exit(1); } /* ** Show an error message an quit. */ static void fatalError(const char *zFormat, ...){ va_list ap; fprintf(stdout, "ERROR: "); va_start(ap, zFormat); vfprintf(stdout, zFormat, ap); va_end(ap); fprintf(stdout, "\n"); exit(1); } /* ** Return the value of a hexadecimal digit. Return -1 if the input ** is not a hex digit. */ static int hexDigitValue(char c){ if( c>='0' && c<='9' ) return c - '0'; if( c>='a' && c<='f' ) return c - 'a' + 10; if( c>='A' && c<='F' ) return c - 'A' + 10; return -1; } /* ** Interpret zArg as an integer value, possibly with suffixes. */ static int integerValue(const char *zArg){ int v = 0; static const struct { char *zSuffix; int iMult; } aMult[] = { { "KiB", 1024 }, { "MiB", 1024*1024 }, { "GiB", 1024*1024*1024 }, { "KB", 1000 }, { "MB", 1000000 }, { "GB", 1000000000 }, { "K", 1000 }, { "M", 1000000 }, { "G", 1000000000 }, }; int i; int isNeg = 0; if( zArg[0]=='-' ){ isNeg = 1; zArg++; }else if( zArg[0]=='+' ){ zArg++; } if( zArg[0]=='0' && zArg[1]=='x' ){ int x; zArg += 2; while( (x = hexDigitValue(zArg[0]))>=0 ){ v = (v<<4) + x; zArg++; } }else{ while( zArg[0]>='0' && zArg[0]<='9' ){ v = v*10 + zArg[0] - '0'; zArg++; } } for(i=0; i>1) ^ ((1+~(x&1)) & 0xd0000001); y = y*1103515245 + 12345; return x^y; } /* ** Do database initialization. */ static int initMain(int argc, char **argv){ char *zDb; int i, rc; int nCount = 1000; int sz = 10000; int iVariance = 0; int pgsz = 4096; sqlite3 *db; char *zSql; char *zErrMsg = 0; assert( strcmp(argv[1],"init")==0 ); assert( argc>=3 ); zDb = argv[2]; for(i=3; i65536 || ((pgsz-1)&pgsz)!=0 ){ fatalError("the --pagesize must be power of 2 between 512 and 65536"); } continue; } fatalError("unknown option: \"%s\"", argv[i]); } rc = sqlite3_open(zDb, &db); if( rc ){ fatalError("cannot open database \"%s\": %s", zDb, sqlite3_errmsg(db)); } zSql = sqlite3_mprintf( "DROP TABLE IF EXISTS kv;\n" "PRAGMA page_size=%d;\n" "VACUUM;\n" "BEGIN;\n" "CREATE TABLE kv(k INTEGER PRIMARY KEY, v BLOB);\n" "WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<%d)" " INSERT INTO kv(k,v) SELECT x, randomblob(%d+(random()%%(%d))) FROM c;\n" "COMMIT;\n", pgsz, nCount, sz, iVariance+1 ); rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg); if( rc ) fatalError("database create failed: %s", zErrMsg); sqlite3_free(zSql); sqlite3_close(db); return 0; } /* ** Analyze an existing database file. Report its content. */ static int statMain(int argc, char **argv){ char *zDb; int i, rc; sqlite3 *db; char *zSql; sqlite3_stmt *pStmt; int doVacuum = 0; assert( strcmp(argv[1],"stat")==0 ); assert( argc>=3 ); zDb = argv[2]; for(i=3; i=3 ); if( argc<4 ) fatalError("Usage: kvtest export DATABASE DIRECTORY [OPTIONS]"); zDb = argv[2]; zDir = argv[3]; kvtest_mkdir(zDir); for(i=4; iiVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ clockVfs->xCurrentTimeInt64(clockVfs, &t); }else{ double r; clockVfs->xCurrentTime(clockVfs, &r); t = (sqlite3_int64)(r*86400000.0); } return t; } #ifdef __linux__ /* ** Attempt to display I/O stats on Linux using /proc/PID/io */ static void displayLinuxIoStats(FILE *out){ FILE *in; char z[200]; sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid()); in = fopen(z, "rb"); if( in==0 ) return; while( fgets(z, sizeof(z), in)!=0 ){ static const struct { const char *zPattern; const char *zDesc; } aTrans[] = { { "rchar: ", "Bytes received by read():" }, { "wchar: ", "Bytes sent to write():" }, { "syscr: ", "Read() system calls:" }, { "syscw: ", "Write() system calls:" }, { "read_bytes: ", "Bytes read from storage:" }, { "write_bytes: ", "Bytes written to storage:" }, { "cancelled_write_bytes: ", "Cancelled write bytes:" }, }; int i; for(i=0; i=3 ); zDb = argv[2]; eType = pathType(zDb); if( eType==PATH_OTHER ) fatalError("unknown object type: \"%s\"", zDb); if( eType==PATH_NEXIST ) fatalError("object does not exist: \"%s\"", zDb); for(i=3; iiMax ) iKey = 1; }else if( eOrder==ORDER_DESC ){ iKey--; if( iKey<=0 ) iKey = iMax; }else{ iKey = (randInt()%iMax)+1; } nTotal += nData; if( nData==0 ){ nCount++; nExtra++; } } if( nAlloc ) sqlite3_free(pData); if( pStmt ) sqlite3_finalize(pStmt); if( pBlob ) sqlite3_blob_close(pBlob); if( bStats ){ display_stats(db, 0); } if( db ){ if( !doMultiTrans ) sqlite3_exec(db, "COMMIT", 0, 0, 0); if( !noCheckpoint ){ sqlite3_close(db); db = 0; } } tmElapsed = timeOfDay() - tmStart; if( db && noCheckpoint ){ sqlite3_close(db); db = 0; } if( nExtra ){ printf("%d cycles due to %d misses\n", nCount, nExtra); } if( eType==PATH_DB ){ printf("SQLite version: %s\n", sqlite3_libversion()); if( doIntegrityCk ){ sqlite3_open(zDb, &db); sqlite3_prepare_v2(db, "PRAGMA integrity_check", -1, &pStmt, 0); while( sqlite3_step(pStmt)==SQLITE_ROW ){ printf("integrity-check: %s\n", sqlite3_column_text(pStmt, 0)); } sqlite3_finalize(pStmt); sqlite3_close(db); db = 0; } } printf("--count %d --max-id %d", nCount-nExtra, iMax); switch( eOrder ){ case ORDER_RANDOM: printf(" --random\n"); break; case ORDER_DESC: printf(" --desc\n"); break; default: printf(" --asc\n"); break; } if( eType==PATH_DB ){ printf("--cache-size %d --jmode %s\n", iCache, zJMode); printf("--mmap %d%s\n", mmapSize, bBlobApi ? " --blob-api" : ""); if( noSync ) printf("--nosync\n"); } if( iPagesize ) printf("Database page size: %d\n", iPagesize); printf("Total elapsed time: %.3f\n", tmElapsed/1000.0); if( isUpdateTest ){ printf("Microseconds per BLOB write: %.3f\n", tmElapsed*1000.0/nCount); printf("Content write rate: %.1f MB/s\n", nTotal/(1000.0*tmElapsed)); }else{ printf("Microseconds per BLOB read: %.3f\n", tmElapsed*1000.0/nCount); printf("Content read rate: %.1f MB/s\n", nTotal/(1000.0*tmElapsed)); } return 0; } int main(int argc, char **argv){ if( argc<3 ) showHelp(); if( strcmp(argv[1],"init")==0 ){ return initMain(argc, argv); } if( strcmp(argv[1],"export")==0 ){ return exportMain(argc, argv); } if( strcmp(argv[1],"run")==0 ){ return runMain(argc, argv); } if( strcmp(argv[1],"stat")==0 ){ return statMain(argc, argv); } showHelp(); return 0; }