mirror of
				https://github.com/tursodatabase/libsql.git
				synced 2025-10-31 14:36:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			249 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| 
 | |
| /*
 | |
| ** SUMMARY
 | |
| **
 | |
| **   This file implements the 'io' subcommand of the test program. It is used
 | |
| **   for testing the performance of various combinations of write() and fsync()
 | |
| **   system calls. All operations occur on a single file, which may or may not
 | |
| **   exist when a test is started.
 | |
| **
 | |
| **   A test consists of a series of commands. Each command is either a write
 | |
| **   or an fsync. A write is specified as "<amount>@<offset>", where <amount>
 | |
| **   is the amount of data written, and <offset> is the offset of the file
 | |
| **   to write to. An <amount> or an <offset> is specified as an integer number
 | |
| **   of bytes. Or, if postfixed with a "K", "M" or "G", an integer number of
 | |
| **   KB, MB or GB, respectively. An fsync is simply "S". All commands are
 | |
| **   case-insensitive.
 | |
| **
 | |
| **   Example test program:
 | |
| **
 | |
| **        2M@6M 1492K@4M S 4096@4K S
 | |
| **
 | |
| **   This program writes 2 MB of data starting at the offset 6MB offset of
 | |
| **   the file, followed by 1492 KB of data written at the 4MB offset of the
 | |
| **   file, followed by a call to fsync(), a write of 4KB of data at byte
 | |
| **   offset 4096, and finally another call to fsync().
 | |
| **
 | |
| **   Commands may either be specified on the command line (one command per
 | |
| **   command line argument) or read from stdin. Commands read from stdin
 | |
| **   must be separated by white-space.
 | |
| **
 | |
| ** COMMAND LINE INVOCATION
 | |
| **
 | |
| **   The sub-command implemented in this file must be invoked with at least
 | |
| **   two arguments - the path to the file to write to and the page-size to
 | |
| **   use for writing. If there are more than two arguments, then each
 | |
| **   subsequent argument is assumed to be a test command. If there are exactly
 | |
| **   two arguments, the test commands are read from stdin.
 | |
| **
 | |
| **   A write command does not result in a single call to system call write().
 | |
| **   Instead, the specified region is written sequentially using one or
 | |
| **   more calls to write(), each of which writes not more than one page of
 | |
| **   data. For example, if the page-size is 4KB, the command "2M@6M" results
 | |
| **   in 512 calls to write(), each of which writes 4KB of data.
 | |
| **
 | |
| ** EXAMPLES
 | |
| **
 | |
| **   Two equivalent examples:
 | |
| **
 | |
| **     $ lsmtest io testfile.db 4KB 2M@6M 1492K@4M S 4096@4K S
 | |
| **     3544K written in 129 ms
 | |
| **     $ echo "2M@6M 1492K@4M S 4096@4K S" | lsmtest io testfile.db 4096 
 | |
| **     3544K written in 127 ms
 | |
| **
 | |
| */
 | |
| 
 | |
| #include "lsmtest.h"
 | |
| 
 | |
| typedef struct IoContext IoContext;
 | |
| 
 | |
| struct IoContext {
 | |
|   int fd;
 | |
|   int nWrite;
 | |
| };
 | |
| 
 | |
| /*
 | |
| ** As isspace(3)
 | |
| */
 | |
| static int safe_isspace(char c){
 | |
|   if( c&0x80) return 0;
 | |
|   return isspace(c);
 | |
| }
 | |
| 
 | |
| /*
 | |
| ** As isdigit(3)
 | |
| */
 | |
| static int safe_isdigit(char c){
 | |
|   if( c&0x80) return 0;
 | |
|   return isdigit(c);
 | |
| }
 | |
| 
 | |
| static i64 getNextSize(char *zIn, char **pzOut, int *pRc){
 | |
|   i64 iRet = 0;
 | |
|   if( *pRc==0 ){
 | |
|     char *z = zIn;
 | |
| 
 | |
|     if( !safe_isdigit(*z) ){
 | |
|       *pRc = 1;
 | |
|       return 0;
 | |
|     }
 | |
| 
 | |
|     /* Process digits */
 | |
|     while( safe_isdigit(*z) ){
 | |
|       iRet = iRet*10 + (*z - '0');
 | |
|       z++;
 | |
|     }
 | |
| 
 | |
|     /* Process suffix */
 | |
|     switch( *z ){
 | |
|       case 'k': case 'K':
 | |
|         iRet = iRet * 1024;
 | |
|         z++;
 | |
|         break;
 | |
| 
 | |
|       case 'm': case 'M':
 | |
|         iRet = iRet * 1024 * 1024;
 | |
|         z++;
 | |
|         break;
 | |
| 
 | |
|       case 'g': case 'G':
 | |
|         iRet = iRet * 1024 * 1024 * 1024;
 | |
|         z++;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     if( pzOut ) *pzOut = z;
 | |
|   }
 | |
|   return iRet;
 | |
| }
 | |
| 
 | |
| static int doOneCmd(
 | |
|   IoContext *pCtx,
 | |
|   u8 *aData,
 | |
|   int pgsz,
 | |
|   char *zCmd,
 | |
|   char **pzOut
 | |
| ){
 | |
|   char c;
 | |
|   char *z = zCmd;
 | |
| 
 | |
|   while( safe_isspace(*z) ) z++;
 | |
|   c = *z;
 | |
| 
 | |
|   if( c==0 ){
 | |
|     if( pzOut ) *pzOut = z;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if( c=='s' || c=='S' ){
 | |
|     if( pzOut ) *pzOut = &z[1];
 | |
|     return fdatasync(pCtx->fd);
 | |
|   }
 | |
| 
 | |
|   if( safe_isdigit(c) ){
 | |
|     i64 iOff = 0;
 | |
|     int nByte = 0;
 | |
|     int rc = 0;
 | |
|     int nPg;
 | |
|     int iPg;
 | |
| 
 | |
|     nByte = (int)getNextSize(z, &z, &rc);
 | |
|     if( rc || *z!='@' ) goto bad_command;
 | |
|     z++;
 | |
|     iOff = getNextSize(z, &z, &rc);
 | |
|     if( rc || (safe_isspace(*z)==0 && *z!='\0') ) goto bad_command;
 | |
|     if( pzOut ) *pzOut = z;
 | |
| 
 | |
|     nPg = (nByte+pgsz-1) / pgsz;
 | |
|     lseek(pCtx->fd, (off_t)iOff, SEEK_SET);
 | |
|     for(iPg=0; iPg<nPg; iPg++){
 | |
|       write(pCtx->fd, aData, pgsz);
 | |
|     }
 | |
|     pCtx->nWrite += nByte/1024;
 | |
| 
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|  bad_command:
 | |
|   testPrintError("unrecognized command: %s", zCmd);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| static int readStdin(char **pzOut){
 | |
|   int nAlloc = 128;
 | |
|   char *zOut = 0;
 | |
|   int nOut = 0;
 | |
| 
 | |
|   while( !feof(stdin) ){
 | |
|     int nRead;
 | |
| 
 | |
|     nAlloc = nAlloc*2;
 | |
|     zOut = realloc(zOut, nAlloc);
 | |
|     nRead = fread(&zOut[nOut], 1, nAlloc-nOut-1, stdin);
 | |
| 
 | |
|     if( nRead==0 ) break;
 | |
|     nOut += nRead;
 | |
|     zOut[nOut] = '\0';
 | |
|   }
 | |
| 
 | |
|   *pzOut = zOut;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int do_io(int nArg, char **azArg){
 | |
|   IoContext ctx;
 | |
|   int pgsz;
 | |
|   char *zFile;
 | |
|   char *zPgsz;
 | |
|   int i;
 | |
|   int rc = 0;
 | |
| 
 | |
|   char *zStdin = 0;
 | |
|   char *z;
 | |
| 
 | |
|   u8 *aData;
 | |
| 
 | |
|   memset(&ctx, 0, sizeof(IoContext));
 | |
|   if( nArg<2 ){
 | |
|     testPrintUsage("FILE PGSZ ?CMD-1 ...?");
 | |
|     return -1;
 | |
|   }
 | |
|   zFile = azArg[0];
 | |
|   zPgsz = azArg[1];
 | |
| 
 | |
|   pgsz = (int)getNextSize(zPgsz, 0, &rc);
 | |
|   if( pgsz<=0 ){
 | |
|     testPrintError("Ridiculous page size: %d", pgsz);
 | |
|     return -1;
 | |
|   }
 | |
|   aData = malloc(pgsz);
 | |
|   memset(aData, 0x77, pgsz);
 | |
| 
 | |
|   ctx.fd = open(zFile, O_RDWR|O_CREAT|_O_BINARY, 0644);
 | |
|   if( ctx.fd<0 ){
 | |
|     perror("open: ");
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   if( nArg==2 ){
 | |
|     readStdin(&zStdin);
 | |
|     testTimeInit();
 | |
|     z = zStdin;
 | |
|     while( *z && rc==0 ){
 | |
|       rc = doOneCmd(&ctx, aData, pgsz, z, &z);
 | |
|     }
 | |
|   }else{
 | |
|     testTimeInit();
 | |
|     for(i=2; i<nArg; i++){
 | |
|       rc = doOneCmd(&ctx, aData, pgsz, azArg[i], 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   printf("%dK written in %d ms\n", ctx.nWrite, testTimeGet());
 | |
| 
 | |
|   free(zStdin);
 | |
|   close(ctx.fd);
 | |
| 
 | |
|   return 0;
 | |
| }
 |