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