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