From: WanBingjiang Date: Fri, 31 Jul 2026 03:42:09 +0000 (+0800) Subject: lsfd: introduce io_uring anon-inode support X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=6cb852e8313c7c617d262b7293e1fccca2e51549;p=thirdparty%2Futil-linux.git lsfd: introduce io_uring anon-inode support Parse io_uring fdinfo to expose SQ/CQ ring state, registered buffers/files, SQ poll thread info, and NAPI mode via dedicated IO-URING.* columns. Note that the io_uring NAME format is used only for opened file objects, not for mapped ones. Signed-off-by: WanBingjiang --- diff --git a/lsfd-cmd/lsfd.1.adoc b/lsfd-cmd/lsfd.1.adoc index 198a3f1d7..8c2dd03b7 100644 --- a/lsfd-cmd/lsfd.1.adoc +++ b/lsfd-cmd/lsfd.1.adoc @@ -271,6 +271,45 @@ INOTIFY.INODES.RAW <``string``>:: List of monitoring inodes. The format of the element is _inode-number_``,``_device-major_``:``_device-minor_. +IO-URING.CQ-HEAD <``number``>:: +Io_uring completion queue head. + +IO-URING.CQ-MASK <``number``>:: +Io_uring completion queue ring mask. + +IO-URING.CQ-TAIL <``number``>:: +Io_uring completion queue tail. + +IO-URING.CQES <``number``>:: +Io_uring unconsumed completion queue entries. + +IO-URING.NAPI <``string``>:: +Io_uring NAPI mode ("enabled" or "disabled"). + +IO-URING.SQ-HEAD <``number``>:: +Io_uring submission queue head. + +IO-URING.SQ-MASK <``number``>:: +Io_uring submission queue ring mask. + +IO-URING.SQ-TAIL <``number``>:: +Io_uring submission queue tail. + +IO-URING.SQES <``number``>:: +Io_uring unconsumed submission queue entries. + +IO-URING.SQTHREAD <``number``>:: +Io_uring SQ poll kernel thread PID (-1 if not used). + +IO-URING.SQTHREAD-CPU <``number``>:: +Io_uring SQ poll thread CPU affinity. + +IO-URING.USER-BUFS <``number``>:: +Io_uring count of registered buffers. + +IO-URING.USER-FILES <``number``>:: +Io_uring count of registered files. + KNAME <``string``>:: // // It seems that the manpage backend of asciidoctor has limitations @@ -331,6 +370,11 @@ id=_EVENTFD.ID_ inotify::: inodes=_INOTIFY.INODES_ + +io_uring::: +sqmask=_IO-URING.SQ-MASK_ cqmask=_IO-URING.CQ-MASK_ sqes=_IO-URING.SQES_ cqes=_IO-URING.CQES_[ sqthread=_IO-URING.SQTHREAD_][ userfiles=_IO-URING.USER-FILES_ userbufs=_IO-URING.USER-BUFS_] ++ +Note that the format is used only for opened file objects, not for mapped ones. ++ misc:tun::: iface=_TUN.IFACE_[ devnetns=_TUN.DEVNETNS_][ socknetns=_SOCK.NETNS_] + diff --git a/lsfd-cmd/lsfd.c b/lsfd-cmd/lsfd.c index 988d37e42..b96a52f62 100644 --- a/lsfd-cmd/lsfd.c +++ b/lsfd-cmd/lsfd.c @@ -276,6 +276,45 @@ static const struct colinfo infos[] = { [COL_INOTIFY_INODES_RAW]={ "INOTIFY.INODES.RAW", 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING, N_("list of monitoring inodes (raw, don't decode devices)") }, + [COL_IO_URING_CQ_HEAD] = { "IO-URING.CQ-HEAD", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring completion queue head") }, + [COL_IO_URING_CQ_MASK] = { "IO-URING.CQ-MASK", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring completion queue ring mask") }, + [COL_IO_URING_CQ_TAIL] = { "IO-URING.CQ-TAIL", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring completion queue tail") }, + [COL_IO_URING_CQES] = { "IO-URING.CQES", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring unconsumed completion queue entries") }, + [COL_IO_URING_NAPI] = { "IO-URING.NAPI", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("io_uring NAPI mode") }, + [COL_IO_URING_SQ_HEAD] = { "IO-URING.SQ-HEAD", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring submission queue head") }, + [COL_IO_URING_SQ_MASK] = { "IO-URING.SQ-MASK", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring submission queue ring mask") }, + [COL_IO_URING_SQ_TAIL] = { "IO-URING.SQ-TAIL", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring submission queue tail") }, + [COL_IO_URING_SQES] = { "IO-URING.SQES", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring unconsumed submission queue entries") }, + [COL_IO_URING_SQTHREAD] = { "IO-URING.SQTHREAD", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring SQ poll kernel thread PID") }, + [COL_IO_URING_SQTHREAD_CPU] = { "IO-URING.SQTHREAD-CPU", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring SQ poll thread CPU affinity") }, + [COL_IO_URING_USER_BUFS] = { "IO-URING.USER-BUFS", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring count of registered buffers") }, + [COL_IO_URING_USER_FILES] = { "IO-URING.USER-FILES", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("io_uring count of registered files") }, [COL_KNAME] = { "KNAME", 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, N_("name of the file (raw)"), diff --git a/lsfd-cmd/lsfd.h b/lsfd-cmd/lsfd.h index 70c344ee9..07b9bac42 100644 --- a/lsfd-cmd/lsfd.h +++ b/lsfd-cmd/lsfd.h @@ -87,6 +87,19 @@ enum { COL_INODE, COL_INOTIFY_INODES, COL_INOTIFY_INODES_RAW, + COL_IO_URING_CQ_HEAD, + COL_IO_URING_CQ_MASK, + COL_IO_URING_CQ_TAIL, + COL_IO_URING_CQES, + COL_IO_URING_NAPI, + COL_IO_URING_SQ_HEAD, + COL_IO_URING_SQ_MASK, + COL_IO_URING_SQ_TAIL, + COL_IO_URING_SQES, + COL_IO_URING_SQTHREAD, + COL_IO_URING_SQTHREAD_CPU, + COL_IO_URING_USER_BUFS, + COL_IO_URING_USER_FILES, COL_KNAME, COL_KTHREAD, COL_MAJMIN, diff --git a/lsfd-cmd/unkn.c b/lsfd-cmd/unkn.c index 8a93dfe8b..5bbae40d1 100644 --- a/lsfd-cmd/unkn.c +++ b/lsfd-cmd/unkn.c @@ -1480,6 +1480,210 @@ static const struct anon_ops anon_bpf_link_ops = { }; +/* + * io_uring + */ +struct anon_io_uring_data { + unsigned int sq_mask; + unsigned int sq_head; + unsigned int sq_tail; + unsigned int cq_mask; + unsigned int cq_head; + unsigned int cq_tail; + unsigned int sqes; + unsigned int cqes; + int sqthread; + int sqthread_cpu; + unsigned int user_files; + unsigned int user_bufs; + char napi[16]; /* "enabled", "disabled", or "" */ + bool has_data; /* true if fdinfo was parsed */ +}; + +static bool anon_io_uring_probe(const char *str) +{ + return strncmp(str, "[io_uring]", 10) == 0; +} + +static void anon_io_uring_init(struct unkn *unkn) +{ + struct anon_io_uring_data *data = xcalloc(1, sizeof(*data)); + + data->sqthread = -1; + data->sqthread_cpu = -1; + unkn->anon_data = data; +} + +static void anon_io_uring_free(struct unkn *unkn) +{ + struct anon_io_uring_data *data = (struct anon_io_uring_data *)unkn->anon_data; + free(data); +} + +static int anon_io_uring_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_io_uring_data *data = (struct anon_io_uring_data *)unkn->anon_data; + + if (strcmp(key, "SqMask") == 0) { + if (ul_strtou32_to_struct_member(data, sq_mask, value, 16) == 0) + data->has_data = true; + return 1; + } + if (strcmp(key, "SqHead") == 0) { + ul_strtou32_to_struct_member(data, sq_head, value, 10); + return 1; + } + if (strcmp(key, "SqTail") == 0) { + ul_strtou32_to_struct_member(data, sq_tail, value, 10); + return 1; + } + if (strcmp(key, "CqMask") == 0) { + ul_strtou32_to_struct_member(data, cq_mask, value, 16); + return 1; + } + if (strcmp(key, "CqHead") == 0) { + ul_strtou32_to_struct_member(data, cq_head, value, 10); + return 1; + } + if (strcmp(key, "CqTail") == 0) { + ul_strtou32_to_struct_member(data, cq_tail, value, 10); + return 1; + } + if (strcmp(key, "SQEs") == 0) { + ul_strtou32_to_struct_member(data, sqes, value, 10); + return 1; + } + if (strcmp(key, "CQEs") == 0) { + ul_strtou32_to_struct_member(data, cqes, value, 10); + return 1; + } + if (strcmp(key, "SqThread") == 0) { + ul_strtos32_to_struct_member(data, sqthread, value, 10); + return 1; + } + if (strcmp(key, "SqThreadCpu") == 0) { + ul_strtos32_to_struct_member(data, sqthread_cpu, value, 10); + return 1; + } + if (strcmp(key, "UserFiles") == 0) { + ul_strtou32_to_struct_member(data, user_files, value, 10); + return 1; + } + if (strcmp(key, "UserBufs") == 0) { + ul_strtou32_to_struct_member(data, user_bufs, value, 10); + return 1; + } + if (strcmp(key, "NAPI") == 0) { + strncpy(data->napi, value, sizeof(data->napi) - 1); + data->napi[sizeof(data->napi) - 1] = '\0'; + return 1; + } + + /* Skip known fields we don't expose as columns: + * CachedSqHead, CachedCqTail, SqTotalTime, SqWorkTime, + * PollList, CqOverflowList + */ + if (strcmp(key, "CachedSqHead") == 0 + || strcmp(key, "CachedCqTail") == 0 + || strcmp(key, "SqTotalTime") == 0 + || strcmp(key, "SqWorkTime") == 0 + || strcmp(key, "PollList") == 0 + || strcmp(key, "CqOverflowList") == 0) + return 1; + + return 0; +} + +static bool anon_io_uring_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_io_uring_data *data = (struct anon_io_uring_data *)unkn->anon_data; + + if (!data->has_data) + return false; + + switch(column_id) { + case COL_IO_URING_SQ_MASK: + xasprintf(str, "0x%x", data->sq_mask); + return true; + case COL_IO_URING_SQ_HEAD: + xasprintf(str, "%u", data->sq_head); + return true; + case COL_IO_URING_SQ_TAIL: + xasprintf(str, "%u", data->sq_tail); + return true; + case COL_IO_URING_CQ_MASK: + xasprintf(str, "0x%x", data->cq_mask); + return true; + case COL_IO_URING_CQ_HEAD: + xasprintf(str, "%u", data->cq_head); + return true; + case COL_IO_URING_CQ_TAIL: + xasprintf(str, "%u", data->cq_tail); + return true; + case COL_IO_URING_SQES: + xasprintf(str, "%u", data->sqes); + return true; + case COL_IO_URING_CQES: + xasprintf(str, "%u", data->cqes); + return true; + case COL_IO_URING_SQTHREAD: + xasprintf(str, "%d", data->sqthread); + return true; + case COL_IO_URING_SQTHREAD_CPU: + xasprintf(str, "%d", data->sqthread_cpu); + return true; + case COL_IO_URING_USER_FILES: + xasprintf(str, "%u", data->user_files); + return true; + case COL_IO_URING_USER_BUFS: + xasprintf(str, "%u", data->user_bufs); + return true; + case COL_IO_URING_NAPI: + if (data->napi[0]) + *str = xstrdup(data->napi); + return true; + default: + return false; + } +} + +static char *anon_io_uring_get_name(struct unkn *unkn) +{ + char *str = NULL; + struct anon_io_uring_data *data = (struct anon_io_uring_data *)unkn->anon_data; + + if (!data->has_data) + return NULL; + + xasprintf(&str, "sqmask=0x%x cqmask=0x%x sqes=%u cqes=%u", + data->sq_mask, data->cq_mask, + data->sqes, data->cqes); + + if (data->sqthread >= 0) + xstrfappend(&str, " sqthread=%d", data->sqthread); + + if (data->user_files > 0 || data->user_bufs > 0) + xstrfappend(&str, " userfiles=%u userbufs=%u", + data->user_files, data->user_bufs); + + return str; +} + +static const struct anon_ops anon_io_uring_ops = { + .class = "io_uring", + .probe = anon_io_uring_probe, + .get_name = anon_io_uring_get_name, + .fill_column = anon_io_uring_fill_column, + .init = anon_io_uring_init, + .free = anon_io_uring_free, + .handle_fdinfo = anon_io_uring_handle_fdinfo, +}; + /* * generic (fallback implementation) */ @@ -1502,6 +1706,7 @@ static const struct anon_ops *const anon_ops[] = { &anon_bpf_prog_ops, &anon_bpf_map_ops, &anon_bpf_link_ops, + &anon_io_uring_ops, }; static const struct anon_ops *anon_probe(const char *str)