Add suppport for toggling iowait Add io_uring_set_iowait(ring, enable_iowait) which can be used to turn on or off setting/utilization of iowait on kernels that support it. Will return -EOPNOTSUPP if the kernel doesn't support it, or 0 in case of success. As iowait accounting and CPU frequency boost while waiting are currently tied together in the kernel, this helper turns both of these on and off collectively. Also adds a basic test case that will test for iowait stats for both types of waits. Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/src/include/liburing.h b/src/include/liburing.h index ae2021b..1c3e401 100644 --- a/src/include/liburing.h +++ b/src/include/liburing.h
@@ -320,6 +320,11 @@ struct io_uring_cqe **cqe_ptr, unsigned submit, unsigned wait_nr, sigset_t *sigmask); +/* + * Enable/disable setting of iowait by the kernel. + */ +int io_uring_set_iowait(struct io_uring *ring, bool enable_iowait); + #define LIBURING_UDATA_TIMEOUT ((__u64) -1) /*
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h index 749ca78..cb9f82f 100644 --- a/src/include/liburing/io_uring.h +++ b/src/include/liburing/io_uring.h
@@ -527,6 +527,7 @@ #define IORING_ENTER_REGISTERED_RING (1U << 4) #define IORING_ENTER_ABS_TIMER (1U << 5) #define IORING_ENTER_EXT_ARG_REG (1U << 6) +#define IORING_ENTER_NO_IOWAIT (1U << 7) /* * Passed in for io_uring_setup(2). Copied back with updated info on success @@ -564,6 +565,7 @@ #define IORING_FEAT_RECVSEND_BUNDLE (1U << 14) #define IORING_FEAT_MIN_TIMEOUT (1U << 15) #define IORING_FEAT_RW_ATTR (1U << 16) +#define IORING_FEAT_NO_IOWAIT (1U << 17) /* * io_uring_register(2) opcodes and arguments
diff --git a/src/int_flags.h b/src/int_flags.h index 85b2f07..ae575fd 100644 --- a/src/int_flags.h +++ b/src/int_flags.h
@@ -2,10 +2,12 @@ #ifndef LIBURING_INT_FLAGS #define LIBURING_INT_FLAGS -#define INT_FLAGS_MASK (IORING_ENTER_REGISTERED_RING) +#define INT_FLAGS_MASK (IORING_ENTER_REGISTERED_RING | \ + IORING_ENTER_NO_IOWAIT) enum { INT_FLAG_REG_RING = IORING_ENTER_REGISTERED_RING, + INT_FLAG_NO_IOWAIT = IORING_ENTER_NO_IOWAIT, INT_FLAG_REG_REG_RING = 1, INT_FLAG_APP_MEM = 2, INT_FLAG_CQ_ENTER = 4,
diff --git a/src/liburing-ffi.map b/src/liburing-ffi.map index 75c9e9b..7f6d4af 100644 --- a/src/liburing-ffi.map +++ b/src/liburing-ffi.map
@@ -233,4 +233,5 @@ io_uring_prep_writev_fixed; io_uring_prep_readv_fixed; io_uring_prep_sendmsg_zc_fixed; + io_uring_set_iowait; } LIBURING_2.9;
diff --git a/src/liburing.map b/src/liburing.map index d1661d9..c946115 100644 --- a/src/liburing.map +++ b/src/liburing.map
@@ -115,4 +115,5 @@ LIBURING_2.10 { global: io_uring_register_ifq; + io_uring_set_iowait; } LIBURING_2.9;
diff --git a/src/register.c b/src/register.c index 99337d1..3926c78 100644 --- a/src/register.c +++ b/src/register.c
@@ -482,3 +482,14 @@ { return do_register(ring, IORING_REGISTER_MEM_REGION, reg, 1); } + +int io_uring_set_iowait(struct io_uring *ring, bool enable_iowait) +{ + if (!(ring->features & IORING_FEAT_NO_IOWAIT)) + return -EOPNOTSUPP; + if (enable_iowait) + ring->int_flags &= ~INT_FLAG_NO_IOWAIT; + else + ring->int_flags |= INT_FLAG_NO_IOWAIT; + return 0; +}
diff --git a/test/Makefile b/test/Makefile index fb11d16..a09ccb7 100644 --- a/test/Makefile +++ b/test/Makefile
@@ -122,6 +122,7 @@ io_uring_passthrough.c \ io_uring_register.c \ io_uring_setup.c \ + iowait.c \ kallsyms.c \ lfs-openat.c \ lfs-openat-write.c \
diff --git a/test/iowait.c b/test/iowait.c new file mode 100644 index 0000000..d70cfc8 --- /dev/null +++ b/test/iowait.c
@@ -0,0 +1,136 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "liburing.h" +#include "helpers.h" + +static int get_iowait(int cpu) +{ + char cpu_buf[32], this_cpu[32]; + int user, nice, system, idle, iowait, ret; + FILE *file; + + file = fopen("/proc/stat", "r"); + if (!file) { + perror("fopen stat"); + return 0; + } + + sprintf(this_cpu, "cpu%d", cpu); + ret = 0; + do { + int r; + r = fscanf(file, "%s %d %d %d %d %d", cpu_buf, &user, &nice, + &system, &idle, &iowait); + if (r != 6) + continue; + if (strncmp(cpu_buf, this_cpu, strlen(this_cpu))) + continue; + ret = iowait; + break; + } while (1); + + fclose(file); + return ret; +} + +static int test(struct io_uring *ring, int with_iowait, int cpu) +{ + struct io_uring_sqe *sqe; + struct io_uring_cqe *cqe; + int iowait_pre, iowait_post; + struct __kernel_timespec ts; + int ret, fds[2], diff; + char buf[32]; + + if (!(ring->features & IORING_FEAT_NO_IOWAIT)) + return T_EXIT_SKIP; + + if (pipe(fds) < 0) { + perror("pipe"); + return T_EXIT_FAIL; + } + + ret = io_uring_set_iowait(ring, with_iowait); + if (ret) { + fprintf(stderr, "set_iowait=%d\n", ret); + return T_EXIT_FAIL; + } + + sqe = io_uring_get_sqe(ring); + io_uring_prep_read(sqe, fds[0], buf, sizeof(buf), 0); + io_uring_submit(ring); + + ts.tv_sec = 1; + ts.tv_nsec = 0; + iowait_pre = get_iowait(cpu); + ret = io_uring_wait_cqe_timeout(ring, &cqe, &ts); + if (ret != -ETIME) { + fprintf(stderr, "Unexpected wait ret: %d\n", ret); + return T_EXIT_FAIL; + } + iowait_post = get_iowait(cpu); + diff = iowait_post - iowait_pre; + + close(fds[0]); + close(fds[1]); + + ret = io_uring_wait_cqe(ring, &cqe); + if (ret) { + fprintf(stderr, "Unexpected wait ret: %d\n", ret); + return T_EXIT_FAIL; + } + io_uring_cqe_seen(ring, cqe); + + if (with_iowait) { + if (diff < 90) { + fprintf(stderr, "iowait diff too small: %d\n", diff); + return T_EXIT_FAIL; + } + } else { + if (diff > 10) { + fprintf(stderr, "iowait diff too large: %d\n", diff); + return T_EXIT_FAIL; + } + } + return T_EXIT_PASS; +} + +int main(int argc, char *argv[]) +{ + struct io_uring ring; + cpu_set_t cpuset; + int ret; + + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) + return T_EXIT_SKIP; + + ret = t_create_ring(64, &ring, 0); + if (ret == T_SETUP_SKIP) + return T_EXIT_SKIP; + else if (ret != T_SETUP_OK) + return T_EXIT_FAIL; + + ret = test(&ring, 0, 0); + if (ret == T_EXIT_SKIP) { + return T_EXIT_SKIP; + } else if (ret == T_EXIT_FAIL) { + fprintf(stderr, "test 0 failed\n"); + return T_EXIT_FAIL; + } + + ret = test(&ring, 1, 0); + if (ret == T_EXIT_SKIP) { + return T_EXIT_SKIP; + } else if (ret == T_EXIT_FAIL) { + fprintf(stderr, "test 1 failed\n"); + return T_EXIT_FAIL; + } + + io_uring_queue_exit(&ring); + return 0; +}