Linux Trace Kernel
 help / color / mirror / Atom feed
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


  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