From: Tobias Schaffner <tobias.schaffner@siemens.com>
To: Steven Rostedt <rostedt@goodmis.org>,
Gabriele Monaco <gmonaco@redhat.com>
Cc: Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Masami Hiramatsu <mhiramat@kernel.org>,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
Jan Kiszka <jan.kiszka@siemens.com>,
Philippe Gerum <rpm@xenomai.org>,
linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
Tobias Schaffner <tobias.schaffner@siemens.com>
Subject: [RFC 2/3] rv: add per-monitor edge-stat facility and stats file
Date: Thu, 27 Aug 2026 09:23:59 +0200 [thread overview]
Message-ID: <20260827072400.45734-3-tobias.schaffner@siemens.com> (raw)
In-Reply-To: <20260827072400.45734-1-tobias.schaffner@siemens.com>
Add CONFIG_RV_EDGE_STAT, an optional feature that records how long a
monitor's automaton dwells in a state and exposes it per edge through a
per-monitor "stats" tracefs file.
The core allocates a per-CPU buffer on first enable and reports, per edge
and per CPU, the count, maximum and summed dwell time. Only the owning CPU
writes the counters, so a reader snapshots them with local64_read() with no
IPI and no locking on the accounting path.
Signed-off-by: Tobias Schaffner <tobias.schaffner@siemens.com>
---
.../trace/rv/runtime-verification.rst | 24 ++++
include/linux/rv.h | 14 +++
include/linux/rv_edge_stat.h | 25 +++-
kernel/trace/rv/Kconfig | 11 ++
kernel/trace/rv/rv.c | 116 +++++++++++++++++-
5 files changed, 184 insertions(+), 6 deletions(-)
diff --git a/Documentation/trace/rv/runtime-verification.rst b/Documentation/trace/rv/runtime-verification.rst
index c700dde9259c..194b2f9db461 100644
--- a/Documentation/trace/rv/runtime-verification.rst
+++ b/Documentation/trace/rv/runtime-verification.rst
@@ -229,3 +229,27 @@ For example::
nop
[panic]
printk
+
+**monitors/MONITOR/stats**
+
+Present only when the kernel is built with CONFIG_RV_EDGE_STAT=y and *MONITOR*
+is a per-cpu DA/HA (automaton) monitor. It reports how long the automaton
+dwells in each state before leaving it, timed with local_clock() and accounted
+per outgoing edge and per CPU.
+
+- The first line is a header naming the columns.
+- Each following line describes one edge on one CPU::
+
+ cpu edge label count max_ns sum_ns
+
+ *count* is the number of times the edge was taken, *max_ns* and *sum_ns* are
+ the worst and total dwell in nanoseconds, and *label* is "state:event".
+
+The counters are reset each time the monitor is enabled.
+
+For example::
+
+ # cat monitors/wip/stats
+ # cpu edge label count max_ns sum_ns
+ 0 0 preemptive:preempt_disable 4210 183200 95501200
+ 0 4 non_preemptive:preempt_enable 4208 42600 3812900
diff --git a/include/linux/rv.h b/include/linux/rv.h
index 541ba404926a..7eeecce17e50 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -136,6 +136,16 @@ struct rv_reactor {
};
#endif
+/**
+ * struct rv_edge_cfg - per-edge dwell-time statistics for a monitor
+ * @n_edges: number of automaton edges (STATE_MAX * EVENT_MAX)
+ * @edge_name: optional, write a human name for @edge into @buf (may be NULL)
+ */
+struct rv_edge_cfg {
+ unsigned int n_edges;
+ void (*edge_name)(unsigned int edge, char *buf, size_t len);
+};
+
struct rv_monitor {
const char *name;
const char *description;
@@ -146,6 +156,10 @@ struct rv_monitor {
#ifdef CONFIG_RV_REACTORS
struct rv_reactor *reactor;
__printf(1, 0) void (*react)(const char *msg, va_list args);
+#endif
+#ifdef CONFIG_RV_EDGE_STAT
+ const struct rv_edge_cfg *edge_cfg;
+ void __percpu *edge_pcpu;
#endif
struct list_head list;
struct rv_monitor *parent;
diff --git a/include/linux/rv_edge_stat.h b/include/linux/rv_edge_stat.h
index 751de8074dcc..fda30ff728a1 100644
--- a/include/linux/rv_edge_stat.h
+++ b/include/linux/rv_edge_stat.h
@@ -9,14 +9,11 @@
#define _LINUX_RV_EDGE_STAT_H
#include <linux/compiler.h>
+#include <linux/percpu.h>
+#include <linux/rv.h>
#include <linux/types.h>
#include <asm/local64.h>
-/*
- * Per-CPU counters kept in local64_t so accounting is safe against interrupt
- * and NMI nesting on the owning CPU without disabling interrupts -- the same
- * approach the trace ring buffer uses. Only the owning CPU writes.
- */
struct rv_edge_stat {
local64_t count;
local64_t sum_ns;
@@ -42,4 +39,22 @@ void rv_edge_stat_account(struct rv_edge_stat *s, u64 dwell_ns)
}
}
+#ifdef CONFIG_RV_EDGE_STAT
+/**
+ * rv_edge_account - record a dwell of @dwell_ns on @edge of monitor @mon
+ *
+ * Cheap and lock-free: the local64_t counters make this safe against interrupt
+ * and NMI nesting on the current CPU without disabling interrupts, so it does
+ * not perturb the latency being measured. The caller only needs to stay on its
+ * CPU for the call (as tracepoint probes already do).
+ */
+static __always_inline void
+rv_edge_account(struct rv_monitor *mon, unsigned int edge, u64 dwell_ns)
+{
+ struct rv_edge_stat *e = this_cpu_ptr(mon->edge_pcpu);
+
+ rv_edge_stat_account(&e[edge], dwell_ns);
+}
+#endif /* CONFIG_RV_EDGE_STAT */
+
#endif /* _LINUX_RV_EDGE_STAT_H */
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 3884b14df375..9d76dff394ca 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -59,6 +59,17 @@ config RV_PER_TASK_MONITORS
This option configures the maximum number of per-task RV monitors that can run
simultaneously.
+config RV_EDGE_STAT
+ bool "Per-edge dwell-time statistics"
+ depends on RV
+ help
+ Record per-edge dwell-time statistics for per-cpu DA/HA monitors and
+ expose them through a per-monitor "stats" tracefs file. This times
+ each monitored automaton transition with local_clock(), so leave it
+ off if you do not need the statistics.
+
+ If unsure, say N.
+
source "kernel/trace/rv/monitors/wip/Kconfig"
source "kernel/trace/rv/monitors/wwnr/Kconfig"
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index ee4e68102f17..88a0bbaec4d0 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -142,6 +142,12 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
+#include <linux/seq_file.h>
+#ifdef CONFIG_RV_EDGE_STAT
+#include <linux/percpu.h>
+#include <linux/rv_edge_stat.h>
+#include <linux/smp.h>
+#endif
#ifdef CONFIG_RV_MON_EVENTS
#define CREATE_TRACE_POINTS
@@ -278,6 +284,9 @@ static void rv_disable_single(struct rv_monitor *mon)
__rv_disable_monitor(mon, true);
}
+static int rv_edge_setup(struct rv_monitor *mon);
+static void rv_edge_reset(struct rv_monitor *mon);
+
static int rv_enable_single(struct rv_monitor *mon)
{
int retval;
@@ -289,9 +298,15 @@ static int rv_enable_single(struct rv_monitor *mon)
retval = mon->enable();
- if (!retval)
+ if (!retval) {
mon->enabled = 1;
+ if (rv_edge_setup(mon))
+ pr_warn("rv: %s: edge statistics unavailable (out of memory)\n",
+ mon->name);
+ rv_edge_reset(mon);
+ }
+
return retval;
}
@@ -412,6 +427,101 @@ static const struct file_operations interface_desc_fops = {
.read = monitor_desc_read_data,
};
+#ifdef CONFIG_RV_EDGE_STAT
+static size_t rv_edge_blob_size(const struct rv_monitor *mon)
+{
+ return mon->edge_cfg->n_edges * sizeof(struct rv_edge_stat);
+}
+
+static void rv_edge_reset_ipi(void *info)
+{
+ struct rv_monitor *mon = info;
+
+ memset(this_cpu_ptr(mon->edge_pcpu), 0, rv_edge_blob_size(mon));
+}
+
+/* rv_edge_reset - zero the statistics; call from a monitor reset/enable. */
+static void rv_edge_reset(struct rv_monitor *mon)
+{
+ if (mon->edge_pcpu)
+ on_each_cpu(rv_edge_reset_ipi, mon, 1);
+}
+
+/*
+ * The counters are per-CPU and only the owning CPU writes them, so a reader on
+ * any CPU can snapshot them with local64_read().
+ */
+static int rv_edge_stats_show(struct seq_file *seq, void *v)
+{
+ struct rv_monitor *mon = seq->private;
+ const struct rv_edge_cfg *cfg = mon->edge_cfg;
+ unsigned int e;
+ int cpu;
+
+ seq_puts(seq, "# cpu edge label count max_ns sum_ns\n");
+
+ if (!mon->edge_pcpu)
+ return 0;
+
+ for_each_online_cpu(cpu) {
+ struct rv_edge_stat *s = per_cpu_ptr(mon->edge_pcpu, cpu);
+
+ for (e = 0; e < cfg->n_edges; e++) {
+ char lbl[48] = "";
+
+ if (cfg->edge_name)
+ cfg->edge_name(e, lbl, sizeof(lbl));
+ seq_printf(seq, "%d %u %s %llu %llu %llu\n",
+ cpu, e, lbl,
+ (u64)local64_read(&s[e].count),
+ (u64)local64_read(&s[e].max_ns),
+ (u64)local64_read(&s[e].sum_ns));
+ }
+ }
+ return 0;
+}
+
+static int rv_edge_stats_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rv_edge_stats_show, inode->i_private);
+}
+
+static const struct file_operations rv_edge_stats_fops = {
+ .open = rv_edge_stats_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * Allocate the per-CPU buffer and expose stats. Done on first enable
+ * rather than at registration because a DA/HA monitor's edge_cfg is bound by
+ * da_monitor_init(), which runs from the monitor's enable path.
+ */
+static int rv_edge_setup(struct rv_monitor *mon)
+{
+ if (!mon->edge_cfg || !mon->edge_cfg->n_edges || mon->edge_pcpu)
+ return 0;
+
+ mon->edge_pcpu = __alloc_percpu(rv_edge_blob_size(mon),
+ __alignof__(struct rv_edge_stat));
+ if (!mon->edge_pcpu)
+ return -ENOMEM;
+
+ if (!rv_create_file("stats", RV_MODE_READ, mon->root_d, mon,
+ &rv_edge_stats_fops)) {
+ free_percpu(mon->edge_pcpu);
+ mon->edge_pcpu = NULL;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+#else
+static int rv_edge_setup(struct rv_monitor *mon) { return 0; }
+static void rv_edge_reset(struct rv_monitor *mon) { }
+#endif /* CONFIG_RV_EDGE_STAT */
+
/*
* During the registration of a monitor, this function creates
* the monitor dir, where the specific options of the monitor
@@ -747,6 +857,10 @@ static const struct file_operations monitoring_on_fops = {
static void destroy_monitor_dir(struct rv_monitor *mon)
{
+#ifdef CONFIG_RV_EDGE_STAT
+ free_percpu(mon->edge_pcpu);
+ mon->edge_pcpu = NULL;
+#endif
rv_remove(mon->root_d);
}
--
2.43.0
next prev parent reply other threads:[~2026-08-27 7:24 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-27 7:23 [RFC 0/3] rv: per-edge dwell-time statistics for per-cpu monitors Tobias Schaffner
2026-08-27 7:23 ` [RFC 1/3] rv: add per-edge dwell-time statistics primitive Tobias Schaffner
2026-08-27 7:34 ` sashiko-bot
2026-08-27 8:22 ` Gabriele Monaco
2026-08-27 10:10 ` Tobias Schaffner
2026-08-27 7:23 ` Tobias Schaffner [this message]
2026-08-27 7:39 ` [RFC 2/3] rv: add per-monitor edge-stat facility and stats file sashiko-bot
2026-08-27 7:24 ` [RFC 3/3] rv: collect per-edge dwell time for per-cpu DA/HA monitors Tobias Schaffner
2026-08-27 7:37 ` sashiko-bot
2026-08-27 8:18 ` [RFC 0/3] rv: per-edge dwell-time statistics for per-cpu monitors Gabriele Monaco
2026-08-27 18:21 ` Tobias Schaffner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260827072400.45734-3-tobias.schaffner@siemens.com \
--to=tobias.schaffner@siemens.com \
--cc=corbet@lwn.net \
--cc=gmonaco@redhat.com \
--cc=jan.kiszka@siemens.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=mhiramat@kernel.org \
--cc=rostedt@goodmis.org \
--cc=rpm@xenomai.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox