[PATCH RFC 1/2] rcu: Expose per-CPU segmented callback counts via debugfs

From: Gustavo Luiz Duarte

Date: Thu May 07 2026 - 13:40:28 EST


The existing rcu_segcb_stats tracepoint requires active tracing, so
there is no always-on, low-overhead way to inspect how many callbacks
are pending on each CPU and at which stage of the grace-period
pipeline.

Add a debugfs file at /sys/kernel/debug/rcu/pending_cbs
that prints per-CPU callback counts broken down by segcblist segment,
plus a "total" row aggregating across CPUs:

- done: Callbacks ready to invoke (GP completed).
- wait: Callbacks waiting for the current GP.
- next_ready: Callbacks to be handled by the next GP.
- next: Newly queued callbacks not yet assigned a GP.
- lazy: Callbacks deferred via the RCU lazy mechanism.

The interface has zero steady-state overhead: it reads the existing
per-CPU rcu_segcblist.seglen[] counters on demand. These counters are
already maintained by the RCU callback infrastructure for its own
bookkeeping, so no new runtime accounting is introduced.

Example output:
cpu done wait next_ready next lazy
0 7 11 0 0 0
1 0 3 2 0 0
2 0 1 8 0 0
3 0 1 1 0 0
total 7 16 11 0 0

Signed-off-by: Gustavo Luiz Duarte <gustavold@xxxxxxxxx>
---
kernel/rcu/tree_stall.h | 67 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)

diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h
index b67532cb8770..d9fc9bfdaf96 100644
--- a/kernel/rcu/tree_stall.h
+++ b/kernel/rcu/tree_stall.h
@@ -71,6 +71,73 @@ late_initcall(kernel_rcu_stall_sysfs_init);

#endif // CONFIG_SYSFS

+#ifdef CONFIG_DEBUG_FS
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+/*
+ * Debugfs interface for displaying per-CPU RCU callback counts broken down
+ * by callback-list segment. This allows monitoring how many callbacks are
+ * waiting for grace periods without any steady-state overhead.
+ */
+static int rcu_pending_cbs_show(struct seq_file *m, void *v)
+{
+ int cpu;
+ long done, wait, nxtrdy, nxt, lazy;
+ long total_done = 0, total_wait = 0, total_nxtrdy = 0;
+ long total_nxt = 0, total_lazy = 0;
+ struct rcu_data *rdp;
+ struct rcu_segcblist *rsclp;
+
+ seq_printf(m, "%-8s %10s %10s %10s %10s %10s\n",
+ "cpu", "done", "wait", "next_ready", "next", "lazy");
+
+ for_each_possible_cpu(cpu) {
+ rdp = per_cpu_ptr(&rcu_data, cpu);
+ rsclp = &rdp->cblist;
+
+ if (!rcu_segcblist_is_enabled(rsclp))
+ continue;
+
+ done = rcu_segcblist_get_seglen(rsclp, RCU_DONE_TAIL);
+ wait = rcu_segcblist_get_seglen(rsclp, RCU_WAIT_TAIL);
+ nxtrdy = rcu_segcblist_get_seglen(rsclp, RCU_NEXT_READY_TAIL);
+ nxt = rcu_segcblist_get_seglen(rsclp, RCU_NEXT_TAIL);
+ lazy = READ_ONCE(rdp->lazy_len);
+
+ seq_printf(m, "%-8d %10ld %10ld %10ld %10ld %10ld\n",
+ cpu, done, wait, nxtrdy, nxt, lazy);
+
+ total_done += done;
+ total_wait += wait;
+ total_nxtrdy += nxtrdy;
+ total_nxt += nxt;
+ total_lazy += lazy;
+ }
+
+ seq_printf(m, "%-8s %10ld %10ld %10ld %10ld %10ld\n",
+ "total", total_done, total_wait, total_nxtrdy,
+ total_nxt, total_lazy);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(rcu_pending_cbs);
+
+static struct dentry *rcu_debugfs_dir;
+
+static int __init rcu_debugfs_init(void)
+{
+ rcu_debugfs_dir = debugfs_create_dir("rcu", NULL);
+ debugfs_create_file("pending_cbs", 0444, rcu_debugfs_dir,
+ NULL, &rcu_pending_cbs_fops);
+
+ return 0;
+}
+late_initcall(rcu_debugfs_init);
+
+#endif // #ifdef CONFIG_DEBUG_FS
+
#ifdef CONFIG_PROVE_RCU
#define RCU_STALL_DELAY_DELTA (5 * HZ)
#else

--
2.53.0-Meta