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