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