added -C option to enable running on more than 1 node
[tridge/junkcode.git] / thread_perf.c
1 /*
2   simple thread/process benchmark
3
4   Copyright (C) Andrew Tridgell <tridge@samba.org> 2003
5
6   Released under the GNU GPL version 2 or later
7 */
8
9 /*
10   this program is designed to test the relative performance of threads/processes
11   for operations typically performed by fileserving applications.
12 */
13
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <time.h>
18 #include <sys/time.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <signal.h>
25 #include <string.h>
26 #include <dirent.h>
27
28 #ifndef NO_THREADS
29 #include <pthread.h>
30 #endif
31
32 /* this contains per-task data */
33 static struct {
34         char *dname;
35         char *fname;
36 } *id_data;
37
38 /* these pipes are used for synchronised startup of the tasks */
39 static struct barrier {
40         int fd1[2];
41         int fd2[2];
42 } barriers[2];
43
44 /* setup a barrier */
45 static void barrier_setup(struct barrier *b)
46 {
47         if (pipe(b->fd1) != 0 || pipe(b->fd2) != 0) {
48                 fprintf(stderr,"Barrier setup failed\n");
49                 exit(1);
50         }
51 }
52
53 /* cleanup the barrier pipes */
54 static void barrier_cleanup(struct barrier *b)
55 {
56         close(b->fd1[0]);
57         close(b->fd1[1]);
58         close(b->fd2[0]);
59         close(b->fd2[1]);
60 }
61
62 /* wait for the parent to signal startup */
63 static void barrier_wait(struct barrier *b)
64 {
65         char c = 0;
66
67         if (write(b->fd1[1], &c, 1) != 1 ||
68             read(b->fd2[0], &c, 1) != 1) {
69                 fprintf(stderr, "Barrier wait failed\n");
70                 exit(1);
71         }
72 }
73
74 /* synchronise children. Return the amount of time since the last
75    barrier */
76 static double barrier_parent(struct barrier *b, int nprocs)
77 {
78         char *s = calloc(nprocs, 1);
79         int i, nwritten=0;
80         char c = 0;
81         double t;
82         static struct timeval tp1;
83         struct timeval tp2;
84
85         for (i=0;i<nprocs;i++) {
86                 while (read(b->fd1[0], &c, 1) != 1) ;
87         }
88
89         /* putting the timer here prevents problems with the parent getting
90            rescheduled after the write */
91         gettimeofday(&tp2,NULL);
92         t = (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) - 
93                 (tp1.tv_sec + (tp1.tv_usec*1.0e-6));
94         gettimeofday(&tp1,NULL);
95
96         while (nwritten != nprocs) {
97                 int n = write(b->fd2[1], s, nprocs-nwritten);
98                 if (n <= 0) {
99                         fprintf(stderr, "Barrier parent failed\n");
100                         exit(1);
101                 }
102                 nwritten += n;
103         }
104         free(s);
105         return t;
106 }
107
108 #ifndef NO_THREADS
109 /*
110   create a thread with initial function fn(private)
111 */
112 static pthread_t thread_start(void *(*fn)(int), int id)
113 {
114         pthread_t thread_id;
115         pthread_attr_t thread_attr;
116         int rc;
117         typedef void *(*thread_fn_t)(void *);
118         pthread_attr_init(&thread_attr);
119         pthread_attr_setdetachstate(&thread_attr, 0);
120         rc = pthread_create(&thread_id, &thread_attr, (thread_fn_t)fn, (void *)id);
121         pthread_attr_destroy(&thread_attr);
122
123         if (rc != 0) {
124                 fprintf(stderr,"Thread create failed for id %d\n", id);
125                 exit(1);
126         }
127
128         return thread_id;
129 }
130
131 /* wait for a thread to exit */
132 static int thread_join(pthread_t id)
133 {
134         return pthread_join(id, NULL);
135 }
136 #endif
137
138
139 /*
140   create a process with initial function fn(private)
141 */
142 static pid_t proc_start(void *(*fn)(int), int id)
143 {
144         pid_t pid;
145
146         pid = fork();
147         if (pid == (pid_t)-1) {
148                 fprintf(stderr,"Fork failed for id %d\n", id);
149                 return pid;
150         }
151         if (pid == 0) {
152                 fn(id);
153                 exit(0);
154         }
155         return pid;
156 }
157
158 /* wait for a process to exit */
159 static int proc_join(pid_t id)
160 {
161         if (waitpid(id, NULL, 0) != id) {
162                 return -1;
163         }
164         return 0;
165 }
166
167 #ifndef NO_THREADS
168 /* run a function under a set of threads */
169 static double run_threads(int nthreads, void *(*fn)(int ))
170 {
171         int i;
172         pthread_t *ids = calloc(sizeof(*ids), nthreads);
173         double t;
174
175         barrier_setup(&barriers[0]);
176         barrier_setup(&barriers[1]);
177
178         for (i=0;i<nthreads;i++) {
179                 ids[i] = thread_start(fn, i);
180         }
181
182         barrier_parent(&barriers[0], nthreads);
183         t = barrier_parent(&barriers[1], nthreads);
184
185         for (i=0;i<nthreads;i++) {
186                 int rc;
187                 rc = thread_join(ids[i]);
188                 if (rc != 0) {
189                         fprintf(stderr, "Thread %d failed : %s\n", i, strerror(errno));
190                         exit(1);
191                 }
192         }
193
194         barrier_cleanup(&barriers[0]);
195         barrier_cleanup(&barriers[1]);
196
197         free(ids);
198
199         return t;
200 }
201 #endif
202
203 /* run a function under a set of processes */
204 static double run_processes(int nprocs, void *(*fn)(int ))
205 {
206         int i;
207         pid_t *ids = calloc(sizeof(*ids), nprocs);
208         double t;
209
210         barrier_setup(&barriers[0]);
211         barrier_setup(&barriers[1]);
212
213         for (i=0;i<nprocs;i++) {
214                 ids[i] = proc_start(fn, i);
215                 if (ids[i] == (pid_t)-1) {
216                         for (i--;i>=0;i--) {
217                                 kill(ids[i], SIGTERM);
218                         }
219                         exit(1);
220                 }
221         }
222
223         barrier_parent(&barriers[0], nprocs);
224         t = barrier_parent(&barriers[1], nprocs);
225
226         for (i=0;i<nprocs;i++) {
227                 int rc;
228                 rc = proc_join(ids[i]);
229                 if (rc != 0) {
230                         fprintf(stderr, "Process %d failed : %s\n", i, strerror(errno));
231                         exit(1);
232                 }
233         }
234
235         barrier_cleanup(&barriers[0]);
236         barrier_cleanup(&barriers[1]);
237
238         free(ids);
239
240         return t;
241 }
242
243
244
245 /***********************************************************************
246   a simple malloc speed test using a wide variety of malloc sizes
247 ************************************************************************/
248 static void *test_malloc(int id)
249 {
250 #define NMALLOCS 300
251         int i, j;
252         void *ptrs[NMALLOCS];
253
254         barrier_wait(&barriers[0]);
255
256         for (j=0;j<500;j++) {
257                 for (i=1;i<NMALLOCS;i++) {
258                         ptrs[i] = malloc(i);
259                         if (!ptrs[i]) {
260                                 printf("malloc(%d) failed!\n", i);
261                                 exit(1);
262                         }
263                 }
264                 for (i=1;i<NMALLOCS;i++) {
265                         free(ptrs[i]);
266                 }
267         }
268
269         barrier_wait(&barriers[1]);
270         return NULL;
271 }
272
273
274 /***********************************************************************
275  simple read/write testing using /dev/null and /dev/zero
276 ************************************************************************/
277 static void *test_readwrite(int id)
278 {
279         int i;
280         int fd_in, fd_out;
281         /* we use less than 1 page to prevent page table games */
282         char buf[4095];
283
284         barrier_wait(&barriers[0]);
285
286         fd_in = open("/dev/zero", O_RDONLY);
287         fd_out = open("/dev/null", O_WRONLY);
288         if (fd_in == -1 || fd_out == -1) {
289                 fprintf(stderr,"Failed to open /dev/zero or /dev/null\n");
290                 exit(1);
291         }
292
293         for (i=0;i<20000;i++) {
294                 if (read(fd_in, buf, sizeof(buf)) != sizeof(buf) ||
295                     write(fd_out, buf, sizeof(buf)) != sizeof(buf)) {
296                         fprintf(stderr,"IO failed at loop %d\n", i);
297                         exit(1);
298                 }
299         }
300
301         close(fd_in);
302         close(fd_out);
303         
304         barrier_wait(&barriers[1]);
305
306         return NULL;
307 }
308
309
310 /***********************************************************************
311 test stat() operations
312 ************************************************************************/
313 static void *test_stat(int id)
314 {
315         int i;
316
317         barrier_wait(&barriers[0]);
318
319         for (i=0;i<30000;i++) {
320                 struct stat st;
321                 if (stat(id_data[id].dname, &st) != 0) goto failed;
322                 if (stat(id_data[id].fname, &st) == 0) goto failed;
323         }
324
325         barrier_wait(&barriers[1]);
326
327         return NULL;
328
329 failed:
330         fprintf(stderr,"stat failed\n");
331         exit(1);
332 }
333
334 /***********************************************************************
335 test fstat() operations
336 ************************************************************************/
337 static void *test_fstat(int id)
338 {
339         int i, fd;
340
341         barrier_wait(&barriers[0]);
342
343         fd = open(id_data[id].fname, O_RDWR|O_CREAT, 0600);
344         if (fd == -1) goto failed;
345
346         for (i=0;i<1000000;i++) {
347                 struct stat st;
348                 if (fstat(fd, &st) != 0) goto failed;
349         }
350
351         close(fd);
352         unlink(id_data[id].fname);
353
354         barrier_wait(&barriers[1]);
355
356         return NULL;
357
358 failed:
359         fprintf(stderr,"fstat failed\n");
360         exit(1);
361 }
362
363 /***********************************************************************
364 test directory operations
365 ************************************************************************/
366 static void *test_dir(int id)
367 {
368         int i;
369
370         barrier_wait(&barriers[0]);
371
372         for (i=0;i<2000;i++) {
373                 DIR *d = opendir(id_data[id].dname);
374                 if (!d) goto failed;
375                 while (readdir(d)) {} ;
376                 closedir(d);
377         }
378
379         barrier_wait(&barriers[1]);
380         return NULL;
381
382 failed: 
383         fprintf(stderr,"dir failed\n");
384         exit(1);
385 }
386
387 /***********************************************************************
388 test directory operations
389 ************************************************************************/
390 static void *test_dirsingle(int id)
391 {
392         int i;
393
394         barrier_wait(&barriers[0]);
395
396         for (i=0;i<2000;i++) {
397                 DIR *d = opendir(".");
398                 if (!d) goto failed;
399                 while (readdir(d)) {} ;
400                 closedir(d);
401         }
402
403         barrier_wait(&barriers[1]);
404         return NULL;
405
406 failed: 
407         fprintf(stderr,"dirsingle failed\n");
408         exit(1);
409 }
410
411
412 /***********************************************************************
413 test create/unlink operations
414 ************************************************************************/
415 static void *test_create(int id)
416 {
417         int i;
418
419         barrier_wait(&barriers[0]);
420
421         for (i=0;i<3000;i++) {
422                 int fd;
423                 fd = open(id_data[id].fname, O_CREAT|O_TRUNC|O_RDWR, 0666);
424                 if (fd == -1) goto failed;
425                 if (open(id_data[id].fname, O_CREAT|O_TRUNC|O_RDWR|O_EXCL, 0666) != -1) goto failed;
426                 close(fd);
427                 if (unlink(id_data[id].fname) != 0) goto failed;
428         }
429         
430         barrier_wait(&barriers[1]);
431         return NULL;
432
433 failed:
434         fprintf(stderr,"create failed\n");
435         exit(1);
436 }
437
438
439 /***********************************************************************
440 test fcntl lock operations
441 ************************************************************************/
442 static void *test_lock(int id)
443 {
444         int i;
445         int fd;
446
447         barrier_wait(&barriers[0]);
448
449         fd = open(id_data[id].fname, O_CREAT|O_RDWR, 0666);
450         if (fd == -1) goto failed;
451         unlink(id_data[id].fname);
452
453         for (i=0;i<20000;i++) {
454                 struct flock lock;
455                 lock.l_type = F_WRLCK;
456                 lock.l_whence = SEEK_SET;
457                 lock.l_start = (id*100) + (i%100);
458                 lock.l_len = 1;
459                 lock.l_pid = 0;
460         
461                 if (fcntl(fd,F_SETLK,&lock) != 0) goto failed;
462
463                 lock.l_type = F_UNLCK;
464
465                 if (fcntl(fd,F_SETLK,&lock) != 0) goto failed;
466         }
467
468         close(fd);
469         
470         barrier_wait(&barriers[1]);
471         return NULL;
472
473 failed:
474         fprintf(stderr,"lock failed\n");
475         exit(1);
476 }
477
478 /***********************************************************************
479 do nothing!
480 ************************************************************************/
481 static void *test_noop(int id)
482 {
483         barrier_wait(&barriers[0]);
484         barrier_wait(&barriers[1]);
485         return NULL;
486 }
487
488
489 /*
490   show the average and range of a set of results
491 */
492 static void show_result(const char *name, double *t, int nrepeats)
493 {
494         double mint, maxt, total;
495         int i;
496         total = mint = maxt = t[0];
497         for (i=1;i<nrepeats;i++) {
498                 if (t[i] < mint) mint = t[i];
499                 if (t[i] > maxt) maxt = t[i];
500                 total += t[i];
501         }
502         printf("%s  %5.2f +/- %.2f seconds\n", name, total/nrepeats, (maxt-mint)/2);
503 }
504
505
506 /* lock a byte range in a open file */
507 int main(int argc, char *argv[])
508 {
509         int nprocs, i;
510         char *tname = "ALL";
511 #define NREPEATS 10
512         struct {
513                 const char *name;
514                 void *(*fn)(int );
515         } tests[] = {
516                 {"noop", test_noop},
517                 {"malloc", test_malloc},
518                 {"readwrite", test_readwrite},
519                 {"stat", test_stat},
520                 {"fstat", test_fstat},
521                 {"dir", test_dir},
522                 {"dirsingle", test_dirsingle},
523                 {"create", test_create},
524                 {"lock", test_lock},
525                 {NULL, NULL}
526         };
527
528         if (argc <= 1) {
529                 printf("thread_perf NPROCS\n");
530                 exit(1);
531         }
532
533         nprocs = atoi(argv[1]);
534
535         if (argc > 2) {
536                 tname = argv[2];
537         }
538
539         id_data = calloc(nprocs, sizeof(*id_data));
540         if (!id_data) {
541                 exit(1);
542         }
543
544 #ifndef NO_THREADS
545         printf("NOTE! for accurate process results please compile with -DNO_THREADS and don't link to -lpthread\n\n");
546 #endif
547
548         for (i=0;i<nprocs;i++) {
549                 char s[30];
550                 sprintf(s, "testd_%d", i);
551                 id_data[i].dname = strdup(s);
552
553                 sprintf(s, "%s/test.dat", id_data[i].dname);
554                 id_data[i].fname = strdup(s);
555
556                 rmdir(id_data[i].dname);
557                 if (mkdir(id_data[i].dname, 0777) != 0) {
558                         fprintf(stderr, "Failed to create %s\n", id_data[i].dname);
559                         exit(1);
560                 }
561
562                 unlink(id_data[i].fname);
563         }
564
565         for (i=0;tests[i].name;i++) {
566                 double t_threads[NREPEATS];
567                 double t_processes[NREPEATS];
568                 int j;
569
570                 if (strcasecmp(tname, "ALL") && strcasecmp(tests[i].name, tname)) {
571                         continue;
572                 }
573
574                 printf("Running test '%s' with %d tasks\n", tests[i].name, nprocs);
575
576                 for (j=0;j<NREPEATS;j++) {
577 #ifndef NO_THREADS
578                         t_threads[j]   = run_threads(nprocs, tests[i].fn);
579 #endif
580                         t_processes[j] = run_processes(nprocs, tests[i].fn);
581                 }
582 #ifndef NO_THREADS
583                 show_result("Threads  ", t_threads, NREPEATS);
584 #endif
585                 show_result("Processes", t_processes, NREPEATS);
586
587                 printf("\n");
588                 fflush(stdout);
589         }
590
591         for (i=0;i<nprocs;i++) {
592                 if (rmdir(id_data[i].dname) != 0) {
593                         fprintf(stderr, "Failed to delete %s\n", id_data[i].dname);
594                         exit(1);
595                 }
596         }
597
598         for (i=0;i<nprocs;i++) {
599                 free(id_data[i].dname);
600                 free(id_data[i].fname);
601         }
602         free(id_data);
603
604         return 0;
605 }