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