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_lstat(uintmax_t num, uintmax_t int_arg __unused, const char *path)
339 {
340 	struct stat sb;
341 	uintmax_t i;
342 	int error;
343 
344 	benchmark_start();
345 	BENCHMARK_FOREACH(i, num) {
346 		error = lstat(path, &sb);
347 		if (error != 0)
348 			err(-1, "lstat");
349 	}
350 	benchmark_stop();
351 	return (i);
352 }
353 
354 static uintmax_t
355 test_memcpy(uintmax_t num, uintmax_t int_arg, const char *path __unused)
356 {
357 	char buf[int_arg], buf2[int_arg];
358 	uintmax_t i;
359 
360 	benchmark_start();
361 	BENCHMARK_FOREACH(i, num) {
362 		/*
363 		 * Copy the memory there and back, to match the total amount
364 		 * moved by pipeping/pipepingtd tests.
365 		 */
366 		memcpy(buf2, buf, int_arg);
367 		memcpy(buf, buf2, int_arg);
368 	}
369 	benchmark_stop();
370 
371 	return (i);
372 }
373 
374 static uintmax_t
375 test_open_close(uintmax_t num, uintmax_t int_arg __unused, const char *path)
376 {
377 	uintmax_t i;
378 	int fd;
379 
380 	fd = open(path, O_RDONLY);
381 	if (fd < 0)
382 		err(-1, "test_open_close: %s", path);
383 	close(fd);
384 
385 	benchmark_start();
386 	BENCHMARK_FOREACH(i, num) {
387 		fd = open(path, O_RDONLY);
388 		if (fd < 0)
389 			err(-1, "test_open_close: %s", path);
390 		close(fd);
391 	}
392 	benchmark_stop();
393 	return (i);
394 }
395 
396 static uintmax_t
397 test_open_read_close(uintmax_t num, uintmax_t int_arg, const char *path)
398 {
399 	char buf[int_arg];
400 	uintmax_t i;
401 	int fd;
402 
403 	fd = open(path, O_RDONLY);
404 	if (fd < 0)
405 		err(-1, "test_open_read_close: %s", path);
406 	(void)read(fd, buf, int_arg);
407 	close(fd);
408 
409 	benchmark_start();
410 	BENCHMARK_FOREACH(i, num) {
411 		fd = open(path, O_RDONLY);
412 		if (fd < 0)
413 			err(-1, "test_open_read_close: %s", path);
414 		(void)read(fd, buf, int_arg);
415 		close(fd);
416 	}
417 	benchmark_stop();
418 	return (i);
419 }
420 
421 static uintmax_t
422 test_pipe(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
423 {
424 	int fd[2];
425 	uintmax_t i;
426 
427 	/*
428 	 * pipe creation is expensive, as it will allocate a new file
429 	 * descriptor, allocate a new pipe, hook it all up, and return.
430 	 * Destroying is also expensive, as we now have to free up
431 	 * the file descriptors and return the pipe.
432 	 */
433 	if (pipe(fd) < 0)
434 		err(-1, "test_pipe: pipe");
435 	close(fd[0]);
436 	close(fd[1]);
437 	benchmark_start();
438 	BENCHMARK_FOREACH(i, num) {
439 		if (pipe(fd) == -1)
440 			err(-1, "test_pipe: pipe");
441 		close(fd[0]);
442 		close(fd[1]);
443 	}
444 	benchmark_stop();
445 	return (i);
446 }
447 
448 static void
449 readx(int fd, char *buf, size_t size)
450 {
451 	ssize_t ret;
452 
453 	do {
454 		ret = read(fd, buf, size);
455 		if (ret == -1)
456 			err(1, "read");
457 		assert((size_t)ret <= size);
458 		size -= ret;
459 		buf += ret;
460 	} while (size > 0);
461 }
462 
463 static void
464 writex(int fd, const char *buf, size_t size)
465 {
466 	ssize_t ret;
467 
468 	do {
469 		ret = write(fd, buf, size);
470 		if (ret == -1)
471 			err(1, "write");
472 		assert((size_t)ret <= size);
473 		size -= ret;
474 		buf += ret;
475 	} while (size > 0);
476 }
477 
478 static uintmax_t
479 test_pipeping(uintmax_t num, uintmax_t int_arg, const char *path __unused)
480 {
481 	char buf[int_arg];
482 	uintmax_t i;
483 	pid_t pid;
484 	int fd[2], procfd;
485 
486 	if (pipe(fd) < 0)
487 		err(-1, "pipe");
488 
489 	pid = pdfork(&procfd, 0);
490 	if (pid < 0)
491 		err(1, "pdfork");
492 
493 	if (pid == 0) {
494 		close(fd[0]);
495 
496 		for (;;) {
497 			readx(fd[1], buf, int_arg);
498 			writex(fd[1], buf, int_arg);
499 		}
500 	}
501 
502 	close(fd[1]);
503 
504 	benchmark_start();
505 	BENCHMARK_FOREACH(i, num) {
506 		writex(fd[0], buf, int_arg);
507 		readx(fd[0], buf, int_arg);
508 	}
509 	benchmark_stop();
510 
511 	close(procfd);
512 	return (i);
513 }
514 
515 #ifdef WITH_PTHREAD
516 struct pipepingtd_ctx {
517 	int		fd;
518 	uintmax_t	int_arg;
519 };
520 
521 static void *
522 pipepingtd_proc(void *arg)
523 {
524 	struct pipepingtd_ctx *ctxp;
525 	int fd;
526 	void *buf;
527 	uintmax_t int_arg;
528 
529 	ctxp = arg;
530 	fd = ctxp->fd;
531 	int_arg = ctxp->int_arg;
532 
533 	buf = malloc(int_arg);
534 	if (buf == NULL)
535 		err(1, "malloc");
536 
537 	for (;;) {
538 		readx(fd, buf, int_arg);
539 		writex(fd, buf, int_arg);
540 	}
541 }
542 
543 static uintmax_t
544 test_pipepingtd(uintmax_t num, uintmax_t int_arg, const char *path __unused)
545 {
546 	struct pipepingtd_ctx ctx;
547 	char buf[int_arg];
548 	pthread_t td;
549 	uintmax_t i;
550 	int error, fd[2];
551 
552 	if (pipe(fd) < 0)
553 		err(-1, "pipe");
554 
555 	ctx.fd = fd[1];
556 	ctx.int_arg = int_arg;
557 
558 	error = pthread_create(&td, NULL, pipepingtd_proc, &ctx);
559 	if (error != 0)
560 		err(1, "pthread_create");
561 
562 	benchmark_start();
563 	BENCHMARK_FOREACH(i, num) {
564 		writex(fd[0], buf, int_arg);
565 		readx(fd[0], buf, int_arg);
566 	}
567 	benchmark_stop();
568 	pthread_cancel(td);
569 
570 	return (i);
571 }
572 #endif /* WITH_PTHREAD */
573 
574 static uintmax_t
575 test_read(uintmax_t num, uintmax_t int_arg, const char *path)
576 {
577 	char buf[int_arg];
578 	uintmax_t i;
579 	int fd;
580 
581 	fd = open(path, O_RDONLY);
582 	if (fd < 0)
583 		err(-1, "test_open_read: %s", path);
584 	(void)pread(fd, buf, int_arg, 0);
585 
586 	benchmark_start();
587 	BENCHMARK_FOREACH(i, num) {
588 		(void)pread(fd, buf, int_arg, 0);
589 	}
590 	benchmark_stop();
591 	close(fd);
592 	return (i);
593 }
594 
595 static uintmax_t
596 test_select(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
597 {
598 	fd_set readfds, writefds, exceptfds;
599 	struct timeval tv;
600 	uintmax_t i;
601 
602 	FD_ZERO(&readfds);
603 	FD_ZERO(&writefds);
604 	FD_ZERO(&exceptfds);
605 
606 	tv.tv_sec = 0;
607 	tv.tv_usec = 0;
608 
609 	benchmark_start();
610 	BENCHMARK_FOREACH(i, num) {
611 		(void)select(0, &readfds, &writefds, &exceptfds, &tv);
612 	}
613 	benchmark_stop();
614 	return (i);
615 }
616 
617 static uintmax_t
618 test_semaping(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
619 {
620 	uintmax_t i;
621 	pid_t pid;
622 	sem_t *buf;
623 	int error, j, procfd;
624 
625 	buf = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
626 	if (buf == MAP_FAILED)
627 		err(1, "mmap");
628 
629 	for (j = 0; j < 2; j++) {
630 		error = sem_init(&buf[j], 1, 0);
631 		if (error != 0)
632 			err(1, "sem_init");
633 	}
634 
635 	pid = pdfork(&procfd, 0);
636 	if (pid < 0)
637 		err(1, "pdfork");
638 
639 	if (pid == 0) {
640 		for (;;) {
641 			error = sem_wait(&buf[0]);
642 			if (error != 0)
643 				err(1, "sem_wait");
644 			error = sem_post(&buf[1]);
645 			if (error != 0)
646 				err(1, "sem_post");
647 		}
648 	}
649 
650 	benchmark_start();
651 	BENCHMARK_FOREACH(i, num) {
652 		error = sem_post(&buf[0]);
653 		if (error != 0)
654 			err(1, "sem_post");
655 		error = sem_wait(&buf[1]);
656 		if (error != 0)
657 			err(1, "sem_wait");
658 	}
659 	benchmark_stop();
660 
661 	close(procfd);
662 
663 	for (j = 0; j < 2; j++) {
664 		error = sem_destroy(&buf[j]);
665 		if (error != 0)
666 			err(1, "sem_destroy");
667 	}
668 
669 	error = munmap(buf, PAGE_SIZE);
670 	if (error != 0)
671 		err(1, "munmap");
672 
673 	return (i);
674 }
675 
676 static uintmax_t
677 test_setuid(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
678 {
679 	uid_t uid;
680 	uintmax_t i;
681 
682 	uid = getuid();
683 	if (setuid(uid) < 0)
684 		err(-1, "test_setuid: setuid");
685 	benchmark_start();
686 	BENCHMARK_FOREACH(i, num) {
687 		if (setuid(uid) < 0)
688 			err(-1, "test_setuid: setuid");
689 	}
690 	benchmark_stop();
691 	return (i);
692 }
693 
694 static uintmax_t
695 test_shmfd(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
696 {
697 	uintmax_t i;
698 	int shmfd;
699 
700 	shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
701 	if (shmfd < 0)
702 		err(-1, "test_shmfd: shm_open");
703 	close(shmfd);
704 	benchmark_start();
705 	BENCHMARK_FOREACH(i, num) {
706 		shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
707 		if (shmfd < 0)
708 			err(-1, "test_shmfd: shm_open");
709 		close(shmfd);
710 	}
711 	benchmark_stop();
712 	return (i);
713 }
714 
715 static uintmax_t
716 test_shmfd_dup(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
717 {
718 	uintmax_t i;
719 	int fd, shmfd;
720 
721 	shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
722 	if (shmfd < 0)
723 		err(-1, "test_shmfd_dup: shm_open");
724 	fd = dup(shmfd);
725 	if (fd >= 0)
726 		close(fd);
727 	benchmark_start();
728 	BENCHMARK_FOREACH(i, num) {
729 		fd = dup(shmfd);
730 		if (fd >= 0)
731 			close(fd);
732 	}
733 	benchmark_stop();
734 	close(shmfd);
735 	return (i);
736 }
737 
738 static uintmax_t
739 test_shmfd_fstat(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
740 {
741 	struct stat sb;
742 	uintmax_t i;
743 	int shmfd;
744 
745 	shmfd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
746 	if (shmfd < 0)
747 		err(-1, "test_shmfd_fstat: shm_open");
748 	if (fstat(shmfd, &sb) < 0)
749 		err(-1, "test_shmfd_fstat: fstat");
750 	benchmark_start();
751 	BENCHMARK_FOREACH(i, num) {
752 		(void)fstat(shmfd, &sb);
753 	}
754 	benchmark_stop();
755 	close(shmfd);
756 	return (i);
757 }
758 
759 static uintmax_t
760 test_socket_stream(uintmax_t num, uintmax_t int_arg, const char *path __unused)
761 {
762 	uintmax_t i;
763 	int so;
764 
765 	so = socket(int_arg, SOCK_STREAM, 0);
766 	if (so < 0)
767 		err(-1, "test_socket_stream: socket");
768 	close(so);
769 	benchmark_start();
770 	BENCHMARK_FOREACH(i, num) {
771 		so = socket(int_arg, SOCK_STREAM, 0);
772 		if (so == -1)
773 			err(-1, "test_socket_stream: socket");
774 		close(so);
775 	}
776 	benchmark_stop();
777 	return (i);
778 }
779 
780 static uintmax_t
781 test_socket_dgram(uintmax_t num, uintmax_t int_arg, const char *path __unused)
782 {
783 	uintmax_t i;
784 	int so;
785 
786 	so = socket(int_arg, SOCK_DGRAM, 0);
787 	if (so < 0)
788 		err(-1, "test_socket_dgram: socket");
789 	close(so);
790 	benchmark_start();
791 	BENCHMARK_FOREACH(i, num) {
792 		so = socket(int_arg, SOCK_DGRAM, 0);
793 		if (so == -1)
794 			err(-1, "test_socket_dgram: socket");
795 		close(so);
796 	}
797 	benchmark_stop();
798 	return (i);
799 }
800 
801 static uintmax_t
802 test_socketpair_stream(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
803 {
804 	uintmax_t i;
805 	int so[2];
806 
807 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, so) == -1)
808 		err(-1, "test_socketpair_stream: socketpair");
809 	close(so[0]);
810 	close(so[1]);
811 	benchmark_start();
812 	BENCHMARK_FOREACH(i, num) {
813 		if (socketpair(PF_LOCAL, SOCK_STREAM, 0, so) == -1)
814 			err(-1, "test_socketpair_stream: socketpair");
815 		close(so[0]);
816 		close(so[1]);
817 	}
818 	benchmark_stop();
819 	return (i);
820 }
821 
822 static uintmax_t
823 test_socketpair_dgram(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
824 {
825 	uintmax_t i;
826 	int so[2];
827 
828 	if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, so) == -1)
829 		err(-1, "test_socketpair_dgram: socketpair");
830 	close(so[0]);
831 	close(so[1]);
832 	benchmark_start();
833 	BENCHMARK_FOREACH(i, num) {
834 		if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, so) == -1)
835 			err(-1, "test_socketpair_dgram: socketpair");
836 		close(so[0]);
837 		close(so[1]);
838 	}
839 	benchmark_stop();
840 	return (i);
841 }
842 
843 static uintmax_t
844 test_readlink(uintmax_t num, uintmax_t int_arg __unused, const char *path)
845 {
846 	char buf[PATH_MAX];
847 	ssize_t rv;
848 	uintmax_t i;
849 
850 	benchmark_start();
851 	BENCHMARK_FOREACH(i, num) {
852 		rv = readlink(path, buf, sizeof(buf));
853 		if (rv < 0 && errno != EINVAL)
854 			err(-1, "readlink");
855 	}
856 	benchmark_stop();
857 	return (i);
858 }
859 
860 static uintmax_t
861 test_vfork(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
862 {
863 	pid_t pid;
864 	uintmax_t i;
865 
866 	pid = vfork();
867 	if (pid < 0)
868 		err(-1, "test_vfork: vfork");
869 	if (pid == 0)
870 		_exit(0);
871 	if (waitpid(pid, NULL, 0) < 0)
872 		err(-1, "test_vfork: waitpid");
873 	benchmark_start();
874 	BENCHMARK_FOREACH(i, num) {
875 		pid = vfork();
876 		if (pid < 0)
877 			err(-1, "test_vfork: vfork");
878 		if (pid == 0)
879 			_exit(0);
880 		if (waitpid(pid, NULL, 0) < 0)
881 			err(-1, "test_vfork: waitpid");
882 	}
883 	benchmark_stop();
884 	return (i);
885 }
886 
887 static uintmax_t
888 test_vfork_exec(uintmax_t num, uintmax_t int_arg __unused, const char *path __unused)
889 {
890 	pid_t pid;
891 	uintmax_t i;
892 
893 	pid = vfork();
894 	if (pid < 0)
895 		err(-1, "test_vfork_exec: vfork");
896 	if (pid == 0) {
897 		(void)execve(USR_BIN_TRUE, execve_args, environ);
898 		err(-1, "test_vfork_exec: execve");
899 	}
900 	if (waitpid(pid, NULL, 0) < 0)
901 		err(-1, "test_vfork_exec: waitpid");
902 	benchmark_start();
903 	BENCHMARK_FOREACH(i, num) {
904 		pid = vfork();
905 		if (pid < 0)
906 			err(-1, "test_vfork_exec: vfork");
907 		if (pid == 0) {
908 			(void)execve(USR_BIN_TRUE, execve_args, environ);
909 			err(-1, "execve");
910 		}
911 		if (waitpid(pid, NULL, 0) < 0)
912 			err(-1, "test_vfork_exec: waitpid");
913 	}
914 	benchmark_stop();
915 	return (i);
916 }
917 
918 struct test {
919 	const char	*t_name;
920 	uintmax_t	(*t_func)(uintmax_t, uintmax_t, const char *);
921 	int		 t_flags;
922 	uintmax_t	 t_int;
923 };
924 
925 #define	FLAG_PATH	0x00000001
926 
927 static const struct test tests[] = {
928 	{ "access", test_access, .t_flags = FLAG_PATH },
929 	{ "bad_open", test_bad_open, .t_flags = 0 },
930 	{ "chroot", test_chroot, .t_flags = 0 },
931 	{ "clock_gettime", test_clock_gettime, .t_flags = 0 },
932 	{ "create_unlink", test_create_unlink, .t_flags = FLAG_PATH },
933 	{ "fork", test_fork, .t_flags = 0 },
934 	{ "fork_exec", test_fork_exec, .t_flags = 0 },
935 	{ "getppid", test_getppid, .t_flags = 0 },
936 	{ "getpriority", test_getpriority, .t_flags = 0 },
937 	{ "getprogname", test_getprogname, .t_flags = 0 },
938 	{ "getresuid", test_getresuid, .t_flags = 0 },
939 	{ "gettimeofday", test_gettimeofday, .t_flags = 0 },
940 	{ "getuid", test_getuid, .t_flags = 0 },
941 	{ "lstat", test_lstat, .t_flags = FLAG_PATH },
942 	{ "memcpy_1", test_memcpy, .t_flags = 0, .t_int = 1 },
943 	{ "memcpy_10", test_memcpy, .t_flags = 0, .t_int = 10 },
944 	{ "memcpy_100", test_memcpy, .t_flags = 0, .t_int = 100 },
945 	{ "memcpy_1000", test_memcpy, .t_flags = 0, .t_int = 1000 },
946 	{ "memcpy_10000", test_memcpy, .t_flags = 0, .t_int = 10000 },
947 	{ "memcpy_100000", test_memcpy, .t_flags = 0, .t_int = 100000 },
948 	{ "memcpy_1000000", test_memcpy, .t_flags = 0, .t_int = 1000000 },
949 	{ "open_close", test_open_close, .t_flags = FLAG_PATH },
950 	{ "open_read_close_1", test_open_read_close, .t_flags = FLAG_PATH,
951 	    .t_int = 1 },
952 	{ "open_read_close_10", test_open_read_close, .t_flags = FLAG_PATH,
953 	    .t_int = 10 },
954 	{ "open_read_close_100", test_open_read_close, .t_flags = FLAG_PATH,
955 	    .t_int = 100 },
956 	{ "open_read_close_1000", test_open_read_close, .t_flags = FLAG_PATH,
957 	    .t_int = 1000 },
958 	{ "open_read_close_10000", test_open_read_close,
959 	    .t_flags = FLAG_PATH, .t_int = 10000 },
960 	{ "open_read_close_100000", test_open_read_close,
961 	    .t_flags = FLAG_PATH, .t_int = 100000 },
962 	{ "open_read_close_1000000", test_open_read_close,
963 	    .t_flags = FLAG_PATH, .t_int = 1000000 },
964 	{ "pipe", test_pipe, .t_flags = 0 },
965 	{ "pipeping_1", test_pipeping, .t_flags = 0, .t_int = 1 },
966 	{ "pipeping_10", test_pipeping, .t_flags = 0, .t_int = 10 },
967 	{ "pipeping_100", test_pipeping, .t_flags = 0, .t_int = 100 },
968 	{ "pipeping_1000", test_pipeping, .t_flags = 0, .t_int = 1000 },
969 	{ "pipeping_10000", test_pipeping, .t_flags = 0, .t_int = 10000 },
970 	{ "pipeping_100000", test_pipeping, .t_flags = 0, .t_int = 100000 },
971 	{ "pipeping_1000000", test_pipeping, .t_flags = 0, .t_int = 1000000 },
972 #ifdef WITH_PTHREAD
973 	{ "pipepingtd_1", test_pipepingtd, .t_flags = 0, .t_int = 1 },
974 	{ "pipepingtd_10", test_pipepingtd, .t_flags = 0, .t_int = 10 },
975 	{ "pipepingtd_100", test_pipepingtd, .t_flags = 0, .t_int = 100 },
976 	{ "pipepingtd_1000", test_pipepingtd, .t_flags = 0, .t_int = 1000 },
977 	{ "pipepingtd_10000", test_pipepingtd, .t_flags = 0, .t_int = 10000 },
978 	{ "pipepingtd_100000", test_pipepingtd, .t_flags = 0, .t_int = 100000 },
979 	{ "pipepingtd_1000000", test_pipepingtd, .t_flags = 0, .t_int = 1000000 },
980 #endif
981 	{ "read_1", test_read, .t_flags = FLAG_PATH, .t_int = 1 },
982 	{ "read_10", test_read, .t_flags = FLAG_PATH, .t_int = 10 },
983 	{ "read_100", test_read, .t_flags = FLAG_PATH, .t_int = 100 },
984 	{ "read_1000", test_read, .t_flags = FLAG_PATH, .t_int = 1000 },
985 	{ "read_10000", test_read, .t_flags = FLAG_PATH, .t_int = 10000 },
986 	{ "read_100000", test_read, .t_flags = FLAG_PATH, .t_int = 100000 },
987 	{ "read_1000000", test_read, .t_flags = FLAG_PATH, .t_int = 1000000 },
988 	{ "select", test_select, .t_flags = 0 },
989 	{ "semaping", test_semaping, .t_flags = 0 },
990 	{ "setuid", test_setuid, .t_flags = 0 },
991 	{ "shmfd", test_shmfd, .t_flags = 0 },
992 	{ "shmfd_dup", test_shmfd_dup, .t_flags = 0 },
993 	{ "shmfd_fstat", test_shmfd_fstat, .t_flags = 0 },
994 	{ "socket_local_stream", test_socket_stream, .t_int = PF_LOCAL },
995 	{ "socket_local_dgram", test_socket_dgram, .t_int = PF_LOCAL },
996 	{ "socketpair_stream", test_socketpair_stream, .t_flags = 0 },
997 	{ "socketpair_dgram", test_socketpair_dgram, .t_flags = 0 },
998 	{ "socket_tcp", test_socket_stream, .t_int = PF_INET },
999 	{ "socket_udp", test_socket_dgram, .t_int = PF_INET },
1000 	{ "readlink", test_readlink, .t_flags = FLAG_PATH },
1001 	{ "vfork", test_vfork, .t_flags = 0 },
1002 	{ "vfork_exec", test_vfork_exec, .t_flags = 0 },
1003 };
1004 static const int tests_count = sizeof(tests) / sizeof(tests[0]);
1005 
1006 static void
1007 usage(void)
1008 {
1009 	int i;
1010 
1011 	fprintf(stderr, "syscall_timing [-i iterations] [-l loops] "
1012 	    "[-p path] [-s seconds] test\n");
1013 	for (i = 0; i < tests_count; i++)
1014 		fprintf(stderr, "  %s\n", tests[i].t_name);
1015 	exit(-1);
1016 }
1017 
1018 int
1019 main(int argc, char *argv[])
1020 {
1021 	struct timespec ts_res;
1022 	const struct test *the_test;
1023 	const char *path;
1024 	char *tmp_dir, *tmp_path;
1025 	long long ll;
1026 	char *endp;
1027 	int ch, fd, error, i, j, rv;
1028 	uintmax_t iterations, k, loops;
1029 
1030 	alarm_timeout = 1;
1031 	iterations = 0;
1032 	loops = 10;
1033 	path = NULL;
1034 	tmp_path = NULL;
1035 	while ((ch = getopt(argc, argv, "i:l:p:s:")) != -1) {
1036 		switch (ch) {
1037 		case 'i':
1038 			ll = strtol(optarg, &endp, 10);
1039 			if (*endp != 0 || ll < 1)
1040 				usage();
1041 			iterations = ll;
1042 			break;
1043 
1044 		case 'l':
1045 			ll = strtol(optarg, &endp, 10);
1046 			if (*endp != 0 || ll < 1 || ll > 100000)
1047 				usage();
1048 			loops = ll;
1049 			break;
1050 
1051 		case 'p':
1052 			path = optarg;
1053 			break;
1054 
1055 		case 's':
1056 			ll = strtol(optarg, &endp, 10);
1057 			if (*endp != 0 || ll < 1 || ll > 60*60)
1058 				usage();
1059 			alarm_timeout = ll;
1060 			break;
1061 
1062 		case '?':
1063 		default:
1064 			usage();
1065 		}
1066 	}
1067 	argc -= optind;
1068 	argv += optind;
1069 
1070 	if (iterations < 1 && alarm_timeout < 1)
1071 		usage();
1072 	if (iterations < 1)
1073 		iterations = UINT64_MAX;
1074 	if (loops < 1)
1075 		loops = 1;
1076 
1077 	if (argc < 1)
1078 		usage();
1079 
1080 	/*
1081 	 * Validate test list and that, if a path is required, it is
1082 	 * defined.
1083 	 */
1084 	for (j = 0; j < argc; j++) {
1085 		the_test = NULL;
1086 		for (i = 0; i < tests_count; i++) {
1087 			if (strcmp(argv[j], tests[i].t_name) == 0)
1088 				the_test = &tests[i];
1089 		}
1090 		if (the_test == NULL)
1091 			usage();
1092 		if ((the_test->t_flags & FLAG_PATH) && (path == NULL)) {
1093 			tmp_dir = strdup("/tmp/syscall_timing.XXXXXXXX");
1094 			if (tmp_dir == NULL)
1095 				err(1, "strdup");
1096 			tmp_dir = mkdtemp(tmp_dir);
1097 			if (tmp_dir == NULL)
1098 				err(1, "mkdtemp");
1099 			rv = asprintf(&tmp_path, "%s/testfile", tmp_dir);
1100 			if (rv <= 0)
1101 				err(1, "asprintf");
1102 		}
1103 	}
1104 
1105 	error = clock_getres(CLOCK_REALTIME, &ts_res);
1106 	assert(error == 0);
1107 	printf("Clock resolution: %ju.%09ju\n", (uintmax_t)ts_res.tv_sec,
1108 	    (uintmax_t)ts_res.tv_nsec);
1109 	printf("test\tloop\ttime\titerations\tperiteration\n");
1110 
1111 	for (j = 0; j < argc; j++) {
1112 		uintmax_t calls, nsecsperit;
1113 
1114 		the_test = NULL;
1115 		for (i = 0; i < tests_count; i++) {
1116 			if (strcmp(argv[j], tests[i].t_name) == 0)
1117 				the_test = &tests[i];
1118 		}
1119 
1120 		if (tmp_path != NULL) {
1121 			fd = open(tmp_path, O_WRONLY | O_CREAT, 0700);
1122 			if (fd < 0)
1123 				err(1, "cannot open %s", tmp_path);
1124 			error = ftruncate(fd, 1000000);
1125 			if (error != 0)
1126 				err(1, "ftruncate");
1127 			error = close(fd);
1128 			if (error != 0)
1129 				err(1, "close");
1130 			path = tmp_path;
1131 		}
1132 
1133 		/*
1134 		 * Run one warmup, then do the real thing (loops) times.
1135 		 */
1136 		the_test->t_func(iterations, the_test->t_int, path);
1137 		calls = 0;
1138 		for (k = 0; k < loops; k++) {
1139 			calls = the_test->t_func(iterations, the_test->t_int,
1140 			    path);
1141 			timespecsub(&ts_end, &ts_start, &ts_end);
1142 			printf("%s\t%ju\t", the_test->t_name, k);
1143 			printf("%ju.%09ju\t%ju\t", (uintmax_t)ts_end.tv_sec,
1144 			    (uintmax_t)ts_end.tv_nsec, calls);
1145 
1146 		/*
1147 		 * Note.  This assumes that each iteration takes less than
1148 		 * a second, and that our total nanoseconds doesn't exceed
1149 		 * the room in our arithmetic unit.  Fine for system calls,
1150 		 * but not for long things.
1151 		 */
1152 			nsecsperit = ts_end.tv_sec * 1000000000;
1153 			nsecsperit += ts_end.tv_nsec;
1154 			nsecsperit /= calls;
1155 			printf("0.%09ju\n", (uintmax_t)nsecsperit);
1156 		}
1157 	}
1158 
1159 	if (tmp_path != NULL) {
1160 		error = unlink(tmp_path);
1161 		if (error != 0 && errno != ENOENT)
1162 			warn("cannot unlink %s", tmp_path);
1163 		error = rmdir(tmp_dir);
1164 		if (error != 0)
1165 			warn("cannot rmdir %s", tmp_dir);
1166 	}
1167 
1168 	return (0);
1169 }
1170