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
58static struct timespec ts_start, ts_end;
59static int alarm_timeout;
60static volatile int alarm_fired;
61
62#define	BENCHMARK_FOREACH(I, NUM) for (I = 0; I < NUM && alarm_fired == 0; I++)
63
64static void
65alarm_handler(int signum __unused)
66{
67
68	alarm_fired = 1;
69}
70
71static void
72benchmark_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
85static void
86benchmark_stop(void)
87{
88	int error;
89
90	error = clock_gettime(CLOCK_REALTIME, &ts_end);
91	assert(error == 0);
92}
93
94static uintmax_t
95test_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
114static uintmax_t
115test_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
127static uintmax_t
128test_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
143static uintmax_t
144test_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
157static uintmax_t
158test_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
183static uintmax_t
184test_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"
211static char *execve_args[] = { __DECONST(char *, USR_BIN_TRUE), NULL};
212extern char **environ;
213
214static uintmax_t
215test_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
245static uintmax_t
246test_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
262static uintmax_t
263test_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 */
279static uintmax_t
280test_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
292static uintmax_t
293test_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
306static uintmax_t
307test_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
320static uintmax_t
321test_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
337static uintmax_t
338test_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
357static uintmax_t
358test_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
379static uintmax_t
380test_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
404static uintmax_t
405test_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
431static void
432readx(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
446static void
447writex(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
461static uintmax_t
462test_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
499struct pipepingtd_ctx {
500	int		fd;
501	uintmax_t	int_arg;
502};
503
504static void *
505pipepingtd_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
526static uintmax_t
527test_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
557static uintmax_t
558test_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
578static uintmax_t
579test_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
600static uintmax_t
601test_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
659static uintmax_t
660test_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
677static uintmax_t
678test_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
698static uintmax_t
699test_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
721static uintmax_t
722test_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
742static uintmax_t
743test_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
763static uintmax_t
764test_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
784static uintmax_t
785test_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
805static uintmax_t
806test_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
826static uintmax_t
827test_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
853static uintmax_t
854test_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
884struct 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
893static 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};
968static const int tests_count = sizeof(tests) / sizeof(tests[0]);
969
970static void
971usage(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
982int
983main(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