|  | 
 | /* | 
 | ** 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; | 
 | } |