blob: 9b9a0c3d4020e559e36013039916647ef992cbc1 [file] [log] [blame]
drhcac028b2016-12-29 03:57:43 +00001/*
2** 2016-12-28
3**
4** The author disclaims copyright to this source code. In place of
5** a legal notice, here is a blessing:
6**
7** May you do good and not evil.
8** May you find forgiveness for yourself and forgive others.
9** May you share freely, never taking more than you give.
10**
11*************************************************************************
12**
13** This file implements "key-value" performance test for SQLite. The
14** purpose is to compare the speed of SQLite for accessing large BLOBs
15** versus reading those same BLOB values out of individual files in the
16** filesystem.
17**
18** Run "kvtest --help" for further information, or see comments below.
19*/
20static const char zHelp[] =
21"Usage: kvhelp COMMAND ARGS...\n"
22"\n"
23" kvhelp init DBFILE --count N --size M --pagesize X\n"
24"\n"
25" Generate a new test database file named DBFILE containing N\n"
26" BLOBs each of size M bytes. The page size of the new database\n"
27" file will be X\n"
28"\n"
29" kvhelp export DBFILE DIRECTORY\n"
30"\n"
31" Export all the blobs in the kv table of DBFILE into separate\n"
32" files in DIRECTORY.\n"
33"\n"
34" kvhelp run DBFILE [options]\n"
35"\n"
36" Run a performance test. DBFILE can be either the name of a\n"
37" database or a directory containing sample files. Options:\n"
38"\n"
39" --count N Read N blobs\n"
40" --blob-api Use the BLOB API\n"
41" --random Read blobs in a random order\n"
42" --desc Read blobs in descending order\n"
43" --max-id N Maximum blob key to use\n"
44" --start N Start reading with this blob key\n"
45;
46
47/* Reference resources used */
48#include <stdio.h>
49#include <stdlib.h>
50#include <sys/types.h>
51#include <sys/stat.h>
52#include <unistd.h>
53#include <assert.h>
54#include <string.h>
55#include "sqlite3.h"
56
57/*
58** Show thqe help text and quit.
59*/
60static void showHelp(void){
61 fprintf(stdout, "%s", zHelp);
62 exit(1);
63}
64
65/*
66** Show an error message an quit.
67*/
68static void fatalError(const char *zFormat, ...){
69 va_list ap;
70 fprintf(stdout, "ERROR: ");
71 va_start(ap, zFormat);
72 vfprintf(stdout, zFormat, ap);
73 va_end(ap);
74 fprintf(stdout, "\n");
75 exit(1);
76}
77
78/*
79** Check the filesystem object zPath. Determine what it is:
80**
81** PATH_DIR A directory
82** PATH_DB An SQLite database
83** PATH_NEXIST Does not exist
84** PATH_OTHER Something else
85*/
86#define PATH_DIR 1
87#define PATH_DB 2
88#define PATH_NEXIST 0
89#define PATH_OTHER 99
90static int pathType(const char *zPath){
91 struct stat x;
92 int rc;
93 if( access(zPath,R_OK) ) return PATH_NEXIST;
94 memset(&x, 0, sizeof(x));
95 rc = stat(zPath, &x);
96 if( rc<0 ) return PATH_OTHER;
97 if( S_ISDIR(x.st_mode) ) return PATH_DIR;
98 if( (x.st_size%512)==0 ) return PATH_DB;
99 return PATH_OTHER;
100}
101
102/*
103** A Pseudo-random number generator with a fixed seed. Use this so
104** that the same sequence of "random" numbers are generated on each
105** run, for repeatability.
106*/
107static unsigned int randInt(void){
108 static unsigned int x = 0x333a13cd;
109 static unsigned int y = 0xecb2adea;
110 x = (x>>1) ^ ((1+~(x&1)) & 0xd0000001);
111 y = y*1103515245 + 12345;
112 return x^y;
113}
114
115/*
116** Do database initialization.
117*/
118static int initMain(int argc, char **argv){
119 char *zDb;
120 int i, rc;
121 int nCount = 1000;
122 int sz = 10000;
123 int pgsz = 4096;
124 sqlite3 *db;
125 char *zSql;
126 char *zErrMsg = 0;
127
128 assert( strcmp(argv[1],"init")==0 );
129 assert( argc>=3 );
130 zDb = argv[2];
131 for(i=3; i<argc; i++){
132 char *z = argv[i];
133 if( z[0]!='-' ) fatalError("unknown argument: \"%s\"", z);
134 if( z[1]=='-' ) z++;
135 if( strcmp(z, "-count")==0 ){
136 if( i==argc-1 ) fatalError("missing argument on \"%s\"", argv[i]);
137 nCount = atoi(argv[++i]);
138 if( nCount<1 ) fatalError("the --count must be positive");
139 continue;
140 }
141 if( strcmp(z, "-size")==0 ){
142 if( i==argc-1 ) fatalError("missing argument on \"%s\"", argv[i]);
143 sz = atoi(argv[++i]);
144 if( sz<1 ) fatalError("the --size must be positive");
145 continue;
146 }
147 if( strcmp(z, "-pagesize")==0 ){
148 if( i==argc-1 ) fatalError("missing argument on \"%s\"", argv[i]);
149 pgsz = atoi(argv[++i]);
150 if( pgsz<512 || pgsz>65536 || ((pgsz-1)&pgsz)!=0 ){
151 fatalError("the --pagesize must be power of 2 between 512 and 65536");
152 }
153 continue;
154 }
155 fatalError("unknown option: \"%s\"", argv[i]);
156 }
157 rc = sqlite3_open(zDb, &db);
158 if( rc ){
159 fatalError("cannot open database \"%s\": %s", zDb, sqlite3_errmsg(db));
160 }
161 zSql = sqlite3_mprintf(
162 "DROP TABLE IF EXISTS kv;\n"
163 "PRAGMA page_size=%d;\n"
164 "VACUUM;\n"
165 "BEGIN;\n"
166 "CREATE TABLE kv(k INTEGER PRIMARY KEY, v BLOB);\n"
167 "WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<%d)"
168 " INSERT INTO kv(k,v) SELECT x, randomblob(%d) FROM c;\n"
169 "COMMIT;\n",
170 pgsz, nCount, sz
171 );
172 rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg);
173 if( rc ) fatalError("database create failed: %s", zErrMsg);
174 sqlite3_free(zSql);
175 sqlite3_close(db);
176 return 0;
177}
178
179/*
180** Implementation of the "writefile(X,Y)" SQL function. The argument Y
181** is written into file X. The number of bytes written is returned. Or
182** NULL is returned if something goes wrong, such as being unable to open
183** file X for writing.
184*/
185static void writefileFunc(
186 sqlite3_context *context,
187 int argc,
188 sqlite3_value **argv
189){
190 FILE *out;
191 const char *z;
192 sqlite3_int64 rc;
193 const char *zFile;
194
195 zFile = (const char*)sqlite3_value_text(argv[0]);
196 if( zFile==0 ) return;
197 out = fopen(zFile, "wb");
198 if( out==0 ) return;
199 z = (const char*)sqlite3_value_blob(argv[1]);
200 if( z==0 ){
201 rc = 0;
202 }else{
203 rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out);
204 }
205 fclose(out);
206 sqlite3_result_int64(context, rc);
207}
208
209/*
210** Export the kv table to individual files in the filesystem
211*/
212static int exportMain(int argc, char **argv){
213 char *zDb;
214 char *zDir;
215 sqlite3 *db;
216 char *zSql;
217 int rc;
218 char *zErrMsg = 0;
219
220 assert( strcmp(argv[1],"export")==0 );
221 assert( argc>=3 );
222 zDb = argv[2];
223 if( argc!=4 ) fatalError("Usage: kvtest export DATABASE DIRECTORY");
224 zDir = argv[3];
225 if( pathType(zDir)!=PATH_DIR ){
226 fatalError("object \"%s\" is not a directory", zDir);
227 }
228 rc = sqlite3_open(zDb, &db);
229 if( rc ){
230 fatalError("cannot open database \"%s\": %s", zDb, sqlite3_errmsg(db));
231 }
232 sqlite3_create_function(db, "writefile", 2, SQLITE_UTF8, 0,
233 writefileFunc, 0, 0);
234 zSql = sqlite3_mprintf(
235 "SELECT writefile(printf('%s/%%06d',k),v) FROM kv;",
236 zDir
237 );
238 rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg);
239 if( rc ) fatalError("database create failed: %s", zErrMsg);
240 sqlite3_free(zSql);
241 sqlite3_close(db);
242 return 0;
243}
244
245/*
246** Read the content of file zName into memory obtained from sqlite3_malloc64()
247** and return a pointer to the buffer. The caller is responsible for freeing
248** the memory.
249**
250** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
251** read.
252**
253** For convenience, a nul-terminator byte is always appended to the data read
254** from the file before the buffer is returned. This byte is not included in
255** the final value of (*pnByte), if applicable.
256**
257** NULL is returned if any error is encountered. The final value of *pnByte
258** is undefined in this case.
259*/
260static unsigned char *readFile(const char *zName, int *pnByte){
261 FILE *in = fopen(zName, "rb");
262 long nIn;
263 size_t nRead;
264 unsigned char *pBuf;
265 if( in==0 ) return 0;
266 fseek(in, 0, SEEK_END);
267 nIn = ftell(in);
268 rewind(in);
269 pBuf = sqlite3_malloc64( nIn+1 );
270 if( pBuf==0 ) return 0;
271 nRead = fread(pBuf, nIn, 1, in);
272 fclose(in);
273 if( nRead!=1 ){
274 sqlite3_free(pBuf);
275 return 0;
276 }
277 pBuf[nIn] = 0;
278 if( pnByte ) *pnByte = nIn;
279 return pBuf;
280}
281
282/*
283** Return the current time in milliseconds since the beginning of
284** the Julian epoch.
285*/
286static sqlite3_int64 timeOfDay(void){
287 static sqlite3_vfs *clockVfs = 0;
288 sqlite3_int64 t;
289 if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0);
290 if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){
291 clockVfs->xCurrentTimeInt64(clockVfs, &t);
292 }else{
293 double r;
294 clockVfs->xCurrentTime(clockVfs, &r);
295 t = (sqlite3_int64)(r*86400000.0);
296 }
297 return t;
298}
299
300/* Blob access order */
301#define ORDER_ASC 1
302#define ORDER_DESC 2
303#define ORDER_RANDOM 3
304
305/*
306** Run a performance test
307*/
308static int runMain(int argc, char **argv){
309 int eType; /* Is zDb a database or a directory? */
310 char *zDb; /* Database or directory name */
311 int i; /* Loop counter */
312 int rc; /* Return code from SQLite calls */
313 int nCount = 1000; /* Number of blob fetch operations */
314 int nExtra = 0; /* Extra cycles */
315 int iKey = 1; /* Next blob key */
316 int iMax = 1000; /* Largest allowed key */
317 int bBlobApi = 0; /* Use the incremental blob I/O API */
318 int eOrder = ORDER_ASC; /* Access order */
319 sqlite3 *db = 0; /* Database connection */
320 sqlite3_stmt *pStmt = 0; /* Prepared statement for SQL access */
321 sqlite3_blob *pBlob = 0; /* Handle for incremental Blob I/O */
322 sqlite3_int64 tmStart; /* Start time */
323 sqlite3_int64 tmElapsed; /* Elapsed time */
324 int nData = 0; /* Bytes of data */
325 sqlite3_int64 nTotal = 0; /* Total data read */
326 unsigned char *pData; /* Content of the blob */
327
328
329 assert( strcmp(argv[1],"run")==0 );
330 assert( argc>=3 );
331 zDb = argv[2];
332 eType = pathType(zDb);
333 if( eType==PATH_OTHER ) fatalError("unknown object type: \"%s\"", zDb);
334 if( eType==PATH_NEXIST ) fatalError("object does not exist: \"%s\"", zDb);
335 for(i=3; i<argc; i++){
336 char *z = argv[i];
337 if( z[0]!='-' ) fatalError("unknown argument: \"%s\"", z);
338 if( z[1]=='-' ) z++;
339 if( strcmp(z, "-count")==0 ){
340 if( i==argc-1 ) fatalError("missing argument on \"%s\"", argv[i]);
341 nCount = atoi(argv[++i]);
342 if( nCount<1 ) fatalError("the --count must be positive");
343 continue;
344 }
345 if( strcmp(z, "-max-id")==0 ){
346 if( i==argc-1 ) fatalError("missing argument on \"%s\"", argv[i]);
347 iMax = atoi(argv[++i]);
348 if( iMax<1 ) fatalError("the --max-id must be positive");
349 continue;
350 }
351 if( strcmp(z, "-start")==0 ){
352 if( i==argc-1 ) fatalError("missing argument on \"%s\"", argv[i]);
353 iKey = atoi(argv[++i]);
354 if( iKey<1 ) fatalError("the --start must be positive");
355 continue;
356 }
357 if( strcmp(z, "-random")==0 ){
358 eOrder = ORDER_RANDOM;
359 continue;
360 }
361 if( strcmp(z, "-asc")==0 ){
362 eOrder = ORDER_ASC;
363 continue;
364 }
365 if( strcmp(z, "-desc")==0 ){
366 eOrder = ORDER_DESC;
367 continue;
368 }
369 if( strcmp(z, "-blob-api")==0 ){
370 bBlobApi = 1;
371 continue;
372 }
373 fatalError("unknown option: \"%s\"", argv[i]);
374 }
375 tmStart = timeOfDay();
376 if( eType==PATH_DB ){
377 rc = sqlite3_open(zDb, &db);
378 if( rc ){
379 fatalError("cannot open database \"%s\": %s", zDb, sqlite3_errmsg(db));
380 }
381 sqlite3_exec(db, "BEGIN", 0, 0, 0);
382 }
383 for(i=0; i<nCount; i++){
384 if( eType==PATH_DIR ){
385 /* CASE 1: Reading blobs out of separate files */
386 char *zKey;
387 zKey = sqlite3_mprintf("%s/%06d", zDb, iKey);
388 nData = 0;
389 pData = readFile(zKey, &nData);
390 sqlite3_free(zKey);
391 sqlite3_free(pData);
392 }else if( bBlobApi ){
393 /* CASE 2: Reading from database using the incremental BLOB I/O API */
394 if( pBlob==0 ){
395 rc = sqlite3_blob_open(db, "main", "kv", "v", iKey, 0, &pBlob);
396 if( rc ){
397 fatalError("could not open sqlite3_blob handle: %s",
398 sqlite3_errmsg(db));
399 }
400 }else{
401 rc = sqlite3_blob_reopen(pBlob, iKey);
402 }
403 if( rc==SQLITE_OK ){
404 nData = sqlite3_blob_bytes(pBlob);
405 pData = sqlite3_malloc( nData+1 );
406 if( pData==0 ) fatalError("cannot allocate %d bytes", nData+1);
407 rc = sqlite3_blob_read(pBlob, pData, nData, 0);
408 if( rc!=SQLITE_OK ){
409 fatalError("could not read the blob at %d: %s", iKey,
410 sqlite3_errmsg(db));
411 }
412 sqlite3_free(pData);
413 }
414 }else{
415 /* CASE 3: Reading from database using SQL */
416 if( pStmt==0 ){
417 rc = sqlite3_prepare_v2(db,
418 "SELECT v FROM kv WHERE k=?1", -1, &pStmt, 0);
419 if( rc ){
420 fatalError("cannot prepare query: %s", sqlite3_errmsg(db));
421 }
422 }else{
423 sqlite3_reset(pStmt);
424 }
425 sqlite3_bind_int(pStmt, 1, iKey);
426 rc = sqlite3_step(pStmt);
427 if( rc==SQLITE_ROW ){
428 nData = sqlite3_column_bytes(pStmt, 0);
429 pData = (unsigned char*)sqlite3_column_blob(pStmt, 0);
430 }else{
431 nData = 0;
432 }
433 }
434 if( eOrder==ORDER_ASC ){
435 iKey++;
436 if( iKey>iMax ) iKey = 1;
437 }else if( eOrder==ORDER_DESC ){
438 iKey--;
439 if( iKey<=0 ) iKey = iMax;
440 }else{
441 iKey = (randInt()%iMax)+1;
442 }
443 nTotal += nData;
444 if( nData==0 ){ nCount++; nExtra++; }
445 }
446 if( pStmt ) sqlite3_finalize(pStmt);
447 if( pBlob ) sqlite3_blob_close(pBlob);
448 if( db ) sqlite3_close(db);
449 tmElapsed = timeOfDay() - tmStart;
450 if( nExtra ){
451 printf("%d cycles due to %d misses\n", nCount, nExtra);
452 }
453 printf("Total elapsed time: %.3f\n", tmElapsed/1000.0);
454 printf("Microseconds per BLOB read: %.3f\n", tmElapsed*1000.0/nCount);
455 printf("Content read rate: %.1f MB/s\n", nTotal/(1000.0*tmElapsed));
456 return 0;
457}
458
459
460int main(int argc, char **argv){
461 if( argc<3 ) showHelp();
462 if( strcmp(argv[1],"init")==0 ){
463 return initMain(argc, argv);
464 }
465 if( strcmp(argv[1],"export")==0 ){
466 return exportMain(argc, argv);
467 }
468 if( strcmp(argv[1],"run")==0 ){
469 return runMain(argc, argv);
470 }
471 showHelp();
472 return 0;
473}