[PATCH v2 3/3] rv: collect per-edge dwell time for per-cpu DA/HA monitors
From: Tobias Schaffner
Date: Fri Sep 11 2026 - 08:51:50 EST
With the core facility in place, hook it into the DA/HA layer so that any
per-CPU automaton monitor also reports how long it lingers in each state.
Pack the automaton state and its entry timestamp into one word so the
transition cmpxchg updates both atomically. This prevents nested events
from attributing dwell time to the wrong edge and requires a native
64-bit cmpxchg.
Account a transition as soon as its state change is committed. An HA
constraint that rejects the transition can therefore reset the monitor
without leaving an accounting window for nested events.
Add a selftest that enables a per-CPU monitor, checks the stats file
appears and is populated under load, and skips cleanly otherwise.
Signed-off-by: Tobias Schaffner <tobias.schaffner@xxxxxxxxxxx>
---
include/linux/rv.h | 9 ++++-
include/rv/da_monitor.h | 16 +++++++--
include/rv/ha_monitor.h | 2 +-
tools/testing/selftests/verification/config | 3 ++
.../verification/test.d/rv_edge_stats.tc | 33 +++++++++++++++++++
5 files changed, 58 insertions(+), 5 deletions(-)
create mode 100644 tools/testing/selftests/verification/test.d/rv_edge_stats.tc
diff --git a/include/linux/rv.h b/include/linux/rv.h
index 541ba404926a..6c5c2fa7dd68 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -21,12 +21,19 @@
#include <linux/list.h>
#include <linux/types.h>
+/* Edge statistics pack the state and its entry timestamp into one word. */
+#ifdef CONFIG_RV_EDGE_STAT
+#define da_state_t unsigned long
+#else
+#define da_state_t unsigned int
+#endif
+
/*
* Deterministic automaton per-object variables.
*/
struct da_monitor {
bool monitoring;
- unsigned int curr_state;
+ da_state_t curr_state;
};
#ifdef CONFIG_RV_LTL_MONITOR
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 34b8fba9ecd4..bd7bd422b39a 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -16,6 +16,7 @@
#include <rv/automata.h>
#include <linux/rv.h>
+#include <rv/edge_stat.h>
#include <linux/stringify.h>
#include <linux/bug.h>
#include <linux/sched.h>
@@ -112,7 +113,7 @@ static inline void da_monitor_reset(struct da_monitor *da_mon)
*/
static inline void da_monitor_start(struct da_monitor *da_mon)
{
- da_mon->curr_state = model_get_initial_state();
+ da_mon->curr_state = da_state_entered(model_get_initial_state());
da_monitor_init_hook(da_mon);
/* Pairs with smp_load_acquire in da_monitoring(). */
smp_store_release(&da_mon->monitoring, 1);
@@ -275,6 +276,11 @@ static inline void da_monitor_reset_state_all(void)
*/
static inline int da_monitor_init(void)
{
+ int retval = rv_edge_stats_create(rv_this.root_d);
+
+ if (retval)
+ return retval;
+
da_monitor_reset_state_all();
return 0;
}
@@ -286,6 +292,7 @@ static inline void da_monitor_destroy(void)
{
da_monitor_reset_all();
da_monitor_sync_hook();
+ rv_edge_stats_remove();
}
#ifndef da_implicit_guard
@@ -683,9 +690,11 @@ static inline void da_trace_error(struct da_monitor *da_mon,
static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_type id)
{
enum states curr_state, next_state;
+ da_state_t old;
- curr_state = READ_ONCE(da_mon->curr_state);
+ old = READ_ONCE(da_mon->curr_state);
for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
+ curr_state = da_state_of(old);
next_state = model_get_next_state(curr_state, event);
if (next_state == INVALID_STATE) {
react(curr_state, event);
@@ -693,7 +702,8 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_
model_get_event_name(event), id);
return false;
}
- if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+ if (likely(da_state_try_commit(&da_mon->curr_state, &old,
+ next_state, event))) {
if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id))
return false;
da_trace_event(da_mon, model_get_state_name(curr_state),
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 28d3c74cabfc..4def2aac92de 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -312,7 +312,7 @@ static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_mon)
if (unlikely(READ_ONCE(ha_mon_destroying)))
return;
/* Ensure consistent curr_state if we race with da_monitor_reset */
- curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
+ curr_state = da_state_of(smp_load_acquire(&ha_mon->da_mon.curr_state));
if (unlikely(!da_monitor_handling_event(&ha_mon->da_mon)))
return;
diff --git a/tools/testing/selftests/verification/config b/tools/testing/selftests/verification/config
index 43072c1c38f4..caf3a96ac8b4 100644
--- a/tools/testing/selftests/verification/config
+++ b/tools/testing/selftests/verification/config
@@ -1 +1,4 @@
CONFIG_RV=y
+CONFIG_PREEMPT_TRACER=y
+CONFIG_RV_MON_WIP=y
+CONFIG_RV_EDGE_STAT=y
diff --git a/tools/testing/selftests/verification/test.d/rv_edge_stats.tc b/tools/testing/selftests/verification/test.d/rv_edge_stats.tc
new file mode 100644
index 000000000000..616bb0b5166e
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/rv_edge_stats.tc
@@ -0,0 +1,33 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test per-edge dwell-time statistics (stats)
+
+MON=wip
+
+check_requires "$MON:monitor"
+
+# The "stats" file is created on enable; if it is still missing the kernel was
+# built without CONFIG_RV_EDGE_STAT, so skip.
+echo 1 > "monitors/$MON/enable"
+if [ ! -e "monitors/$MON/stats" ]; then
+ echo 0 > "monitors/$MON/enable"
+ echo "CONFIG_RV_EDGE_STAT is not enabled."
+ exit_unsupported
+fi
+
+# The first line is the column header; the body has one line per (cpu, edge).
+head -n1 "monitors/$MON/stats" | grep -q "^# cpu edge label count sum_ns max_ns"
+[ "$(grep -cvE '^#' "monitors/$MON/stats")" -gt 0 ]
+
+# Drive some scheduler activity so the automaton records transitions.
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ (true) &
+ wait
+done
+
+# At least one edge must now show a non-zero count. Do not require a non-zero
+# dwell: a coarse local_clock() may legitimately return the same value twice.
+grep -vE '^#' "monitors/$MON/stats" | \
+ awk '$4 > 0 && $5 >= $6 { hit = 1 } END { exit !hit }'
+
+echo 0 > "monitors/$MON/enable"
--
2.43.0