1 
2 /*
3 ** SUMMARY
4 **
5 **   This file implements the 'io' subcommand of the test program. It is used
6 **   for testing the performance of various combinations of write() and fsync()
7 **   system calls. All operations occur on a single file, which may or may not
8 **   exist when a test is started.
9 **
10 **   A test consists of a series of commands. Each command is either a write
11 **   or an fsync. A write is specified as "<amount>@<offset>", where <amount>
12 **   is the amount of data written, and <offset> is the offset of the file
13 **   to write to. An <amount> or an <offset> is specified as an integer number
14 **   of bytes. Or, if postfixed with a "K", "M" or "G", an integer number of
15 **   KB, MB or GB, respectively. An fsync is simply "S". All commands are
16 **   case-insensitive.
17 **
18 **   Example test program:
19 **
20 **        2M@6M 1492K@4M S 4096@4K S
21 **
22 **   This program writes 2 MB of data starting at the offset 6MB offset of
23 **   the file, followed by 1492 KB of data written at the 4MB offset of the
24 **   file, followed by a call to fsync(), a write of 4KB of data at byte
25 **   offset 4096, and finally another call to fsync().
26 **
27 **   Commands may either be specified on the command line (one command per
28 **   command line argument) or read from stdin. Commands read from stdin
29 **   must be separated by white-space.
30 **
31 ** COMMAND LINE INVOCATION
32 **
33 **   The sub-command implemented in this file must be invoked with at least
34 **   two arguments - the path to the file to write to and the page-size to
35 **   use for writing. If there are more than two arguments, then each
36 **   subsequent argument is assumed to be a test command. If there are exactly
37 **   two arguments, the test commands are read from stdin.
38 **
39 **   A write command does not result in a single call to system call write().
40 **   Instead, the specified region is written sequentially using one or
41 **   more calls to write(), each of which writes not more than one page of
42 **   data. For example, if the page-size is 4KB, the command "2M@6M" results
43 **   in 512 calls to write(), each of which writes 4KB of data.
44 **
45 ** EXAMPLES
46 **
47 **   Two equivalent examples:
48 **
49 **     $ lsmtest io testfile.db 4KB 2M@6M 1492K@4M S 4096@4K S
50 **     3544K written in 129 ms
51 **     $ echo "2M@6M 1492K@4M S 4096@4K S" | lsmtest io testfile.db 4096
52 **     3544K written in 127 ms
53 **
54 */
55 
56 #include "lsmtest.h"
57 
58 typedef struct IoContext IoContext;
59 
60 struct IoContext {
61   int fd;
62   int nWrite;
63 };
64 
65 /*
66 ** As isspace(3)
67 */
safe_isspace(char c)68 static int safe_isspace(char c){
69   if( c&0x80) return 0;
70   return isspace(c);
71 }
72 
73 /*
74 ** As isdigit(3)
75 */
safe_isdigit(char c)76 static int safe_isdigit(char c){
77   if( c&0x80) return 0;
78   return isdigit(c);
79 }
80 
getNextSize(char * zIn,char ** pzOut,int * pRc)81 static i64 getNextSize(char *zIn, char **pzOut, int *pRc){
82   i64 iRet = 0;
83   if( *pRc==0 ){
84     char *z = zIn;
85 
86     if( !safe_isdigit(*z) ){
87       *pRc = 1;
88       return 0;
89     }
90 
91     /* Process digits */
92     while( safe_isdigit(*z) ){
93       iRet = iRet*10 + (*z - '0');
94       z++;
95     }
96 
97     /* Process suffix */
98     switch( *z ){
99       case 'k': case 'K':
100         iRet = iRet * 1024;
101         z++;
102         break;
103 
104       case 'm': case 'M':
105         iRet = iRet * 1024 * 1024;
106         z++;
107         break;
108 
109       case 'g': case 'G':
110         iRet = iRet * 1024 * 1024 * 1024;
111         z++;
112         break;
113     }
114 
115     if( pzOut ) *pzOut = z;
116   }
117   return iRet;
118 }
119 
doOneCmd(IoContext * pCtx,u8 * aData,int pgsz,char * zCmd,char ** pzOut)120 static int doOneCmd(
121   IoContext *pCtx,
122   u8 *aData,
123   int pgsz,
124   char *zCmd,
125   char **pzOut
126 ){
127   char c;
128   char *z = zCmd;
129 
130   while( safe_isspace(*z) ) z++;
131   c = *z;
132 
133   if( c==0 ){
134     if( pzOut ) *pzOut = z;
135     return 0;
136   }
137 
138   if( c=='s' || c=='S' ){
139     if( pzOut ) *pzOut = &z[1];
140     return fdatasync(pCtx->fd);
141   }
142 
143   if( safe_isdigit(c) ){
144     i64 iOff = 0;
145     int nByte = 0;
146     int rc = 0;
147     int nPg;
148     int iPg;
149 
150     nByte = (int)getNextSize(z, &z, &rc);
151     if( rc || *z!='@' ) goto bad_command;
152     z++;
153     iOff = getNextSize(z, &z, &rc);
154     if( rc || (safe_isspace(*z)==0 && *z!='\0') ) goto bad_command;
155     if( pzOut ) *pzOut = z;
156 
157     nPg = (nByte+pgsz-1) / pgsz;
158     lseek(pCtx->fd, (off_t)iOff, SEEK_SET);
159     for(iPg=0; iPg<nPg; iPg++){
160       write(pCtx->fd, aData, pgsz);
161     }
162     pCtx->nWrite += nByte/1024;
163 
164     return 0;
165   }
166 
167  bad_command:
168   testPrintError("unrecognized command: %s", zCmd);
169   return 1;
170 }
171 
readStdin(char ** pzOut)172 static int readStdin(char **pzOut){
173   int nAlloc = 128;
174   char *zOut = 0;
175   int nOut = 0;
176 
177   while( !feof(stdin) ){
178     int nRead;
179 
180     nAlloc = nAlloc*2;
181     zOut = realloc(zOut, nAlloc);
182     nRead = fread(&zOut[nOut], 1, nAlloc-nOut-1, stdin);
183 
184     if( nRead==0 ) break;
185     nOut += nRead;
186     zOut[nOut] = '\0';
187   }
188 
189   *pzOut = zOut;
190   return 0;
191 }
192 
do_io(int nArg,char ** azArg)193 int do_io(int nArg, char **azArg){
194   IoContext ctx;
195   int pgsz;
196   char *zFile;
197   char *zPgsz;
198   int i;
199   int rc = 0;
200 
201   char *zStdin = 0;
202   char *z;
203 
204   u8 *aData;
205 
206   memset(&ctx, 0, sizeof(IoContext));
207   if( nArg<2 ){
208     testPrintUsage("FILE PGSZ ?CMD-1 ...?");
209     return -1;
210   }
211   zFile = azArg[0];
212   zPgsz = azArg[1];
213 
214   pgsz = (int)getNextSize(zPgsz, 0, &rc);
215   if( pgsz<=0 ){
216     testPrintError("Ridiculous page size: %d", pgsz);
217     return -1;
218   }
219   aData = malloc(pgsz);
220   memset(aData, 0x77, pgsz);
221 
222   ctx.fd = open(zFile, O_RDWR|O_CREAT|_O_BINARY, 0644);
223   if( ctx.fd<0 ){
224     perror("open: ");
225     return -1;
226   }
227 
228   if( nArg==2 ){
229     readStdin(&zStdin);
230     testTimeInit();
231     z = zStdin;
232     while( *z && rc==0 ){
233       rc = doOneCmd(&ctx, aData, pgsz, z, &z);
234     }
235   }else{
236     testTimeInit();
237     for(i=2; i<nArg; i++){
238       rc = doOneCmd(&ctx, aData, pgsz, azArg[i], 0);
239     }
240   }
241 
242   printf("%dK written in %d ms\n", ctx.nWrite, testTimeGet());
243 
244   free(zStdin);
245   close(ctx.fd);
246 
247   return 0;
248 }
249