1 /*-
2  * Copyright (c) 2003-2004, 2010 Robert N. M. Watson
3  * All rights reserved.
4  *
5  * Portions of this software were developed at the University of Cambridge
6  * Computer Laboratory with support from a grant from Google, Inc.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD$
30  */
31 
32 #include <sys/param.h>
33 #include <sys/types.h>
34 #include <sys/mman.h>
35 #include <sys/procdesc.h>
36 #include <sys/resource.h>
37 #include <sys/socket.h>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <sys/wait.h>
41 
42 #include <assert.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <inttypes.h>
47 #include <limits.h>
48 #ifdef WITH_PTHREAD
49 #include <pthread.h>
50 #endif
51 #include <semaphore.h>
52 #include <signal.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 static struct timespec ts_start, ts_end;
59 static int alarm_timeout;
60 static volatile int alarm_fired;
61 
62 #define	BENCHMARK_FOREACH(I, NUM) for (I = 0; I < NUM && alarm_fired == 0; I++)
63 
64 static void
65 alarm_handler(int signum __unused)
66 {
67 
68 	alarm_fired = 1;
69 }
70 
71 static void
72 benchmark_start(void)
73 {
74 	int error;
75 
76 	alarm_fired = 0;
77 	if (alarm_timeout) {
78 		signal(SIGALRM, alarm_handler);
79 		alarm(alarm_timeout);
80 	}
81 	error = clock_gettime(CLOCK_REALTIME, &ts_start);
82 	assert(error == 0);
83 }
84 
85 static void
86 benchmark_stop(void)
87 {
88 	int error;
89 
90 	error = clock_gettime(CLOCK_REALTIME, &ts_end);
91 	assert(error == 0);
92 }
93 
94 static uintmax_t
95 test_access(uintmax_t num, uintmax_t int_arg __unused, const char *path)
96 {
97 	uintmax_t i;
98 	int fd;
99 
100 	fd = access(path, O_RDONLY);
101 	if (fd < 0)
102 		err(-1, "test_access: %s", path);
103 	close(fd);
104 
105 	benchmark_start();
106 	BENCHMARK_FOREACH(i, num) {
107 		access(path, O_RDONLY);
108 		close(fd);
109 	}
110 	benchmark_stop();
111 	return (i);
112 }
113 
114 static uintmax_t
115 test_bad_open(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
116 {
117 	uintmax_t i;
118 
119 	benchmark_start();
120 	BENCHMARK_FOREACH(i, num) {
121 		open("", O_RDONLY);
122 	}
123 	benchmark_stop();
124 	return (i);
125 }
126 
127 static uintmax_t
128 test_chroot(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
129 {
130 	uintmax_t i;
131 
132 	if (chroot("/") < 0)
133 		err(-1, "test_chroot: chroot");
134 	benchmark_start();
135 	BENCHMARK_FOREACH(i, num) {
136 		if (chroot("/") < 0)
137 			err(-1, "test_chroot: chroot");
138 	}
139 	benchmark_stop();
140 	return (i);
141 }
142 
143 static uintmax_t
144 test_clock_gettime(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
145 {
146 	struct timespec ts;
147 	uintmax_t i;
148 
149 	benchmark_start();
150 	BENCHMARK_FOREACH(i, num) {
151 		(void)clock_gettime(CLOCK_REALTIME, &ts);
152 	}
153 	benchmark_stop();
154 	return (i);
155 }
156 
157 static uintmax_t
158 test_create_unlink(uintmax_t num, uintmax_t int_arg __unused, const char *path)
159 {
160 	uintmax_t i;
161 	int fd;
162 
163 	(void)unlink(path);
164 	fd = open(path, O_RDWR | O_CREAT, 0600);
165 	if (fd < 0)
166 		err(-1, "test_create_unlink: create: %s", path);
167 	close(fd);
168 	if (unlink(path) < 0)
169 		err(-1, "test_create_unlink: unlink: %s", path);
170 	benchmark_start();
171 	BENCHMARK_FOREACH(i, num) {
172 		fd = open(path, O_RDWR | O_CREAT, 0600);
173 		if (fd < 0)
174 			err(-1, "test_create_unlink: create: %s", path);
175 		close(fd);
176 		if (unlink(path) < 0)
177 			err(-1, "test_create_unlink: unlink: %s", path);
178 	}
179 	benchmark_stop();
180 	return (i);
181 }
182 
183 static uintmax_t
184 test_fork(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
185 {
186 	pid_t pid;
187 	uintmax_t i;
188 
189 	pid = fork();
190 	if (pid < 0)
191 		err(-1, "test_fork: fork");
192 	if (pid == 0)
193 		_exit(0);
194 	if (waitpid(pid, NULL, 0) < 0)
195 		err(-1, "test_fork: waitpid");
196 	benchmark_start();
197 	BENCHMARK_FOREACH(i, num) {
198 		pid = fork();
199 		if (pid < 0)
200 			err(-1, "test_fork: fork");
201 		if (pid == 0)
202 			_exit(0);
203 		if (waitpid(pid, NULL, 0) < 0)
204 			err(-1, "test_fork: waitpid");
205 	}
206 	benchmark_stop();
207 	return (i);
208 }
209 
210 #define	USR_BIN_TRUE	"/usr/bin/true"
211 static char *execve_args[] = { __DECONST(char *, USR_BIN_TRUE), NULL};
212 extern char **environ;
213 
214 static uintmax_t
215 test_fork_exec(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
216 {
217 	pid_t pid;
218 	uintmax_t i;
219 
220 	pid = fork();
221 	if (pid < 0)
222 		err(-1, "test_fork_exec: fork");
223 	if (pid == 0) {
224 		(void)execve(USR_BIN_TRUE, execve_args, environ);
225 		err(-1, "execve");
226 	}
227 	if (waitpid(pid, NULL, 0) < 0)
228 		err(-1, "test_fork: waitpid");
229 	benchmark_start();
230 	BENCHMARK_FOREACH(i, num) {
231 		pid = fork();
232 		if (pid < 0)
233 			err(-1, "test_fork_exec: fork");
234 		if (pid == 0) {
235 			(void)execve(USR_BIN_TRUE, execve_args, environ);
236 			err(-1, "test_fork_exec: execve");
237 		}
238 		if (waitpid(pid, NULL, 0) < 0)
239 			err(-1, "test_fork_exec: waitpid");
240 	}
241 	benchmark_stop();
242 	return (i);
243 }
244 
245 static uintmax_t
246 test_getppid(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
247 {
248 	uintmax_t i;
249 
250 	/*
251 	 * This is process-local, but can change, so will require a
252 	 * lock.
253 	 */
254 	benchmark_start();
255 	BENCHMARK_FOREACH(i, num) {
256 		getppid();
257 	}
258 	benchmark_stop();
259 	return (i);
260 }
261 
262 static uintmax_t
263 test_getpriority(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
264 {
265 	uintmax_t i;
266 
267 	benchmark_start();
268 	BENCHMARK_FOREACH(i, num) {
269 		(void)getpriority(PRIO_PROCESS, 0);
270 	}
271 	benchmark_stop();
272 	return (i);
273 }
274 
275 /*
276  * The point of this one is to figure out the cost of a call into libc,
277  * through PLT, and back.
278  */
279 static uintmax_t
280 test_getprogname(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
281 {
282 	uintmax_t i;
283 
284 	benchmark_start();
285 	BENCHMARK_FOREACH(i, num) {
286 		(void)getprogname();
287 	}
288 	benchmark_stop();
289 	return (i);
290 }
291 
292 static uintmax_t
293 test_getresuid(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
294 {
295 	uid_t ruid, euid, suid;
296 	uintmax_t i;
297 
298 	benchmark_start();
299 	BENCHMARK_FOREACH(i, num) {
300 		(void)getresuid(&ruid, &euid, &suid);
301 	}
302 	benchmark_stop();
303 	return (i);
304 }
305 
306 static uintmax_t
307 test_gettimeofday(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
308 {
309 	struct timeval tv;
310 	uintmax_t i;
311 
312 	benchmark_start();
313 	BENCHMARK_FOREACH(i, num) {
314 		(void)gettimeofday(&tv, NULL);
315 	}
316 	benchmark_stop();
317 	return (i);
318 }
319 
320 static uintmax_t
321 test_getuid(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
322 {
323 	uintmax_t i;
324 
325 	/*
326 	 * Thread-local data should require no locking if system
327 	 * call is MPSAFE.
328 	 */
329 	benchmark_start();
330 	BENCHMARK_FOREACH(i, num) {
331 		getuid();
332 	}
333 	benchmark_stop();
334 	return (i);
335 }
336 
337 static uintmax_t
338 test_memcpy(uintmax_t num, uintmax_t int_arg, const char *path __unused)
339 {
340 	char buf[int_arg], buf2[int_arg];
341 	uintmax_t i;
342 
343 	benchmark_start();
344 	BENCHMARK_FOREACH(i, num) {
345 		/*
346 		 * Copy the memory there and back, to match the total amount
347 		 * moved by pipeping/pipepingtd tests.
348 		 */
349 		memcpy(buf2, buf, int_arg);
350 		memcpy(buf, buf2, int_arg);
351 	}
352 	benchmark_stop();
353 
354 	return (i);
355 }
356 
357 static uintmax_t
358 test_open_close(uintmax_t num, uintmax_t int_arg __unused, const char *path)
359 {
360 	uintmax_t i;
361 	int fd;
362 
363 	fd = open(path, O_RDONLY);
364 	if (fd < 0)
365 		err(-1, "test_open_close: %s", path);
366 	close(fd);
367 
368 	benchmark_start();
369 	BENCHMARK_FOREACH(i, num) {
370 		fd = open(path, O_RDONLY);
371 		if (fd < 0)
372 			err(-1, "test_open_close: %s", path);
373 		close(fd);
374 	}
375 	benchmark_stop();
376 	return (i);
377 }
378 
379 static uintmax_t
380 test_open_read_close(uintmax_t num, uintmax_t int_arg, const char *path)
381 {
382 	char buf[int_arg];
383 	uintmax_t i;
384 	int fd;
385 
386 	fd = open(path, O_RDONLY);
387 	if (fd < 0)
388 		err(-1, "test_open_read_close: %s", path);
389 	(void)read(fd, buf, int_arg);
390 	close(fd);
391 
392 	benchmark_start();
393 	BENCHMARK_FOREACH(i, num) {
394 		fd = open(path, O_RDONLY);
395 		if (fd < 0)
396 			err(-1, "test_open_read_close: %s", path);
397 		(void)read(fd, buf, int_arg);
398 		close(fd);
399 	}
400 	benchmark_stop();
401 	return (i);
402 }
403 
404 static uintmax_t
405 test_pipe(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
406 {
407 	int fd[2];
408 	uintmax_t i;
409 
410 	/*
411 	 * pipe creation is expensive, as it will allocate a new file
412 	 * descriptor, allocate a new pipe, hook it all up, and return.
413 	 * Destroying is also expensive, as we now have to free up
414 	 * the file descriptors and return the pipe.
415 	 */
416 	if (pipe(fd) < 0)
417 		err(-1, "test_pipe: pipe");
418 	close(fd[0]);
419 	close(fd[1]);
420 	benchmark_start();
421 	BENCHMARK_FOREACH(i, num) {
422 		if (pipe(fd) == -1)
423 			err(-1, "test_pipe: pipe");
424 		close(fd[0]);
425 		close(fd[1]);
426 	}
427 	benchmark_stop();
428 	return (i);
429 }
430 
431 static void
432 readx(int fd, char *buf, size_t size)
433 {
434 	ssize_t ret;
435 
436 	do {
437 		ret = read(fd, buf, size);
438 		if (ret == -1)
439 			err(1, "read");
440 		assert((size_t)ret <= size);
441 		size -= ret;
442 		buf += ret;
443 	} while (size > 0);
444 }
445 
446 static void
447 writex(int fd, const char *buf, size_t size)
448 {
449 	ssize_t ret;
450 
451 	do {
452 		ret = write(fd, buf, size);
453 		if (ret == -1)
454 			err(1, "write");
455 		assert((size_t)ret <= size);
456 		size -= ret;
457 		buf += ret;
458 	} while (size > 0);
459 }
460 
461 static uintmax_t
462 test_pipeping(uintmax_t num, uintmax_t int_arg, const char *path __unused)
463 {
464 	char buf[int_arg];
465 	uintmax_t i;
466 	pid_t pid;
467 	int fd[2], procfd;
468 
469 	if (pipe(fd) < 0)
470 		err(-1, "pipe");
471 
472 	pid = pdfork(&procfd, 0);
473 	if (pid < 0)
474 		err(1, "pdfork");
475 
476 	if (pid == 0) {
477 		close(fd[0]);
478 
479 		for (;;) {
480 			readx(fd[1], buf, int_arg);
481 			writex(fd[1], buf, int_arg);
482 		}
483 	}
484 
485 	close(fd[1]);
486 
487 	benchmark_start();
488 	BENCHMARK_FOREACH(i, num) {
489 		writex(fd[0], buf, int_arg);
490 		readx(fd[0], buf, int_arg);
491 	}
492 	benchmark_stop();
493 
494 	close(procfd);
495 	return (i);
496 }
497 
498 #ifdef WITH_PTHREAD
499 struct pipepingtd_ctx {
500 	int		fd;
501 	uintmax_t	int_arg;
502 };
503 
504 static void *
505 pipepingtd_proc(void *arg)
506 {
507 	struct pipepingtd_ctx *ctxp;
508 	int fd;
509 	void *buf;
510 	uintmax_t int_arg;
511 
512 	ctxp = arg;
513 	fd = ctxp->fd;
514 	int_arg = ctxp->int_arg;
515 
516 	buf = malloc(int_arg);
517 	if (buf == NULL)
518 		err(1, "malloc");
519 
520 	for (;;) {
521 		readx(fd, buf, int_arg);
522 		writex(fd, buf, int_arg);
523 	}
524 }
525 
526 static uintmax_t
527 test_pipepingtd(uintmax_t num, uintmax_t int_arg, const char *path __unused)
528 {
529 	struct pipepingtd_ctx ctx;
530 	char buf[int_arg];
531 	pthread_t td;
532 	uintmax_t i;
533 	int error, fd[2];
534 
535 	if (pipe(fd) < 0)
536 		err(-1, "pipe");
537 
538 	ctx.fd = fd[1];
539 	ctx.int_arg = int_arg;
540 
541 	error = pthread_create(&td, NULL, pipepingtd_proc, &ctx);
542 	if (error != 0)
543 		err(1, "pthread_create");
544 
545 	benchmark_start();
546 	BENCHMARK_FOREACH(i, num) {
547 		writex(fd[0], buf, int_arg);
548 		readx(fd[0], buf, int_arg);
549 	}
550 	benchmark_stop();
551 	pthread_cancel(td);
552 
553 	return (i);
554 }
555 #endif /* WITH_PTHREAD */
556 
557 static uintmax_t
558 test_read(uintmax_t num, uintmax_t int_arg, const char *path)
559 {
560 	char buf[int_arg];
561 	uintmax_t i;
562 	int fd;
563 
564 	fd = open(path, O_RDONLY);
565 	if (fd < 0)
566 		err(-1, "test_open_read: %s", path);
567 	(void)pread(fd, buf, int_arg, 0);
568 
569 	benchmark_start();
570 	BENCHMARK_FOREACH(i, num) {
571 		(void)pread(fd, buf, int_arg, 0);
572 	}
573 	benchmark_stop();
574 	close(fd);
575 	return (i);
576 }
577 
578 static uintmax_t
579 test_select(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
580 {
581 	fd_set readfds, writefds, exceptfds;
582 	struct timeval tv;
583 	uintmax_t i;
584 
585 	FD_ZERO(&readfds);
586 	FD_ZERO(&writefds);
587 	FD_ZERO(&exceptfds);
588 
589 	tv.tv_sec = 0;
590 	tv.tv_usec = 0;
591 
592 	benchmark_start();
593 	BENCHMARK_FOREACH(i, num) {
594 		(void)select(0, &readfds, &writefds, &exceptfds, &tv);
595 	}
596 	benchmark_stop();
597 	return (i);
598 }
599 
600 static uintmax_t
601 test_semaping(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
602 {
603 	uintmax_t i;
604 	pid_t pid;
605 	sem_t *buf;
606 	int error, j, procfd;
607 
608 	buf = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
609 	if (buf == MAP_FAILED)
610 		err(1, "mmap");
611 
612 	for (j = 0; j < 2; j++) {
613 		error = sem_init(&buf[j], 1, 0);
614 		if (error != 0)
615 			err(1, "sem_init");
616 	}
617 
618 	pid = pdfork(&procfd, 0);
619 	if (pid < 0)
620 		err(1, "pdfork");
621 
622 	if (pid == 0) {
623 		for (;;) {
624 			error = sem_wait(&buf[0]);
625 			if (error != 0)
626 				err(1, "sem_wait");
627 			error = sem_post(&buf[1]);
628 			if (error != 0)
629 				err(1, "sem_post");
630 		}
631 	}
632 
633 	benchmark_start();
634 	BENCHMARK_FOREACH(i, num) {
635 		error = sem_post(&buf[0]);
636 		if (error != 0)
637 			err(1, "sem_post");
638 		error = sem_wait(&buf[1]);
639 		if (error != 0)
640 			err(1, "sem_wait");
641 	}
642 	benchmark_stop();
643 
644 	close(procfd);
645 
646 	for (j = 0; j < 2; j++) {
647 		error = sem_destroy(&buf[j]);
648 		if (error != 0)
649 			err(1, "sem_destroy");
650 	}
651 
652 	error = munmap(buf, PAGE_SIZE);
653 	if (error != 0)
654 		err(1, "munmap");
655 
656 	return (i);
657 }
658 
659 static uintmax_t
660 test_setuid(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
661 {
662 	uid_t uid;
663 	uintmax_t i;
664 
665 	uid = getuid();
666 	if (setuid(uid) < 0)
667 		err(-1, "test_setuid: setuid");
668 	benchmark_start();
669 	BENCHMARK_FOREACH(i, num) {
670 		if (setuid(uid) < 0)
671 			err(-1, "test_setuid: setuid");
672 	}
673 	benchmark_stop();
674 	return (i);
675 }
676 
677 static uintmax_t
678 test_shmfd(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
679 {
680 	uintmax_t i;
681 	int shmfd;
682 
683 	shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
684 	if (shmfd < 0)
685 		err(-1, "test_shmfd: shm_open");
686 	close(shmfd);
687 	benchmark_start();
688 	BENCHMARK_FOREACH(i, num) {
689 		shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
690 		if (shmfd < 0)
691 			err(-1, "test_shmfd: shm_open");
692 		close(shmfd);
693 	}
694 	benchmark_stop();
695 	return (i);
696 }
697 
698 static uintmax_t
699 test_shmfd_dup(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
700 {
701 	uintmax_t i;
702 	int fd, shmfd;
703 
704 	shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
705 	if (shmfd < 0)
706 		err(-1, "test_shmfd_dup: shm_open");
707 	fd = dup(shmfd);
708 	if (fd >= 0)
709 		close(fd);
710 	benchmark_start();
711 	BENCHMARK_FOREACH(i, num) {
712 		fd = dup(shmfd);
713 		if (fd >= 0)
714 			close(fd);
715 	}
716 	benchmark_stop();
717 	close(shmfd);
718 	return (i);
719 }
720 
721 static uintmax_t
722 test_shmfd_fstat(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
723 {
724 	struct stat sb;
725 	uintmax_t i;
726 	int shmfd;
727 
728 	shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
729 	if (shmfd < 0)
730 		err(-1, "test_shmfd_fstat: shm_open");
731 	if (fstat(shmfd, &sb) < 0)
732 		err(-1, "test_shmfd_fstat: fstat");
733 	benchmark_start();
734 	BENCHMARK_FOREACH(i, num) {
735 		(void)fstat(shmfd, &sb);
736 	}
737 	benchmark_stop();
738 	close(shmfd);
739 	return (i);
740 }
741 
742 static uintmax_t
743 test_socket_stream(uintmax_t num, uintmax_t int_arg, const char *path __unused)
744 {
745 	uintmax_t i;
746 	int so;
747 
748 	so = socket(int_arg, SOCK_STREAM, 0);
749 	if (so < 0)
750 		err(-1, "test_socket_stream: socket");
751 	close(so);
752 	benchmark_start();
753 	BENCHMARK_FOREACH(i, num) {
754 		so = socket(int_arg, SOCK_STREAM, 0);
755 		if (so == -1)
756 			err(-1, "test_socket_stream: socket");
757 		close(so);
758 	}
759 	benchmark_stop();
760 	return (i);
761 }
762 
763 static uintmax_t
764 test_socket_dgram(uintmax_t num, uintmax_t int_arg, const char *path __unused)
765 {
766 	uintmax_t i;
767 	int so;
768 
769 	so = socket(int_arg, SOCK_DGRAM, 0);
770 	if (so < 0)
771 		err(-1, "test_socket_dgram: socket");
772 	close(so);
773 	benchmark_start();
774 	BENCHMARK_FOREACH(i, num) {
775 		so = socket(int_arg, SOCK_DGRAM, 0);
776 		if (so == -1)
777 			err(-1, "test_socket_dgram: socket");
778 		close(so);
779 	}
780 	benchmark_stop();
781 	return (i);
782 }
783 
784 static uintmax_t
785 test_socketpair_stream(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
786 {
787 	uintmax_t i;
788 	int so[2];
789 
790 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, so) == -1)
791 		err(-1, "test_socketpair_stream: socketpair");
792 	close(so[0]);
793 	close(so[1]);
794 	benchmark_start();
795 	BENCHMARK_FOREACH(i, num) {
796 		if (socketpair(PF_LOCAL, SOCK_STREAM, 0, so) == -1)
797 			err(-1, "test_socketpair_stream: socketpair");
798 		close(so[0]);
799 		close(so[1]);
800 	}
801 	benchmark_stop();
802 	return (i);
803 }
804 
805 static uintmax_t
806 test_socketpair_dgram(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
807 {
808 	uintmax_t i;
809 	int so[2];
810 
811 	if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, so) == -1)
812 		err(-1, "test_socketpair_dgram: socketpair");
813 	close(so[0]);
814 	close(so[1]);
815 	benchmark_start();
816 	BENCHMARK_FOREACH(i, num) {
817 		if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, so) == -1)
818 			err(-1, "test_socketpair_dgram: socketpair");
819 		close(so[0]);
820 		close(so[1]);
821 	}
822 	benchmark_stop();
823 	return (i);
824 }
825 
826 static uintmax_t
827 test_vfork(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
828 {
829 	pid_t pid;
830 	uintmax_t i;
831 
832 	pid = vfork();
833 	if (pid < 0)
834 		err(-1, "test_vfork: vfork");
835 	if (pid == 0)
836 		_exit(0);
837 	if (waitpid(pid, NULL, 0) < 0)
838 		err(-1, "test_vfork: waitpid");
839 	benchmark_start();
840 	BENCHMARK_FOREACH(i, num) {
841 		pid = vfork();
842 		if (pid < 0)
843 			err(-1, "test_vfork: vfork");
844 		if (pid == 0)
845 			_exit(0);
846 		if (waitpid(pid, NULL, 0) < 0)
847 			err(-1, "test_vfork: waitpid");
848 	}
849 	benchmark_stop();
850 	return (i);
851 }
852 
853 static uintmax_t
854 test_vfork_exec(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
855 {
856 	pid_t pid;
857 	uintmax_t i;
858 
859 	pid = vfork();
860 	if (pid < 0)
861 		err(-1, "test_vfork_exec: vfork");
862 	if (pid == 0) {
863 		(void)execve(USR_BIN_TRUE, execve_args, environ);
864 		err(-1, "test_vfork_exec: execve");
865 	}
866 	if (waitpid(pid, NULL, 0) < 0)
867 		err(-1, "test_vfork_exec: waitpid");
868 	benchmark_start();
869 	BENCHMARK_FOREACH(i, num) {
870 		pid = vfork();
871 		if (pid < 0)
872 			err(-1, "test_vfork_exec: vfork");
873 		if (pid == 0) {
874 			(void)execve(USR_BIN_TRUE, execve_args, environ);
875 			err(-1, "execve");
876 		}
877 		if (waitpid(pid, NULL, 0) < 0)
878 			err(-1, "test_vfork_exec: waitpid");
879 	}
880 	benchmark_stop();
881 	return (i);
882 }
883 
884 struct test {
885 	const char	*t_name;
886 	uintmax_t	(*t_func)(uintmax_t, uintmax_t, const char *);
887 	int		 t_flags;
888 	uintmax_t	 t_int;
889 };
890 
891 #define	FLAG_PATH	0x00000001
892 
893 static const struct test tests[] = {
894 	{ "access", test_access, .t_flags = FLAG_PATH },
895 	{ "bad_open", test_bad_open, .t_flags = 0 },
896 	{ "chroot", test_chroot, .t_flags = 0 },
897 	{ "clock_gettime", test_clock_gettime, .t_flags = 0 },
898 	{ "create_unlink", test_create_unlink, .t_flags = FLAG_PATH },
899 	{ "fork", test_fork, .t_flags = 0 },
900 	{ "fork_exec", test_fork_exec, .t_flags = 0 },
901 	{ "getppid", test_getppid, .t_flags = 0 },
902 	{ "getpriority", test_getpriority, .t_flags = 0 },
903 	{ "getprogname", test_getprogname, .t_flags = 0 },
904 	{ "getresuid", test_getresuid, .t_flags = 0 },
905 	{ "gettimeofday", test_gettimeofday, .t_flags = 0 },
906 	{ "getuid", test_getuid, .t_flags = 0 },
907 	{ "memcpy_1", test_memcpy, .t_flags = 0, .t_int = 1 },
908 	{ "memcpy_10", test_memcpy, .t_flags = 0, .t_int = 10 },
909 	{ "memcpy_100", test_memcpy, .t_flags = 0, .t_int = 100 },
910 	{ "memcpy_1000", test_memcpy, .t_flags = 0, .t_int = 1000 },
911 	{ "memcpy_10000", test_memcpy, .t_flags = 0, .t_int = 10000 },
912 	{ "memcpy_100000", test_memcpy, .t_flags = 0, .t_int = 100000 },
913 	{ "memcpy_1000000", test_memcpy, .t_flags = 0, .t_int = 1000000 },
914 	{ "open_close", test_open_close, .t_flags = FLAG_PATH },
915 	{ "open_read_close_1", test_open_read_close, .t_flags = FLAG_PATH,
916 	    .t_int = 1 },
917 	{ "open_read_close_10", test_open_read_close, .t_flags = FLAG_PATH,
918 	    .t_int = 10 },
919 	{ "open_read_close_100", test_open_read_close, .t_flags = FLAG_PATH,
920 	    .t_int = 100 },
921 	{ "open_read_close_1000", test_open_read_close, .t_flags = FLAG_PATH,
922 	    .t_int = 1000 },
923 	{ "open_read_close_10000", test_open_read_close,
924 	    .t_flags = FLAG_PATH, .t_int = 10000 },
925 	{ "open_read_close_100000", test_open_read_close,
926 	    .t_flags = FLAG_PATH, .t_int = 100000 },
927 	{ "open_read_close_1000000", test_open_read_close,
928 	    .t_flags = FLAG_PATH, .t_int = 1000000 },
929 	{ "pipe", test_pipe, .t_flags = 0 },
930 	{ "pipeping_1", test_pipeping, .t_flags = 0, .t_int = 1 },
931 	{ "pipeping_10", test_pipeping, .t_flags = 0, .t_int = 10 },
932 	{ "pipeping_100", test_pipeping, .t_flags = 0, .t_int = 100 },
933 	{ "pipeping_1000", test_pipeping, .t_flags = 0, .t_int = 1000 },
934 	{ "pipeping_10000", test_pipeping, .t_flags = 0, .t_int = 10000 },
935 	{ "pipeping_100000", test_pipeping, .t_flags = 0, .t_int = 100000 },
936 	{ "pipeping_1000000", test_pipeping, .t_flags = 0, .t_int = 1000000 },
937 #ifdef WITH_PTHREAD
938 	{ "pipepingtd_1", test_pipepingtd, .t_flags = 0, .t_int = 1 },
939 	{ "pipepingtd_10", test_pipepingtd, .t_flags = 0, .t_int = 10 },
940 	{ "pipepingtd_100", test_pipepingtd, .t_flags = 0, .t_int = 100 },
941 	{ "pipepingtd_1000", test_pipepingtd, .t_flags = 0, .t_int = 1000 },
942 	{ "pipepingtd_10000", test_pipepingtd, .t_flags = 0, .t_int = 10000 },
943 	{ "pipepingtd_100000", test_pipepingtd, .t_flags = 0, .t_int = 100000 },
944 	{ "pipepingtd_1000000", test_pipepingtd, .t_flags = 0, .t_int = 1000000 },
945 #endif
946 	{ "read_1", test_read, .t_flags = FLAG_PATH, .t_int = 1 },
947 	{ "read_10", test_read, .t_flags = FLAG_PATH, .t_int = 10 },
948 	{ "read_100", test_read, .t_flags = FLAG_PATH, .t_int = 100 },
949 	{ "read_1000", test_read, .t_flags = FLAG_PATH, .t_int = 1000 },
950 	{ "read_10000", test_read, .t_flags = FLAG_PATH, .t_int = 10000 },
951 	{ "read_100000", test_read, .t_flags = FLAG_PATH, .t_int = 100000 },
952 	{ "read_1000000", test_read, .t_flags = FLAG_PATH, .t_int = 1000000 },
953 	{ "select", test_select, .t_flags = 0 },
954 	{ "semaping", test_semaping, .t_flags = 0 },
955 	{ "setuid", test_setuid, .t_flags = 0 },
956 	{ "shmfd", test_shmfd, .t_flags = 0 },
957 	{ "shmfd_dup", test_shmfd_dup, .t_flags = 0 },
958 	{ "shmfd_fstat", test_shmfd_fstat, .t_flags = 0 },
959 	{ "socket_local_stream", test_socket_stream, .t_int = PF_LOCAL },
960 	{ "socket_local_dgram", test_socket_dgram, .t_int = PF_LOCAL },
961 	{ "socketpair_stream", test_socketpair_stream, .t_flags = 0 },
962 	{ "socketpair_dgram", test_socketpair_dgram, .t_flags = 0 },
963 	{ "socket_tcp", test_socket_stream, .t_int = PF_INET },
964 	{ "socket_udp", test_socket_dgram, .t_int = PF_INET },
965 	{ "vfork", test_vfork, .t_flags = 0 },
966 	{ "vfork_exec", test_vfork_exec, .t_flags = 0 },
967 };
968 static const int tests_count = sizeof(tests) / sizeof(tests[0]);
969 
970 static void
971 usage(void)
972 {
973 	int i;
974 
975 	fprintf(stderr, "syscall_timing [-i iterations] [-l loops] "
976 	    "[-p path] [-s seconds] test\n");
977 	for (i = 0; i < tests_count; i++)
978 		fprintf(stderr, "  %s\n", tests[i].t_name);
979 	exit(-1);
980 }
981 
982 int
983 main(int argc, char *argv[])
984 {
985 	struct timespec ts_res;
986 	const struct test *the_test;
987 	const char *path;
988 	char *tmp_dir, *tmp_path;
989 	long long ll;
990 	char *endp;
991 	int ch, fd, error, i, j, rv;
992 	uintmax_t iterations, k, loops;
993 
994 	alarm_timeout = 1;
995 	iterations = 0;
996 	loops = 10;
997 	path = NULL;
998 	tmp_path = NULL;
999 	while ((ch = getopt(argc, argv, "i:l:p:s:")) != -1) {
1000 		switch (ch) {
1001 		case 'i':
1002 			ll = strtol(optarg, &endp, 10);
1003 			if (*endp != 0 || ll < 1)
1004 				usage();
1005 			iterations = ll;
1006 			break;
1007 
1008 		case 'l':
1009 			ll = strtol(optarg, &endp, 10);
1010 			if (*endp != 0 || ll < 1 || ll > 100000)
1011 				usage();
1012 			loops = ll;
1013 			break;
1014 
1015 		case 'p':
1016 			path = optarg;
1017 			break;
1018 
1019 		case 's':
1020 			ll = strtol(optarg, &endp, 10);
1021 			if (*endp != 0 || ll < 1 || ll > 60*60)
1022 				usage();
1023 			alarm_timeout = ll;
1024 			break;
1025 
1026 		case '?':
1027 		default:
1028 			usage();
1029 		}
1030 	}
1031 	argc -= optind;
1032 	argv += optind;
1033 
1034 	if (iterations < 1 && alarm_timeout < 1)
1035 		usage();
1036 	if (iterations < 1)
1037 		iterations = UINT64_MAX;
1038 	if (loops < 1)
1039 		loops = 1;
1040 
1041 	if (argc < 1)
1042 		usage();
1043 
1044 	/*
1045 	 * Validate test list and that, if a path is required, it is
1046 	 * defined.
1047 	 */
1048 	for (j = 0; j < argc; j++) {
1049 		the_test = NULL;
1050 		for (i = 0; i < tests_count; i++) {
1051 			if (strcmp(argv[j], tests[i].t_name) == 0)
1052 				the_test = &tests[i];
1053 		}
1054 		if (the_test == NULL)
1055 			usage();
1056 		if ((the_test->t_flags & FLAG_PATH) && (path == NULL)) {
1057 			tmp_dir = strdup("/tmp/syscall_timing.XXXXXXXX");
1058 			if (tmp_dir == NULL)
1059 				err(1, "strdup");
1060 			tmp_dir = mkdtemp(tmp_dir);
1061 			if (tmp_dir == NULL)
1062 				err(1, "mkdtemp");
1063 			rv = asprintf(&tmp_path, "%s/testfile", tmp_dir);
1064 			if (rv <= 0)
1065 				err(1, "asprintf");
1066 		}
1067 	}
1068 
1069 	error = clock_getres(CLOCK_REALTIME, &ts_res);
1070 	assert(error == 0);
1071 	printf("Clock resolution: %ju.%09ju\n", (uintmax_t)ts_res.tv_sec,
1072 	    (uintmax_t)ts_res.tv_nsec);
1073 	printf("test\tloop\ttime\titerations\tperiteration\n");
1074 
1075 	for (j = 0; j < argc; j++) {
1076 		uintmax_t calls, nsecsperit;
1077 
1078 		the_test = NULL;
1079 		for (i = 0; i < tests_count; i++) {
1080 			if (strcmp(argv[j], tests[i].t_name) == 0)
1081 				the_test = &tests[i];
1082 		}
1083 
1084 		if (tmp_path != NULL) {
1085 			fd = open(tmp_path, O_WRONLY | O_CREAT, 0700);
1086 			if (fd < 0)
1087 				err(1, "cannot open %s", tmp_path);
1088 			error = ftruncate(fd, 1000000);
1089 			if (error != 0)
1090 				err(1, "ftruncate");
1091 			error = close(fd);
1092 			if (error != 0)
1093 				err(1, "close");
1094 			path = tmp_path;
1095 		}
1096 
1097 		/*
1098 		 * Run one warmup, then do the real thing (loops) times.
1099 		 */
1100 		the_test->t_func(iterations, the_test->t_int, path);
1101 		calls = 0;
1102 		for (k = 0; k < loops; k++) {
1103 			calls = the_test->t_func(iterations, the_test->t_int,
1104 			    path);
1105 			timespecsub(&ts_end, &ts_start, &ts_end);
1106 			printf("%s\t%ju\t", the_test->t_name, k);
1107 			printf("%ju.%09ju\t%ju\t", (uintmax_t)ts_end.tv_sec,
1108 			    (uintmax_t)ts_end.tv_nsec, calls);
1109 
1110 		/*
1111 		 * Note.  This assumes that each iteration takes less than
1112 		 * a second, and that our total nanoseconds doesn't exceed
1113 		 * the room in our arithmetic unit.  Fine for system calls,
1114 		 * but not for long things.
1115 		 */
1116 			nsecsperit = ts_end.tv_sec * 1000000000;
1117 			nsecsperit += ts_end.tv_nsec;
1118 			nsecsperit /= calls;
1119 			printf("0.%09ju\n", (uintmax_t)nsecsperit);
1120 		}
1121 	}
1122 
1123 	if (tmp_path != NULL) {
1124 		error = unlink(tmp_path);
1125 		if (error != 0 && errno != ENOENT)
1126 			warn("cannot unlink %s", tmp_path);
1127 		error = rmdir(tmp_dir);
1128 		if (error != 0)
1129 			warn("cannot rmdir %s", tmp_dir);
1130 	}
1131 
1132 	return (0);
1133 }
1134