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