0095f03b2a129fdfa539a114d46f4c4d8313b243
[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 report_latencies(void)
185 {
186         struct opnames sum;
187         int i, j, n = (sizeof(op_names)/sizeof(op_names[0]));
188         struct op *op1, *op2;
189         struct child_struct *child;
190
191         memset(&sum, 0, sizeof(sum));
192         for (i=0;i<n;i++) {
193                 op1 = (struct op *)(op_names[i].offset + (char *)&sum);
194                 for (j=0;j<options.nprocs * options.clients_per_process;j++) {
195                         child = &children[j];
196                         op2 = (struct op *)(op_names[i].offset + (char *)&child->op);
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         printf(" Operation      Count    AvgLat    MaxLat\n");
203         printf(" ----------------------------------------\n");
204         for (i=0;i<n;i++) {
205                 op1 = (struct op *)(op_names[i].offset + (char *)&sum);
206                 if (op1->count == 0) continue;
207                 printf(" %-12s %7u %9.03f %9.03f\n",
208                        op_names[i].name, op1->count, 
209                        1000*op1->total_time/op1->count,
210                        op1->max_latency*1000);
211         }
212         printf("\n");
213 }
214
215 /* this creates the specified number of child processes and runs fn()
216    in all of them */
217 static void create_procs(int nprocs, void (*fn)(struct child_struct *, const char *))
218 {
219         int nclients = nprocs * options.clients_per_process;
220         int i, status;
221         int synccount;
222         struct timeval tv;
223         FILE *load;
224         struct sembuf sbuf;
225         double t;
226
227         load = open_loadfile();
228         if (load == NULL) {
229                 exit(1);
230         }
231
232         if (nprocs < 1) {
233                 fprintf(stderr,
234                         "create %d procs?  you must be kidding.\n",
235                         nprocs);
236                 return;
237         }
238
239         children = shm_setup(sizeof(struct child_struct)*nclients);
240         if (!children) {
241                 printf("Failed to setup shared memory\n");
242                 return;
243         }
244
245         memset(children, 0, sizeof(*children)*nclients);
246
247         for (i=0;i<nclients;i++) {
248                 children[i].id = i;
249                 children[i].cleanup = 0;
250                 children[i].directory = options.directory;
251                 children[i].starttime = timeval_current();
252                 children[i].lasttime = timeval_current();
253         }
254
255         if (atexit(sem_cleanup) != 0) {
256                 printf("can't register cleanup function on exit\n");
257                 exit(1);
258         }
259         sbuf.sem_num =  0;
260         if ( !(barrier = semget(IPC_PRIVATE,1,IPC_CREAT | S_IRUSR | S_IWUSR)) ) {
261                 printf("failed to create barrier semaphore \n");
262         }
263         sbuf.sem_flg =  SEM_UNDO;
264         sbuf.sem_op  =  1;
265         if (semop(barrier, &sbuf, 1) == -1) {
266                 printf("failed to initialize the barrier semaphore\n");
267                 exit(1);
268         }
269         sbuf.sem_flg =  0;
270
271         for (i=0;i<nprocs;i++) {
272                 if (fork() == 0) {
273                         int j;
274
275                         setlinebuf(stdout);
276
277                         for (j=0;j<options.clients_per_process;j++) {
278                                 nb_setup(&children[i*options.clients_per_process + j]);
279                         }
280
281                         sbuf.sem_op = 0;
282                         if (semop(barrier, &sbuf, 1) == -1) {
283                                 printf("failed to use the barrier semaphore in child %d\n",getpid());
284                                 exit(1);
285                         }
286
287                         semctl(barrier,0,IPC_RMID);
288
289                         fn(&children[i*options.clients_per_process], options.loadfile);
290                         _exit(0);
291                 }
292         }
293
294         synccount = 0;
295         tv = timeval_current();
296         do {
297                 synccount = semctl(barrier,0,GETZCNT);
298                 t = timeval_elapsed(&tv);
299                 printf("%d of %d processes prepared for launch %3.0f sec\n", synccount, nprocs, t);
300                 if (synccount == nprocs) break;
301                 usleep(100*1000);
302         } while (timeval_elapsed(&tv) < 30);
303
304         if (synccount != nprocs) {
305                 printf("FAILED TO START %d CLIENTS (started %d)\n", nprocs, synccount);
306                 return;
307         }
308
309         printf("releasing clients\n");
310         tv_start = timeval_current();
311         sbuf.sem_op  =  -1;
312         if (semop(barrier, &sbuf, 1) == -1) {
313                 printf("failed to release barrier\n");
314                 exit(1);
315         }
316
317         semctl(barrier,0,IPC_RMID);
318
319         signal(SIGALRM, sig_alarm);
320         alarm(PRINT_FREQ);
321
322         for (i=0;i<nprocs;) {
323                 if (waitpid(0, &status, 0) == -1) continue;
324                 if (WEXITSTATUS(status) != 0) {
325                         printf("Child failed with status %d\n",
326                                WEXITSTATUS(status));
327                         exit(1);
328                 }
329                 i++;
330         }
331
332         alarm(0);
333         sig_alarm(SIGALRM);
334
335         printf("\n");
336
337         report_latencies();
338 }
339
340
341 static void show_usage(void)
342 {
343         printf("usage: dbench [OPTIONS] nprocs\n" \
344                "usage: tbench [OPTIONS] nprocs <server>\n" \
345                "options:\n" \
346                "  -v               show version\n" \
347                "  -t timelimit     run time in seconds (default 600)\n" \
348                "  -D directory     base directory to run in\n" \
349                "  -c loadfile      set location of the loadfile\n" \
350                "  -R               target rate (MByte/sec)\n" \
351                "  -s               synchronous file IO\n" \
352                "  -F               fsync on write\n" \
353                "  -S               synchronous directories (mkdir, unlink...)\n" \
354                "  -x               enable EA support\n" \
355                "  -T options       set socket options for tbench\n");
356         exit(1);
357 }
358
359
360
361 static int process_opts(int argc, const char **argv)
362 {
363         const char **extra_argv;
364         int extra_argc = 0;
365         struct poptOption popt_options[] = {
366                 POPT_AUTOHELP
367                 { "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0, 
368                   "timelimit", "integer" },
369                 { "loadfile",  'c', POPT_ARG_STRING, &options.loadfile, 0, 
370                   "loadfile", "filename" },
371                 { "directory", 'D', POPT_ARG_STRING, &options.directory, 0, 
372                   "working directory", NULL },
373                 { "tcp-options", 'T', POPT_ARG_STRING, &options.tcp_options, 0, 
374                   "TCP socket options", NULL },
375                 { "target-rate", 'R', POPT_ARG_DOUBLE, &options.targetrate, 0, 
376                   "target throughput (MB/sec)", NULL },
377                 { "sync", 's', POPT_ARG_NONE, &options.sync_open, 0, 
378                   "use O_SYNC", NULL },
379                 { "sync-dir", 'S', POPT_ARG_NONE, &options.sync_dirs, 0, 
380                   "sync directory changes", NULL },
381                 { "fsync", 'F', POPT_ARG_NONE, &options.do_fsync, 0, 
382                   "fsync on write", NULL },
383                 { "xattr", 'x', POPT_ARG_NONE, &options.ea_enable, 0, 
384                   "use xattrs", NULL },
385                 { "no-resolve", 0, POPT_ARG_NONE, &options.no_resolve, 0, 
386                   "disable name resolution simulation", NULL },
387                 { "clients-per-process", 0, POPT_ARG_INT, &options.clients_per_process, 0, 
388                   "number of clients per process", NULL },
389                 { "one-byte-write-fix", 0, POPT_ARG_NONE, &options.one_byte_write_fix, 0, 
390                   "try to fix 1 byte writes", NULL },
391                 { "stat-check", 0, POPT_ARG_NONE, &options.stat_check, 0, 
392                   "check for pointless calls with stat", NULL },
393                 POPT_TABLEEND
394         };
395         poptContext pc;
396         int opt;
397
398         pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST);
399
400         while ((opt = poptGetNextOpt(pc)) != -1) {
401                 switch (opt) {
402                 default:
403                         fprintf(stderr, "Invalid option %s: %s\n", 
404                                 poptBadOption(pc, 0), poptStrerror(opt));
405                         exit(1);
406                 }
407         }
408
409         /* setup the remaining options for the main program to use */
410         extra_argv = poptGetArgs(pc);
411         if (extra_argv) {
412                 extra_argv++;
413                 while (extra_argv[extra_argc]) extra_argc++;
414         }
415
416         if (extra_argc < 1) {
417                 printf("You need to specify NPROCS\n");
418                 poptPrintHelp(pc, stdout, 0);
419                 exit(1);
420         }
421
422 #ifndef HAVE_EA_SUPPORT
423         if (options.ea_enable) {
424                 printf("EA suppport not compiled in\n");
425                 exit(1);
426         }
427 #endif
428         
429         options.nprocs = atoi(extra_argv[0]);
430
431         if (extra_argc >= 2) {
432                 options.server = extra_argv[1];
433         }
434
435         return 1;
436 }
437
438
439
440  int main(int argc, const char *argv[])
441 {
442         double total_bytes = 0;
443         double t, latency=0;
444         int i;
445
446         setlinebuf(stdout);
447
448         printf("dbench version %s - Copyright Andrew Tridgell 1999-2004\n\n", VERSION);
449
450         if (!process_opts(argc, argv))
451                 show_usage();
452
453         if (options.warmup == -1) {
454                 options.warmup = options.timelimit / 5;
455         }
456
457         printf("Running for %d seconds with load '%s' and minimum warmup %d secs\n", 
458                options.timelimit, options.loadfile, options.warmup);
459
460         create_procs(options.nprocs, child_run);
461
462         for (i=0;i<options.nprocs*options.clients_per_process;i++) {
463                 total_bytes += children[i].bytes - children[i].bytes_done_warmup;
464                 latency = MAX(latency, children[i].worst_latency);
465         }
466
467         t = timeval_elapsed2(&tv_start, &tv_end);
468
469         printf("Throughput %g MB/sec%s%s  %d clients  %d procs  max_latency=%.03f ms\n", 
470                throughput,
471                options.sync_open ? " (sync open)" : "",
472                options.sync_dirs ? " (sync dirs)" : "", 
473                options.nprocs*options.clients_per_process,
474                options.nprocs, latency*1000);
475         return 0;
476 }