This adds a helper for testing the new ringbuffer syscall using /dev/ringbuffer-test; it can do performance testing of both normal reads and writes, and reads and writes via the ringbuffer interface. Signed-off-by: Kent Overstreet <kent.overstreet@xxxxxxxxx> --- tools/ringbuffer/Makefile | 3 + tools/ringbuffer/ringbuffer-test.c | 254 +++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 tools/ringbuffer/Makefile create mode 100644 tools/ringbuffer/ringbuffer-test.c diff --git a/tools/ringbuffer/Makefile b/tools/ringbuffer/Makefile new file mode 100644 index 000000000000..2fb27a19b43e --- /dev/null +++ b/tools/ringbuffer/Makefile @@ -0,0 +1,3 @@ +CFLAGS=-g -O2 -Wall -Werror -I../../include + +all: ringbuffer-test diff --git a/tools/ringbuffer/ringbuffer-test.c b/tools/ringbuffer/ringbuffer-test.c new file mode 100644 index 000000000000..0fba99e40858 --- /dev/null +++ b/tools/ringbuffer/ringbuffer-test.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> + +#define READ 0 +#define WRITE 1 + +#define min(a, b) (a < b ? a : b) + +#define __EXPORTED_HEADERS__ +#include <uapi/linux/ringbuffer_sys.h> + +#define BUF_NR 4 + +typedef uint32_t u32; +typedef unsigned long ulong; + +static inline struct ringbuffer_ptrs *ringbuffer(int fd, int rw, u32 size) +{ + ulong addr = 0; + int ret = syscall(463, fd, rw, size, &addr); + if (ret < 0) + errno = -ret; + return (void *) addr; +} + +static inline int ringbuffer_wait(int fd, int rw) +{ + return syscall(464, fd, rw); +} + +static inline int ringbuffer_wakeup(int fd, int rw) +{ + return syscall(465, fd, rw); +} + +static ssize_t ringbuffer_read(int fd, struct ringbuffer_ptrs *rb, + void *buf, size_t len) +{ + void *rb_data = (void *) rb + rb->data_offset; + + u32 head, orig_tail = rb->tail, tail = orig_tail; + + while ((head = __atomic_load_n(&rb->head, __ATOMIC_ACQUIRE)) == tail) + ringbuffer_wait(fd, READ); + + while (len && head != tail) { + u32 tail_masked = tail & rb->mask; + unsigned b = min(len, + min(head - tail, + rb->size - tail_masked)); + + memcpy(buf, rb_data + tail_masked, b); + buf += b; + len -= b; + tail += b; + } + + __atomic_store_n(&rb->tail, tail, __ATOMIC_RELEASE); + + __atomic_thread_fence(__ATOMIC_SEQ_CST); + + if (rb->head - orig_tail >= rb->size) + ringbuffer_wakeup(fd, READ); + + return tail - orig_tail; +} + +static ssize_t ringbuffer_write(int fd, struct ringbuffer_ptrs *rb, + void *buf, size_t len) +{ + void *rb_data = (void *) rb + rb->data_offset; + + u32 orig_head = rb->head, head = orig_head, tail; + + while (head - (tail = __atomic_load_n(&rb->tail, __ATOMIC_ACQUIRE)) >= rb->size) + ringbuffer_wait(fd, WRITE); + + while (len && head - tail < rb->size) { + u32 head_masked = head & rb->mask; + unsigned b = min(len, + min(tail - head + rb->size, + rb->size - head_masked)); + + memcpy(rb_data + head_masked, buf, b); + buf += b; + len -= b; + head += b; + } + + __atomic_store_n(&rb->head, head, __ATOMIC_RELEASE); + + __atomic_thread_fence(__ATOMIC_SEQ_CST); + + if ((s32) (rb->tail - orig_head) >= 0) + ringbuffer_wakeup(fd, WRITE); + + return head - orig_head; +} + +static void usage(void) +{ + puts("ringbuffer-test - test ringbuffer syscall\n" + "Usage: ringbuffer-test [OPTION]...\n" + "\n" + "Options:\n" + " --type=(io|ringbuffer)\n" + " --rw=(read|write)\n" + " -h, --help Display this help and exit\n"); +} + +static inline ssize_t rb_test_read(int fd, struct ringbuffer_ptrs *rb, + void *buf, size_t len) +{ + return rb + ? ringbuffer_read(fd, rb, buf, len) + : read(fd, buf, len); +} + +static inline ssize_t rb_test_write(int fd, struct ringbuffer_ptrs *rb, + void *buf, size_t len) +{ + return rb + ? ringbuffer_write(fd, rb, buf, len) + : write(fd, buf, len); +} + +int main(int argc, char *argv[]) +{ + const struct option longopts[] = { + { "type", required_argument, NULL, 't' }, + { "rw", required_argument, NULL, 'r' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + int use_ringbuffer = false, rw = false; + int opt; + + while ((opt = getopt_long(argc, argv, "h", longopts, NULL)) != -1) { + switch (opt) { + case 't': + if (!strcmp(optarg, "io")) + use_ringbuffer = false; + else if (!strcmp(optarg, "ringbuffer") || + !strcmp(optarg, "rb")) + use_ringbuffer = true; + else { + fprintf(stderr, "Invalid type %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'r': + if (!strcmp(optarg, "read")) + rw = false; + else if (!strcmp(optarg, "write")) + rw = true; + else { + fprintf(stderr, "Invalid rw %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case '?': + fprintf(stderr, "Invalid option %c\n", opt); + usage(); + exit(EXIT_FAILURE); + case 'h': + usage(); + exit(EXIT_SUCCESS); + } + } + + int fd = open("/dev/ringbuffer-test", O_RDWR); + if (fd < 0) { + fprintf(stderr, "Error opening /dev/ringbuffer-test: %m\n"); + exit(EXIT_FAILURE); + } + + struct ringbuffer_ptrs *rb = NULL; + if (use_ringbuffer) { + rb = ringbuffer(fd, rw, 4096); + if (!rb) { + fprintf(stderr, "Error from sys_ringbuffer: %m\n"); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "got ringbuffer %p\n", rb); + } + + printf("Starting test with ringbuffer=%u, rw=%u\n", use_ringbuffer, rw); + static const char * const rw_str[] = { "read", "wrote" }; + + struct timeval start; + gettimeofday(&start, NULL); + size_t nr_prints = 1; + + u32 buf[BUF_NR]; + u32 idx = 0; + + while (true) { + struct timeval now; + gettimeofday(&now, NULL); + + struct timeval next_print = start; + next_print.tv_sec += nr_prints; + + if (timercmp(&now, &next_print, >)) { + printf("%s %u u32s, %lu mb/sec\n", rw_str[rw], idx, + (idx * sizeof(u32) / (now.tv_sec - start.tv_sec)) / (1UL << 20)); + nr_prints++; + if (nr_prints > 20) + break; + } + + if (rw == READ) { + int r = rb_test_read(fd, rb, buf, sizeof(buf)); + if (r <= 0) { + fprintf(stderr, "Read returned %i (%m)\n", r); + exit(EXIT_FAILURE); + } + + unsigned nr = r / sizeof(u32); + for (unsigned i = 0; i < nr; i++) { + if (buf[i] != idx + i) { + fprintf(stderr, "Read returned wrong data at idx %u: got %u instead\n", + idx + i, buf[i]); + exit(EXIT_FAILURE); + } + } + + idx += nr; + } else { + for (unsigned i = 0; i < BUF_NR; i++) + buf[i] = idx + i; + + int r = rb_test_write(fd, rb, buf, sizeof(buf)); + if (r <= 0) { + fprintf(stderr, "Write returned %i (%m)\n", r); + exit(EXIT_FAILURE); + } + + unsigned nr = r / sizeof(u32); + idx += nr; + } + } + + exit(EXIT_SUCCESS); +} -- 2.45.1