]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: introduce io_uring anon-inode support
authorWanBingjiang <wanbingjiang@webray.com.cn>
Fri, 31 Jul 2026 03:42:09 +0000 (11:42 +0800)
committerWanBingjiang <wanbingjiang@webray.com.cn>
Wed, 5 Aug 2026 02:19:10 +0000 (10:19 +0800)
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 <wanbingjiang@webray.com.cn>
lsfd-cmd/lsfd.1.adoc
lsfd-cmd/lsfd.c
lsfd-cmd/lsfd.h
lsfd-cmd/unkn.c

index 198a3f1d70894c5fcae3ec486b5b93f64d4b972b..8c2dd03b7ff4715eff191cf554c72f51a24007e2 100644 (file)
@@ -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_]
 +
index 988d37e4291cfde06d4f407c18ad111e901dcfd1..b96a52f62d0a78c4c15842bd98719058550e231c 100644 (file)
@@ -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)"),
index 70c344ee9dc1aeb4de0345f157ce6110f7f5e9af..07b9bac429623d7f425129ddd2b89ce90c7e4f43 100644 (file)
@@ -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,
index 8a93dfe8bb3935365fe3745e70821d78d358edf0..5bbae40d1a1584e66510647fe7063a80f2077b89 100644 (file)
@@ -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)