[PATCH] fio: Add option for setting cpu affinity mask
[disktools.git] / fio.c
1 /*
2  * fio - the flexible io tester
3  *
4  * Copyright (C) 2005 Jens Axboe <axboe@suse.de>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <signal.h>
28 #include <time.h>
29 #include <ctype.h>
30 #include <sched.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/wait.h>
35 #include <semaphore.h>
36 #include <sys/ipc.h>
37 #include <sys/shm.h>
38 #include <asm/unistd.h>
39
40 /*
41  * assume we don't have _get either, if _set isn't defined
42  */
43 #ifndef __NR_ioprio_set
44
45 #if defined(__i386__)
46 #define __NR_ioprio_set         289
47 #define __NR_ioprio_get         290
48 #elif defined(__powerpc__) || defined(__powerpc64__)
49 #define __NR_ioprio_set         273
50 #define __NR_ioprio_get         274
51 #elif defined(__x86_64__)
52 #define __NR_ioprio_set         251
53 #define __NR_ioprio_get         252
54 #elif defined(__ia64__)
55 #define __NR_ioprio_set         1274
56 #define __NR_ioprio_get         1275
57 #elif defined(__alpha__)
58 #define __NR_ioprio_set         442
59 #define __NR_ioprio_get         443
60 #elif defined(__s390x__) || defined(__s390__)
61 #define __NR_ioprio_set         282
62 #define __NR_ioprio_get         283
63 #else
64 #error "Unsupported arch"
65 #endif
66
67 #endif
68
69 static int ioprio_set(int which, int who, int ioprio)
70 {
71         return syscall(__NR_ioprio_set, which, who, ioprio);
72 }
73
74 enum {
75         IOPRIO_WHO_PROCESS = 1,
76         IOPRIO_WHO_PGRP,
77         IOPRIO_WHO_USER,
78 };
79
80 #define IOPRIO_CLASS_SHIFT      13
81
82 #define BS      (4096)
83 #define MASK    (4095)
84
85 #define TIMEOUT (30)
86
87 #define ALIGN(buf)      (((unsigned long) (buf) + MASK) & ~(MASK))
88
89 static int sequential = 1;
90 static int write_stat = 0;
91 static int repeatable = 1;
92 static int thread_number;
93 static int timeout = TIMEOUT;
94 static int odirect = 1;
95 static int global_bs = BS;
96 static char *ini_file;
97
98 static int shm_id;
99
100 static cpu_set_t def_cpumask;
101
102 #define DDIR_READ       (0)
103 #define DDIR_WRITE      (1)
104
105 struct thread_data {
106         char file_name[256];
107         int thread_number;
108         int error;
109         int fd;
110         int stat_fd;
111         pid_t pid;
112         int terminate;
113         int ddir;
114         int ioprio;
115         int sequential;
116         int bs;
117         int odirect;
118         int delay_sleep;
119         cpu_set_t cpumask;
120
121         unsigned int rate;
122         unsigned int rate_usec_cycle;
123         unsigned int rate_pending_usleep;
124
125         unsigned long max_latency;      /* msec */
126         unsigned long min_latency;      /* msec */
127         unsigned long runtime;          /* sec */
128         unsigned long blocks;
129         unsigned long blocks_read;
130         unsigned long last_block;
131         sem_t mutex;
132         sem_t done_mutex;
133         struct drand48_data random_state;
134
135         /*
136          * bandwidth stat
137          */
138         unsigned long stat_time;
139         unsigned long stat_time_last;
140         unsigned long stat_blocks_last;
141 };
142
143 static struct thread_data *threads;
144
145 static sem_t startup_sem;
146
147 void sig_handler(int sig)
148 {
149         int i;
150
151         for (i = 0; i < thread_number; i++) {
152                 struct thread_data *td = &threads[i];
153
154                 td->terminate = 1;
155         }
156 }
157
158 int init_random_state(struct thread_data *td)
159 {
160         unsigned long seed = 123;
161
162         if (td->sequential)
163                 return 0;
164
165         if (!repeatable) {
166                 int fd = open("/dev/random", O_RDONLY);
167
168                 if (fd == -1) {
169                         td->error = errno;
170                         return 1;
171                 }
172
173                 if (read(fd, &seed, sizeof(seed)) < (int) sizeof(seed)) {
174                         td->error = EIO;
175                         close(fd);
176                         return 1;
177                 }
178
179                 close(fd);
180         }
181
182         srand48_r(seed, &td->random_state);
183         return 0;
184 }
185
186 void shutdown_stat_file(struct thread_data *td)
187 {
188         if (td->stat_fd != -1) {
189                 fsync(td->stat_fd);
190                 close(td->stat_fd);
191         }
192 }
193
194 int init_stat_file(struct thread_data *td)
195 {
196         char *n;
197
198         if (!write_stat)
199                 return 0;
200
201         n = malloc(256);
202         sprintf(n, "%s.stat", td->file_name);
203         td->stat_fd = open(n, O_WRONLY | O_CREAT | O_TRUNC, 0644);
204         if (td->stat_fd == -1) {
205                 free(n);
206                 td->error = errno;
207                 return 1;
208         }
209
210         free(n);
211         return 0;
212 }
213
214 unsigned long utime_since(struct timeval *s, struct timeval *e)
215 {
216         double sec, usec;
217
218         sec = e->tv_sec - s->tv_sec;
219         usec = e->tv_usec - s->tv_usec;
220         if (sec > 0 && usec < 0) {
221                 sec--;
222                 usec += 1000000;
223         }
224
225         sec *= (double) 1000000;
226
227         return sec + usec;
228 }
229
230 unsigned long mtime_since(struct timeval *s, struct timeval *e)
231 {
232         double sec, usec;
233
234         sec = e->tv_sec - s->tv_sec;
235         usec = e->tv_usec - s->tv_usec;
236         if (sec > 0 && usec < 0) {
237                 sec--;
238                 usec += 1000000;
239         }
240
241         sec *= (double) 1000;
242         usec /= (double) 1000;
243
244         return sec + usec;
245 }
246
247 unsigned long time_since(struct timeval *s, struct timeval *e)
248 {
249         double sec, usec, ret;
250
251         sec = e->tv_sec - s->tv_sec;
252         usec = e->tv_usec - s->tv_usec;
253         if (sec > 0 && usec < 0) {
254                 sec--;
255                 usec += 1000000;
256         }
257
258         ret = sec + usec / (double) 1000000;
259         if (ret < 0)
260                 ret = 0;
261
262         return (unsigned long) ret;
263 }
264
265 unsigned long get_next_offset(struct thread_data *td)
266 {
267         unsigned long b;
268         long r;
269
270         if (!td->sequential) {
271                 lrand48_r(&td->random_state, &r);
272                 b = (1+(double) (td->blocks-1) * r / (RAND_MAX+1.0));
273         } else {
274                 b = td->last_block;
275                 td->last_block++;
276         }
277
278         return b * td->bs;
279 }
280
281 void add_stat_sample(struct thread_data *td, unsigned long msec)
282 {
283         char sample[256];
284
285         if (!td->stat_fd)
286                 return;
287
288 #if 0
289         sprintf(sample, "%lu, %lu\n", td->blocks_read, msec);
290         write(td->stat_fd, sample, strlen(sample));
291 #else
292         td->stat_time += msec;
293         td->stat_time_last += msec;
294         td->stat_blocks_last++;
295
296         if (td->stat_time_last >= 500) {
297                 unsigned long rate = td->stat_blocks_last * td->bs / (td->stat_time_last);
298
299                 td->stat_time_last = 0;
300                 td->stat_blocks_last = 0;
301                 sprintf(sample, "%lu, %lu\n", td->stat_time, rate);
302                 //sprintf(sample, "%lu, %lu\n", td->blocks_read, msec);
303                 write(td->stat_fd, sample, strlen(sample));
304         }
305 #endif
306 }
307
308 void usec_sleep(int usec)
309 {
310         struct timespec req = { .tv_sec = 0, .tv_nsec = usec * 1000 };
311         struct timespec rem;
312
313         do {
314                 rem.tv_sec = rem.tv_nsec = 0;
315                 nanosleep(&req, &rem);
316                 if (!rem.tv_nsec)
317                         break;
318
319                 req.tv_nsec = rem.tv_nsec;
320         } while (1);
321 }
322
323 void rate_throttle(struct thread_data *td, unsigned long time_spent)
324 {
325         if (time_spent < td->rate_usec_cycle) {
326                 unsigned long s = td->rate_usec_cycle - time_spent;
327
328                 td->rate_pending_usleep += s;
329                 if (td->rate_pending_usleep >= 100000) {
330                         usec_sleep(td->rate_pending_usleep);
331                         td->rate_pending_usleep = 0;
332                 }
333         } else if (td->rate_pending_usleep) {
334                 long overtime = time_spent - td->rate_usec_cycle;
335
336                 if (overtime > td->rate_pending_usleep)
337                         td->rate_pending_usleep = 0;
338                 else
339                         td->rate_pending_usleep -= overtime;
340         }
341 }
342
343 void do_thread_io(struct thread_data *td)
344 {
345         struct timeval s, e, start;
346         char *buffer, *ptr;
347         unsigned long blocks, msec, usec;
348
349         ptr = malloc(td->bs+MASK);
350         buffer = (char *) ALIGN(ptr);
351
352         gettimeofday(&start, NULL);
353
354         for (blocks = 0; blocks < td->blocks; blocks++) {
355                 off_t offset = get_next_offset(td);
356                 int ret;
357
358                 if (td->terminate)
359                         break;
360
361                 if (lseek(td->fd, offset, SEEK_SET) == -1) {
362                         td->error = errno;
363                         break;
364                 }
365
366                 if (td->delay_sleep)
367                         usec_sleep(td->delay_sleep);
368
369                 gettimeofday(&s, NULL);
370
371                 if (td->ddir == DDIR_READ)
372                         ret = read(td->fd, buffer, td->bs);
373                 else
374                         ret = write(td->fd, buffer, td->bs);
375
376                 if (ret < td->bs) {
377                         if (ret == -1)
378                                 td->error = errno;
379                         break;
380                 }
381
382                 gettimeofday(&e, NULL);
383
384                 usec = utime_since(&s, &e);
385                 msec = usec / 1000;
386
387                 if (td->rate)
388                         rate_throttle(td, usec);
389
390                 add_stat_sample(td, msec);
391
392                 td->blocks_read++;
393
394                 //if (td->ddir == DDIR_WRITE && !(td->blocks_read % 512))
395                 //      fsync(td->fd);
396
397                 if (msec < td->min_latency)
398                         td->min_latency = msec;
399                 if (msec > td->max_latency)
400                         td->max_latency = msec;
401         }
402
403         if (td->ddir == DDIR_WRITE && !td->odirect)
404                 fsync(td->fd);
405
406         gettimeofday(&e, NULL);
407         td->runtime = mtime_since(&start, &e);
408
409         free(ptr);
410 }
411         
412 void *thread_main(int shm_id, int offset, char *argv[])
413 {
414         struct thread_data *td;
415         void *data;
416         struct stat *statbuf = NULL;
417         int ret = 1, flags;
418
419         data = shmat(shm_id, NULL, 0);
420         td = data + offset * sizeof(struct thread_data);
421         td->pid = getpid();
422
423         td->fd = -1;
424
425         if (sched_setaffinity(td->pid, sizeof(td->cpumask), &td->cpumask) == -1) {
426                 td->error = errno;
427                 goto err;
428         }
429
430         printf("Thread (%s) (pid=%u) (f=%s) started\n", td->ddir == DDIR_READ ? "read" : "write", td->pid, td->file_name);
431         fflush(stdout);
432
433         sprintf(argv[0], "%s%d\n", argv[0], offset);
434
435         flags = 0;
436         if (td->odirect)
437                 flags |= O_DIRECT;
438
439         if (td->ddir == DDIR_READ)
440                 td->fd = open(td->file_name, flags | O_RDONLY);
441         else
442                 td->fd = open(td->file_name, flags | O_WRONLY | O_CREAT | O_TRUNC, 0644);
443
444         if (td->fd == -1) {
445                 td->error = errno;
446                 goto err;
447         }
448
449         if (init_random_state(td))
450                 goto out;
451         if (init_stat_file(td))
452                 goto out;
453
454         if (td->ddir == DDIR_READ) {
455                 statbuf = malloc(sizeof(*statbuf));
456                 if (fstat(td->fd, statbuf) == -1) {
457                         td->error = errno;
458                         goto out;
459                 }
460
461                 td->blocks = statbuf->st_size / td->bs;
462                 if (!td->blocks) {
463                         td->error = EINVAL;
464                         goto out;
465                 }
466         } else
467                 td->blocks = 1024 * 1024 * 1024 / td->bs;
468
469         if (td->ioprio != -1) {
470                 if (ioprio_set(IOPRIO_WHO_PROCESS, 0, td->ioprio) == -1) {
471                         td->error = errno;
472                         goto out;
473                 }
474         }
475
476         sem_post(&startup_sem);
477         sem_wait(&td->mutex);
478         do_thread_io(td);
479         ret = 0;
480
481 out:
482         if (statbuf)
483                 free(statbuf);
484         shutdown_stat_file(td);
485 err:
486         if (td->fd != -1)
487                 close(td->fd);
488         sem_post(&td->done_mutex);
489         if (ret)
490                 sem_post(&startup_sem);
491         shmdt(td);
492         return NULL;
493 }
494
495 void free_shm(void)
496 {
497         shmdt(threads);
498 }
499
500 void show_thread_status(struct thread_data *td)
501 {
502         int prio, prio_class;
503         unsigned long bw = 0;
504
505         if (td->runtime)
506                 bw = (td->blocks_read * td->bs) / td->runtime;
507
508         prio = td->ioprio & 0xff;
509         prio_class = td->ioprio >> IOPRIO_CLASS_SHIFT;
510
511         printf("thread%d (%s): err=%2d, prio=%1d/%1d maxl=%5lumsec, io=%6luMiB, bw=%6luKiB/sec\n", td->thread_number, td->ddir == DDIR_READ ? " read": "write", td->error, prio_class, prio, td->max_latency, td->blocks_read * td->bs >> 20, bw);
512 }
513
514 void usage(char *progname)
515 {
516         printf("%s: <-s 0/1> <-b kb> <-t sec> <-w 0/1> <-c r,w,r...> file0... fileN\n", progname);
517 }
518
519 void setup_rate(struct thread_data *td, int rate)
520 {
521         int nr_reads_per_sec = rate * 1024 / td->bs;
522
523         td->rate = rate;
524         td->rate_usec_cycle = 1000000 / nr_reads_per_sec;
525         td->rate_pending_usleep = 0;
526 }
527
528 void add_job(const char *filename, int rw, int bs, int direct, int prio, int random, int delay, int rate, cpu_set_t cpumask)
529 {
530         struct thread_data *td = &threads[thread_number++];
531
532         strcpy(td->file_name, filename);
533         td->thread_number = thread_number;
534         td->stat_fd = -1;
535         sem_init(&td->mutex, 1, 1);
536         sem_init(&td->done_mutex, 1, 0);
537         td->min_latency = 10000000;
538         td->ddir = rw;
539         td->ioprio = prio;
540         td->sequential = !random;
541         td->odirect = direct;
542         td->bs = bs;
543         td->delay_sleep = delay;
544         td->cpumask = cpumask;
545
546         if (rate)
547                 setup_rate(td, rate);
548
549         printf("Client%d: file=%s, rw=%d, prio=%d, seq=%d, odir=%d, bs=%d, rate=%d\n", thread_number, filename, rw, prio, !random, direct, bs, rate);
550 }
551
552 static void fill_cpu_mask(cpu_set_t cpumask, int cpu)
553 {
554         int i;
555
556         CPU_ZERO(&cpumask);
557
558         for (i = 0; i < sizeof(int) * 8; i++) {
559                 if ((1 << i) & cpu)
560                         CPU_SET(i, &cpumask);
561         }
562 }
563
564 void fill_option(const char *input, char *output)
565 {
566         int i;
567
568         i = 0;
569         while (input[i] != ',' && input[i] != '}' && input[i] != '\0') {
570                 output[i] = input[i];
571                 i++;
572         }
573
574         output[i] = '\0';
575 }
576
577 /*
578  * job key words:
579  *
580  * file=
581  * bs=
582  * rw=
583  * direct=
584  */
585 int parse_jobs_cmd(int argc, char *argv[], int index)
586 {
587         int rw, bs, direct, prio, random, prioclass, delay, rate, cpu;
588         char *string, *filename, *p, *c;
589         cpu_set_t cpumask;
590         int i;
591
592         string = malloc(256);
593         filename = malloc(256);
594
595         for (i = index; i < argc; i++) {
596                 p = argv[i];
597
598                 c = strpbrk(p, "{");
599                 if (!c)
600                         break;
601
602                 filename[0] = 0;
603                 rw = DDIR_READ;
604                 bs = global_bs;
605                 direct = 1;
606                 prio = 4;
607                 random = !sequential;
608                 prioclass = 2;
609                 delay = 0;
610                 rate = 0;
611                 memcpy(&cpumask, &def_cpumask, sizeof(cpumask));
612
613                 c = strstr(p, "rw=");
614                 if (c) {
615                         c += 3;
616                         if (*c == '0')
617                                 rw = DDIR_READ;
618                         else
619                                 rw = DDIR_WRITE;
620                 }
621
622                 c = strstr(p, "prio=");
623                 if (c) {
624                         c += 5;
625                         prio = *c - '0';
626                 }
627
628                 c = strstr(p, "prioclass=");
629                 if (c) {
630                         c += 10;
631                         prioclass = *c - '0';
632                 }
633
634                 c = strstr(p, "file=");
635                 if (c) {
636                         c += 5;
637                         fill_option(c, filename);
638                 }
639
640                 c = strstr(p, "bs=");
641                 if (c) {
642                         c += 3;
643                         fill_option(c, string);
644                         bs = strtoul(string, NULL, 10);
645                         bs <<= 10;
646                 }
647
648                 c = strstr(p, "direct=");
649                 if (c) {
650                         c += 7;
651                         if (*c != '0')
652                                 direct = 1;
653                         else
654                                 direct = 0;
655                 }
656
657                 c = strstr(p, "delay=");
658                 if (c) {
659                         c += 6;
660                         fill_option(c, string);
661                         delay = strtoul(string, NULL, 10);
662                 }
663
664                 c = strstr(p, "rate=");
665                 if (c) {
666                         c += 5;
667                         fill_option(c, string);
668                         rate = strtoul(string, NULL, 10);
669                 }
670
671                 c = strstr(p, "cpumask=");
672                 if (c) {
673                         c += 8;
674                         fill_option(c, string);
675                         cpu = strtoul(string, NULL, 10);
676                         fill_cpu_mask(cpumask, cpu);
677                 }
678
679
680                 c = strstr(p, "random");
681                 if (c)
682                         random = 1;
683                 c = strstr(p, "sequential");
684                 if (c)
685                         random = 0;
686
687                 add_job(filename, rw, bs, direct, (prioclass << IOPRIO_CLASS_SHIFT) | prio, random, delay, rate, cpumask);
688         }
689
690         free(string);
691         free(filename);
692         return thread_number;
693 }
694
695 int check_int(char *p, char *name, int *val)
696 {
697         char str[128];
698
699         sprintf(str, "%s=%%d", name);
700         if (sscanf(p, str, val) == 1)
701                 return 0;
702
703         sprintf(str, "%s = %%d", name);
704         if (sscanf(p, str, val) == 1)
705                 return 0;
706
707         return 1;
708 }
709
710 int is_empty(char *line)
711 {
712         unsigned int i;
713
714         for (i = 0; i < strlen(line); i++)
715                 if (!isspace(line[i]) && !iscntrl(line[i]))
716                         return 0;
717
718         return 1;
719 }
720
721 int parse_jobs_ini(char *file)
722 {
723         int rw, bs, direct, prio, random, prioclass, delay, rate, jobs, cpu;
724         cpu_set_t cpumask;
725         char *string, *name;
726         fpos_t off;
727         FILE *f;
728         char *p;
729
730         f = fopen(file, "r");
731         if (!f) {
732                 perror("fopen");
733                 return 0;
734         }
735
736         string = malloc(4096);
737         name = malloc(256);
738
739         jobs = 0;
740
741         while ((p = fgets(string, 4096, f)) != NULL) {
742                 if (sscanf(p, "[%s]", name) != 1)
743                         continue;
744
745                 name[strlen(name) - 1] = '\0';
746
747                 rw = DDIR_READ;
748                 bs = global_bs;
749                 direct = 1;
750                 prio = 4;
751                 random = !sequential;
752                 prioclass = 2;
753                 delay = 0;
754                 rate = 0;
755                 memcpy(&cpumask, &def_cpumask, sizeof(cpumask));
756                 cpu = 0;
757
758                 fgetpos(f, &off);
759                 while ((p = fgets(string, 4096, f)) != NULL) {
760                         if (is_empty(p))
761                                 break;
762                         if (!check_int(p, "bs", &bs)) {
763                                 bs <<= 10;
764                                 fgetpos(f, &off);
765                                 continue;
766                         }
767                         if (!check_int(p, "rw", &rw)) {
768                                 fgetpos(f, &off);
769                                 continue;
770                         }
771                         if (!check_int(p, "prio", &prio)) {
772                                 fgetpos(f, &off);
773                                 continue;
774                         }
775                         if (!check_int(p, "prioclass", &prioclass)) {
776                                 fgetpos(f, &off);
777                                 continue;
778                         }
779                         if (!check_int(p, "direct", &direct)) {
780                                 fgetpos(f, &off);
781                                 continue;
782                         }
783                         if (!check_int(p, "rate", &rate)) {
784                                 fgetpos(f, &off);
785                                 continue;
786                         }
787                         if (!check_int(p, "delay", &delay)) {
788                                 fgetpos(f, &off);
789                                 continue;
790                         }
791                         if (!check_int(p, "cpumask", &cpu)) {
792                                 fill_cpu_mask(cpumask, cpu);
793                                 fgetpos(f, &off);
794                                 continue;
795                         }
796                         if (!strcmp(p, "sequential")) {
797                                 sequential = 1;
798                                 fgetpos(f, &off);
799                                 continue;
800                         }
801                         if (!strcmp(p, "random")) {
802                                 sequential = 0;
803                                 fgetpos(f, &off);
804                                 continue;
805                         }
806                 }
807                 fsetpos(f, &off);
808
809                 add_job(name, rw, bs, direct, (prioclass << IOPRIO_CLASS_SHIFT) | prio, random, delay, rate, cpumask);
810                 jobs++;
811         }
812
813         free(string);
814         free(name);
815         return jobs;
816 }
817
818 int parse_options(int argc, char *argv[])
819 {
820         int i;
821
822         for (i = 1; i < argc; i++) {
823                 char *parm = argv[i];
824
825                 if (parm[0] != '-')
826                         break;
827
828                 parm++;
829                 switch (*parm) {
830                         case 's':
831                                 parm++;
832                                 sequential = !!atoi(parm);
833                                 break;
834                         case 'b':
835                                 parm++;
836                                 global_bs = atoi(parm);
837                                 global_bs <<= 10;
838                                 break;
839                         case 't':
840                                 parm++;
841                                 timeout = atoi(parm);
842                                 break;
843                         case 'w':
844                                 parm++;
845                                 write_stat = !!atoi(parm);
846                                 break;
847                         case 'r':
848                                 parm++;
849                                 repeatable = !!atoi(parm);
850                                 break;
851                         case 'o':
852                                 parm++;
853                                 odirect = !!atoi(parm);
854                                 break;
855                         case 'f':
856                                 if (i + 1 >= argc) {
857                                         printf("-f needs file as arg\n");
858                                         break;
859                                 }
860                                 ini_file = strdup(argv[i+1]);
861                                 break;
862                         default:
863                                 printf("bad option %s\n", argv[i]);
864                                 break;
865                 }
866         }
867
868         return i;
869 }
870
871 int main(int argc, char *argv[])
872 {
873         static unsigned long max_run[2], min_run[2], total_blocks[2];
874         static unsigned long max_bw[2], min_bw[2], maxl[2], minl[2];
875         static unsigned long read_mb, write_mb, read_agg, write_agg;
876         int i, jobs;
877
878         if (sched_getaffinity(getpid(), sizeof(def_cpumask), &def_cpumask) == -1) {
879                 perror("sched_getaffinity");
880                 return 1;
881         }
882
883         shm_id = shmget(0, (argc - 1) * sizeof(struct thread_data), IPC_CREAT | 0600);
884         if (shm_id == -1) {
885                 perror("shmget");
886                 return 1;
887         }
888
889         threads = shmat(shm_id, NULL, 0);
890         if (threads == (void *) -1 ) {
891                 perror("shmat");
892                 return 1;
893         }
894
895         atexit(free_shm);
896
897         i = parse_options(argc, argv);
898
899         if (ini_file)
900                 jobs = parse_jobs_ini(ini_file);
901         else
902                 jobs = parse_jobs_cmd(argc, argv, i);
903
904         if (global_bs <= 0)
905                 global_bs = BS;
906         if (timeout <= 0)
907                 timeout = TIMEOUT;
908
909         printf("%s: %s, bs=%uKiB, timeo=%u, write_stat=%u, odirect=%d\n", argv[0], sequential ? "sequential" : "random", global_bs >> 10, timeout, write_stat, odirect);
910
911         if (!jobs) {
912                 printf("Nothing to do\n");
913                 return 1;
914         } else
915                 printf("%d Clients configured\n", jobs);
916
917         for (i = 0; i < jobs; i++) {
918                 sem_init(&startup_sem, 1, 1);
919
920                 if (fork())
921                         sem_wait(&startup_sem);
922                 else {
923                         thread_main(shm_id, i, argv);
924                         exit(0);
925                 }
926         }
927
928         if (!thread_number) {
929                 usage(argv[0]);
930                 return 1;
931         }
932
933         signal(SIGALRM, sig_handler);
934         alarm(timeout);
935
936         printf("Starting %d threads\n", thread_number);
937         for (i = 0; i < thread_number; i++) {
938                 struct thread_data *td = &threads[i];
939
940                 sem_post(&td->mutex);
941         }
942
943         for (i = 0; i < thread_number; i++) {
944                 struct thread_data *td = &threads[i];
945
946                 waitpid(td->pid, NULL, 0);
947         }
948
949         min_bw[0] = min_run[0] = ~0UL;
950         min_bw[1] = min_run[1] = ~0UL;
951         minl[0] = minl[1] = ~0UL;
952         for (i = 0; i < thread_number; i++) {
953                 struct thread_data *td = &threads[i];
954                 unsigned long bw = 0;
955
956                 if (td->error)
957                         goto show_stat;
958
959                 if (td->runtime < min_run[td->ddir])
960                         min_run[td->ddir] = td->runtime;
961                 if (td->runtime > max_run[td->ddir])
962                         max_run[td->ddir] = td->runtime;
963
964                 if (td->runtime)
965                         bw = (td->blocks_read * td->bs) / td->runtime;
966                 if (bw < min_bw[td->ddir])
967                         min_bw[td->ddir] = bw;
968                 if (bw > max_bw[td->ddir])
969                         max_bw[td->ddir] = bw;
970                 if (td->max_latency < minl[td->ddir])
971                         minl[td->ddir] = td->max_latency;
972                 if (td->max_latency > maxl[td->ddir])
973                         maxl[td->ddir] = td->max_latency;
974
975                 total_blocks[td->ddir] += td->blocks_read;
976
977                 if (td->ddir == DDIR_READ) {
978                         read_mb += (td->bs * td->blocks_read) >> 20;
979                         if (td->runtime)
980                                 read_agg += (td->blocks_read * td->bs) / td->runtime;
981                 }
982                 if (td->ddir == DDIR_WRITE) {
983                         write_mb += (td->bs * td->blocks_read) >> 20;
984                         if (td->runtime)
985                                 write_agg += (td->blocks_read * td->bs) / td->runtime;
986                 }
987
988 show_stat:
989                 show_thread_status(td);
990         }
991
992         printf("Run status:\n");
993         if (max_run[DDIR_READ])
994                 printf("   READ: io=%luMiB, aggrb=%lu, minl=%lu, maxl=%lu, minb=%lu, maxb=%lu, mint=%lumsec, maxt=%lumsec\n", read_mb, read_agg, minl[0], maxl[0], min_bw[0], max_bw[0], min_run[0], max_run[0]);
995         if (max_run[DDIR_WRITE])
996                 printf("  WRITE: io=%luMiB, aggrb=%lu, minl=%lu, maxl=%lu, minb=%lu, maxb=%lu, mint=%lumsec, maxt=%lumsec\n", write_mb, write_agg, minl[1], maxl[1], min_bw[1], max_bw[1], min_run[1], max_run[1]);
997         return 0;
998 }