add nfs client library and the dbench nfsio backend
[tridge/dbench.git] / dbench.c
1 /* 
2    Copyright (C) by Andrew Tridgell <tridge@samba.org> 1999-2007
3    Copyright (C) 2001 by Martin Pool <mbp@samba.org>
4    
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9    
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14    
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /* TODO: We could try allowing for different flavours of synchronous
20    operation: data sync and so on.  Linux apparently doesn't make any
21    distinction, however, and for practical purposes it probably
22    doesn't matter.  On NFSv4 it might be interesting, since the client
23    can choose what kind it wants for each OPEN operation. */
24
25 #include "dbench.h"
26 #include "popt.h"
27 #include <sys/sem.h>
28
29 struct options options = {
30         .timelimit           = 600,
31         .loadfile            = DATADIR "/client.txt",
32         .directory           = ".",
33         .tcp_options         = TCP_OPTIONS,
34         .nprocs              = 10,
35         .sync_open           = 0,
36         .sync_dirs           = 0,
37         .do_fsync            = 0,
38         .fsync_frequency     = 0,
39         .warmup              = -1,
40         .targetrate          = 0.0,
41         .ea_enable           = 0,
42         .clients_per_process = 1,
43         .server              = "localhost",
44 };
45
46 static struct timeval tv_start;
47 static struct timeval tv_end;
48 static int barrier=-1;
49 static double throughput;
50
51 static FILE *open_loadfile(void)
52 {
53         FILE            *f;
54
55         if ((f = fopen(options.loadfile, "rt")) != NULL)
56                 return f;
57
58         fprintf(stderr,
59                 "dbench: error opening '%s': %s\n", options.loadfile,
60                 strerror(errno));
61
62         return NULL;
63 }
64
65
66 static struct child_struct *children;
67
68 static void sem_cleanup() {
69         if (!(barrier==-1)) 
70                 semctl(barrier,0,IPC_RMID);
71 }
72
73 static void sig_alarm(int sig)
74 {
75         double total_bytes = 0;
76         int total_lines = 0;
77         int i;
78         int nclients = options.nprocs * options.clients_per_process;
79         int in_warmup = 0;
80         double t;
81         static int in_cleanup;
82         double latency;
83         struct timeval tnow;
84         int num_active = 0;
85         int num_finished = 0;
86         (void)sig;
87
88         tnow = timeval_current();
89
90         for (i=0;i<nclients;i++) {
91                 total_bytes += children[i].bytes - children[i].bytes_done_warmup;
92                 if (children[i].bytes == 0) {
93                         in_warmup = 1;
94                 } else {
95                         num_active++;
96                 }
97                 total_lines += children[i].line;
98                 if (children[i].cleanup_finished) {
99                         num_finished++;
100                 }
101         }
102
103         t = timeval_elapsed(&tv_start);
104
105         if (!in_warmup && options.warmup>0 && t > options.warmup) {
106                 tv_start = tnow;
107                 options.warmup = 0;
108                 for (i=0;i<nclients;i++) {
109                         children[i].bytes_done_warmup = children[i].bytes;
110                         children[i].worst_latency = 0;
111                         memset(&children[i].op, 0, sizeof(children[i].op));
112                 }
113                 goto next;
114         }
115         if (t < options.warmup) {
116                 in_warmup = 1;
117         } else if (!in_warmup && !in_cleanup && t > options.timelimit) {
118                 for (i=0;i<nclients;i++) {
119                         children[i].done = 1;
120                 }
121                 tv_end = tnow;
122                 in_cleanup = 1;
123         }
124         if (t < 1) {
125                 goto next;
126         }
127
128         latency = 0;
129         if (!in_cleanup) {
130                 for (i=0;i<nclients;i++) {
131                         latency = MAX(children[i].max_latency, latency);
132                         latency = MAX(latency, timeval_elapsed2(&children[i].lasttime, &tnow));
133                         children[i].max_latency = 0;
134                         if (latency > children[i].worst_latency) {
135                                 children[i].worst_latency = latency;
136                         }
137                 }
138         }
139
140         if (in_warmup) {
141                 printf("%4d  %8d  %7.2f MB/sec  warmup %3.0f sec  latency %.03f ms\n", 
142                        num_active, total_lines/nclients, 
143                        1.0e-6 * total_bytes / t, t, latency*1000);
144         } else if (in_cleanup) {
145                 printf("%4d  cleanup %3.0f sec\n", nclients - num_finished, t);
146         } else {
147                 printf("%4d  %8d  %7.2f MB/sec  execute %3.0f sec  latency %.03f ms\n", 
148                        nclients, total_lines/nclients, 
149                        1.0e-6 * total_bytes / t, t, latency*1000);
150                 throughput = 1.0e-6 * total_bytes / t;
151         }
152
153         fflush(stdout);
154 next:
155         signal(SIGALRM, sig_alarm);
156         alarm(PRINT_FREQ);
157 }
158
159
160 static const struct {
161         const char *name;
162         size_t offset;
163 } op_names[] = {
164 #define OP_NAME(opname) { #opname, offsetof(struct opnames, op_ ## opname) }
165         OP_NAME(NTCreateX),
166         OP_NAME(Close),
167         OP_NAME(Rename),
168         OP_NAME(Unlink),
169         OP_NAME(Deltree),
170         OP_NAME(Rmdir),
171         OP_NAME(Mkdir),
172         OP_NAME(Qpathinfo),
173         OP_NAME(Qfileinfo),
174         OP_NAME(Qfsinfo),
175         OP_NAME(Sfileinfo),
176         OP_NAME(Find),
177         OP_NAME(WriteX),
178         OP_NAME(ReadX),
179         OP_NAME(LockX),
180         OP_NAME(UnlockX),
181         OP_NAME(Flush),
182 };
183
184 static void show_one_latency(struct opnames *ops, struct opnames *ops_all)
185 {
186         int i, n = (sizeof(op_names)/sizeof(op_names[0]));
187         printf(" Operation      Count    AvgLat    MaxLat\n");
188         printf(" ----------------------------------------\n");
189         for (i=0;i<n;i++) {
190                 struct op *op1, *op_all;
191                 op1    = (struct op *)(op_names[i].offset + (char *)ops);
192                 op_all = (struct op *)(op_names[i].offset + (char *)ops_all);
193                 if (op_all->count == 0) continue;
194                 printf(" %-12s %7u %9.03f %9.03f\n",
195                        op_names[i].name, op1->count, 
196                        1000*op1->total_time/op1->count,
197                        op1->max_latency*1000);
198         }
199         printf("\n");
200 }
201
202 static void report_latencies(void)
203 {
204         struct opnames sum;
205         int i, j, n = (sizeof(op_names)/sizeof(op_names[0]));
206         struct op *op1, *op2;
207         struct child_struct *child;
208
209         memset(&sum, 0, sizeof(sum));
210         for (i=0;i<n;i++) {
211                 op1 = (struct op *)(op_names[i].offset + (char *)&sum);
212                 for (j=0;j<options.nprocs * options.clients_per_process;j++) {
213                         child = &children[j];
214                         op2 = (struct op *)(op_names[i].offset + (char *)&child->op);
215                         op1->count += op2->count;
216                         op1->total_time += op2->total_time;
217                         op1->max_latency = MAX(op1->max_latency, op2->max_latency);
218                 }
219         }
220         show_one_latency(&sum, &sum);
221
222         if (!options.per_client_results) {
223                 return;
224         }
225
226         printf("Per client results:\n");
227         for (i=0;i<options.nprocs * options.clients_per_process;i++) {
228                 child = &children[i];
229                 printf("Client %u did %u lines and %.0f bytes\n", 
230                        i, child->line, child->bytes - child->bytes_done_warmup);
231                 show_one_latency(&child->op, &sum);             
232         }
233 }
234
235 /* this creates the specified number of child processes and runs fn()
236    in all of them */
237 static void create_procs(int nprocs, void (*fn)(struct child_struct *, const char *))
238 {
239         int nclients = nprocs * options.clients_per_process;
240         int i, status;
241         int synccount;
242         struct timeval tv;
243         FILE *load;
244         struct sembuf sbuf;
245         double t;
246
247         load = open_loadfile();
248         if (load == NULL) {
249                 exit(1);
250         }
251
252         if (nprocs < 1) {
253                 fprintf(stderr,
254                         "create %d procs?  you must be kidding.\n",
255                         nprocs);
256                 return;
257         }
258
259         children = shm_setup(sizeof(struct child_struct)*nclients);
260         if (!children) {
261                 printf("Failed to setup shared memory\n");
262                 return;
263         }
264
265         memset(children, 0, sizeof(*children)*nclients);
266
267         for (i=0;i<nclients;i++) {
268                 children[i].id = i;
269                 children[i].cleanup = 0;
270                 children[i].directory = options.directory;
271                 children[i].starttime = timeval_current();
272                 children[i].lasttime = timeval_current();
273         }
274
275         if (atexit(sem_cleanup) != 0) {
276                 printf("can't register cleanup function on exit\n");
277                 exit(1);
278         }
279         sbuf.sem_num =  0;
280         if ( !(barrier = semget(IPC_PRIVATE,1,IPC_CREAT | S_IRUSR | S_IWUSR)) ) {
281                 printf("failed to create barrier semaphore \n");
282         }
283         sbuf.sem_flg =  SEM_UNDO;
284         sbuf.sem_op  =  1;
285         if (semop(barrier, &sbuf, 1) == -1) {
286                 printf("failed to initialize the barrier semaphore\n");
287                 exit(1);
288         }
289         sbuf.sem_flg =  0;
290
291         for (i=0;i<nprocs;i++) {
292                 if (fork() == 0) {
293                         int j;
294
295                         setlinebuf(stdout);
296
297                         for (j=0;j<options.clients_per_process;j++) {
298                                 nb_setup(&children[i*options.clients_per_process + j]);
299                         }
300
301                         sbuf.sem_op = 0;
302                         if (semop(barrier, &sbuf, 1) == -1) {
303                                 printf("failed to use the barrier semaphore in child %d\n",getpid());
304                                 exit(1);
305                         }
306
307                         semctl(barrier,0,IPC_RMID);
308
309                         fn(&children[i*options.clients_per_process], options.loadfile);
310                         _exit(0);
311                 }
312         }
313
314         synccount = 0;
315         tv = timeval_current();
316         do {
317                 synccount = semctl(barrier,0,GETZCNT);
318                 t = timeval_elapsed(&tv);
319                 printf("%d of %d processes prepared for launch %3.0f sec\n", synccount, nprocs, t);
320                 if (synccount == nprocs) break;
321                 usleep(100*1000);
322         } while (timeval_elapsed(&tv) < 30);
323
324         if (synccount != nprocs) {
325                 printf("FAILED TO START %d CLIENTS (started %d)\n", nprocs, synccount);
326                 return;
327         }
328
329         printf("releasing clients\n");
330         tv_start = timeval_current();
331         sbuf.sem_op  =  -1;
332         if (semop(barrier, &sbuf, 1) == -1) {
333                 printf("failed to release barrier\n");
334                 exit(1);
335         }
336
337         semctl(barrier,0,IPC_RMID);
338
339         signal(SIGALRM, sig_alarm);
340         alarm(PRINT_FREQ);
341
342         for (i=0;i<nprocs;) {
343                 if (waitpid(0, &status, 0) == -1) continue;
344                 if (WEXITSTATUS(status) != 0) {
345                         printf("Child failed with status %d\n",
346                                WEXITSTATUS(status));
347                         exit(1);
348                 }
349                 i++;
350         }
351
352         alarm(0);
353         sig_alarm(SIGALRM);
354
355         printf("\n");
356
357         report_latencies();
358 }
359
360
361 static void show_usage(void)
362 {
363         printf("usage: dbench [OPTIONS] nprocs\n" \
364                "usage: tbench [OPTIONS] nprocs <server>\n" \
365                "options:\n" \
366                "  -v               show version\n" \
367                "  -t timelimit     run time in seconds (default 600)\n" \
368                "  -D directory     base directory to run in\n" \
369                "  -c loadfile      set location of the loadfile\n" \
370                "  -R               target rate (MByte/sec)\n" \
371                "  -s               synchronous file IO\n" \
372                "  -F               fsync on write\n" \
373                "  -S               synchronous directories (mkdir, unlink...)\n" \
374                "  -x               enable EA support\n" \
375                "  -T options       set socket options for tbench\n");
376         exit(1);
377 }
378
379
380
381 static int process_opts(int argc, const char **argv)
382 {
383         const char **extra_argv;
384         int extra_argc = 0;
385         struct poptOption popt_options[] = {
386                 POPT_AUTOHELP
387                 { "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0, 
388                   "timelimit", "integer" },
389                 { "loadfile",  'c', POPT_ARG_STRING, &options.loadfile, 0, 
390                   "loadfile", "filename" },
391                 { "directory", 'D', POPT_ARG_STRING, &options.directory, 0, 
392                   "working directory", NULL },
393                 { "tcp-options", 'T', POPT_ARG_STRING, &options.tcp_options, 0, 
394                   "TCP socket options", NULL },
395                 { "target-rate", 'R', POPT_ARG_DOUBLE, &options.targetrate, 0, 
396                   "target throughput (MB/sec)", NULL },
397                 { "sync", 's', POPT_ARG_NONE, &options.sync_open, 0, 
398                   "use O_SYNC", NULL },
399                 { "sync-dir", 'S', POPT_ARG_NONE, &options.sync_dirs, 0, 
400                   "sync directory changes", NULL },
401                 { "fsync", 'F', POPT_ARG_NONE, &options.do_fsync, 0, 
402                   "fsync on write", NULL },
403                 { "xattr", 'x', POPT_ARG_NONE, &options.ea_enable, 0, 
404                   "use xattrs", NULL },
405                 { "no-resolve", 0, POPT_ARG_NONE, &options.no_resolve, 0, 
406                   "disable name resolution simulation", NULL },
407                 { "clients-per-process", 0, POPT_ARG_INT, &options.clients_per_process, 0, 
408                   "number of clients per process", NULL },
409                 { "one-byte-write-fix", 0, POPT_ARG_NONE, &options.one_byte_write_fix, 0, 
410                   "try to fix 1 byte writes", NULL },
411                 { "stat-check", 0, POPT_ARG_NONE, &options.stat_check, 0, 
412                   "check for pointless calls with stat", NULL },
413                 { "fake-io", 0, POPT_ARG_NONE, &options.fake_io, 0, 
414                   "fake up read/write calls", NULL },
415                 { "skip-cleanup", 0, POPT_ARG_NONE, &options.skip_cleanup, 0, 
416                   "skip cleanup operations", NULL },
417                 { "per-client-results", 0, POPT_ARG_NONE, &options.per_client_results, 0, 
418                   "show results per client", NULL },
419                 POPT_TABLEEND
420         };
421         poptContext pc;
422         int opt;
423
424         pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST);
425
426         while ((opt = poptGetNextOpt(pc)) != -1) {
427                 switch (opt) {
428                 default:
429                         fprintf(stderr, "Invalid option %s: %s\n", 
430                                 poptBadOption(pc, 0), poptStrerror(opt));
431                         exit(1);
432                 }
433         }
434
435         /* setup the remaining options for the main program to use */
436         extra_argv = poptGetArgs(pc);
437         if (extra_argv) {
438                 extra_argv++;
439                 while (extra_argv[extra_argc]) extra_argc++;
440         }
441
442         if (extra_argc < 1) {
443                 printf("You need to specify NPROCS\n");
444                 poptPrintHelp(pc, stdout, 0);
445                 exit(1);
446         }
447
448 #ifndef HAVE_EA_SUPPORT
449         if (options.ea_enable) {
450                 printf("EA suppport not compiled in\n");
451                 exit(1);
452         }
453 #endif
454         
455         options.nprocs = atoi(extra_argv[0]);
456
457         if (extra_argc >= 2) {
458                 options.server = extra_argv[1];
459         }
460
461         return 1;
462 }
463
464
465
466  int main(int argc, const char *argv[])
467 {
468         double total_bytes = 0;
469         double t, latency=0;
470         int i;
471
472         setlinebuf(stdout);
473
474         printf("dbench version %s - Copyright Andrew Tridgell 1999-2004\n\n", VERSION);
475
476         if (!process_opts(argc, argv))
477                 show_usage();
478
479         if (options.warmup == -1) {
480                 options.warmup = options.timelimit / 5;
481         }
482
483         printf("Running for %d seconds with load '%s' and minimum warmup %d secs\n", 
484                options.timelimit, options.loadfile, options.warmup);
485
486         create_procs(options.nprocs, child_run);
487
488         for (i=0;i<options.nprocs*options.clients_per_process;i++) {
489                 total_bytes += children[i].bytes - children[i].bytes_done_warmup;
490                 latency = MAX(latency, children[i].worst_latency);
491         }
492
493         t = timeval_elapsed2(&tv_start, &tv_end);
494
495         printf("Throughput %g MB/sec%s%s  %d clients  %d procs  max_latency=%.03f ms\n", 
496                throughput,
497                options.sync_open ? " (sync open)" : "",
498                options.sync_dirs ? " (sync dirs)" : "", 
499                options.nprocs*options.clients_per_process,
500                options.nprocs, latency*1000);
501         return 0;
502 }