From ebf57dfc03c9f3bda5ee065d45c28feb6ebb1c97 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 30 Oct 2026 03:25:35 +0100 Subject: Reduce syscalls and add helper to count them --- tests/syscount.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/t_event.c | 37 ++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 tests/syscount.c (limited to 'tests') diff --git a/tests/syscount.c b/tests/syscount.c new file mode 100644 index 0000000..ec45c15 --- /dev/null +++ b/tests/syscount.c @@ -0,0 +1,144 @@ +/* Counts the system calls a run makes, by standing in front of libc. + + cc -O2 -shared -fPIC -o syscount.so tests/syscount.c -ldl + LD_PRELOAD=./syscount.so ./opentracker -f conf (DYLD_INSERT_LIBRARIES on macOS) + + The tally goes to stderr when the process exits. Reading the source tells + you which calls a path should make; this tells you which it does make, + which is not always the same thing. */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#define COUNTER(name) static _Atomic long g_##name +#define BUMP(name) atomic_fetch_add_explicit(&g_##name, 1, memory_order_relaxed) +#define REPORT(name) fprintf(stderr, " %-14s %10ld\n", #name, atomic_load(&g_##name)) + +COUNTER(accept); +COUNTER(accept4); +COUNTER(read); +COUNTER(write); +COUNTER(close); +COUNTER(fcntl); +COUNTER(recvfrom); +COUNTER(sendto); +COUNTER(epoll_ctl); +COUNTER(epoll_wait); + +typedef int (*fn_accept)(int, struct sockaddr *, socklen_t *); +typedef int (*fn_accept4)(int, struct sockaddr *, socklen_t *, int); +typedef ssize_t (*fn_read)(int, void *, size_t); +typedef ssize_t (*fn_write)(int, const void *, size_t); +typedef int (*fn_close)(int); +typedef int (*fn_fcntl)(int, int, ...); +typedef ssize_t (*fn_recvfrom)(int, void *, size_t, int, struct sockaddr *, socklen_t *); +typedef ssize_t (*fn_sendto)(int, const void *, size_t, int, const struct sockaddr *, socklen_t); + +static fn_accept real_accept; +static fn_accept4 real_accept4; +static fn_read real_read; +static fn_write real_write; +static fn_close real_close; +static fn_fcntl real_fcntl; +static fn_recvfrom real_recvfrom; +static fn_sendto real_sendto; + +/* Resolving lazily keeps the library out of the way until something is + actually called, which matters because dlsym itself allocates */ +#define WRAP(name) \ + if (!real_##name) \ + real_##name = (fn_##name)dlsym(RTLD_NEXT, #name); \ + BUMP(name) + +int accept(int s, struct sockaddr *addr, socklen_t *len) { + WRAP(accept); + return real_accept(s, addr, len); +} + +#ifdef SOCK_NONBLOCK +int accept4(int s, struct sockaddr *addr, socklen_t *len, int flags) { + WRAP(accept4); + return real_accept4(s, addr, len, flags); +} +#endif + +ssize_t read(int fd, void *buf, size_t count) { + WRAP(read); + return real_read(fd, buf, count); +} + +ssize_t write(int fd, const void *buf, size_t count) { + WRAP(write); + return real_write(fd, buf, count); +} + +int close(int fd) { + WRAP(close); + return real_close(fd); +} + +int fcntl(int fd, int cmd, ...) { + va_list args; + long arg; + + va_start(args, cmd); + arg = va_arg(args, long); + va_end(args); + WRAP(fcntl); + return real_fcntl(fd, cmd, arg); +} + +ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { + WRAP(recvfrom); + return real_recvfrom(s, buf, len, flags, from, fromlen); +} + +ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) { + WRAP(sendto); + return real_sendto(s, buf, len, flags, to, tolen); +} + +#ifdef __linux__ +typedef int (*fn_epoll_ctl)(int, int, int, struct epoll_event *); +typedef int (*fn_epoll_wait)(int, struct epoll_event *, int, int); +static fn_epoll_ctl real_epoll_ctl; +static fn_epoll_wait real_epoll_wait; + +int epoll_ctl(int ep, int op, int fd, struct epoll_event *ev) { + WRAP(epoll_ctl); + return real_epoll_ctl(ep, op, fd, ev); +} + +int epoll_wait(int ep, struct epoll_event *events, int max, int timeout) { + WRAP(epoll_wait); + return real_epoll_wait(ep, events, max, timeout); +} +#endif + +__attribute__((destructor)) static void report(void) { + fprintf(stderr, "\n system calls this run\n"); + REPORT(accept); +#ifdef SOCK_NONBLOCK + REPORT(accept4); +#endif + REPORT(read); + REPORT(write); + REPORT(close); + REPORT(fcntl); + REPORT(recvfrom); + REPORT(sendto); +#ifdef __linux__ + REPORT(epoll_ctl); + REPORT(epoll_wait); +#endif +} diff --git a/tests/t_event.c b/tests/t_event.c index 5509f19..ad3948f 100644 --- a/tests/t_event.c +++ b/tests/t_event.c @@ -4,6 +4,7 @@ $id$ */ /* System */ +#include #include #include #include @@ -205,6 +206,41 @@ static void case_unknown_descriptor(void) { CHECK_INT(event_del(4096), -1); } +/* event_close exists so that the mechanisms which drop a closed descriptor + by themselves are not asked to do it twice. The one that keeps its own + list has to be told, and forgetting would leave the entry there to be + reported for ever, so what is checked here is the silence afterwards. */ +static void case_close_takes_it_out(void) { + int other[2], closed, count, i, strays = 0; + + test_case("a closed descriptor is gone from the set and really closed"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0); + CHECK_INT(event_add(sv[0], EVENT_IN, 1), 0); + CHECK_INT(event_add(other[0], EVENT_IN, 2), 0); + + closed = sv[0]; + CHECK_INT(event_close(closed), 0); + sv[0] = -1; + CHECK_INT(fcntl(closed, F_GETFD), -1); + + /* The survivor is made ready so the wait has something to come back with, + and anything left over from the closed one shows up beside it */ + CHECK_INT((int)write(other[1], "x", 1), 1); + count = event_wait(ready, EVENTS, 200); + CHECK(count >= 1); + for (i = 0; i < count; ++i) + if (ready[i].fd != other[0]) + ++strays; + CHECK_INT(strays, 0); + + event_del(other[0]); + close(other[0]); + close(other[1]); + pair_close(); +} + void test_event(void) { CHECK_INT(event_init(), 0); CHECK(event_mechanism() != NULL); @@ -217,6 +253,7 @@ void test_event(void) { case_delete_from_the_middle(); case_timeout(); case_unknown_descriptor(); + case_close_takes_it_out(); event_deinit(); } -- cgit v1.3