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.

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 <[email protected]>
---
 include/linux/rv.h                            |  4 ++
 include/rv/da_monitor.h                       | 53 +++++++++++++++++++
 tools/testing/selftests/verification/config   |  1 +
 .../verification/test.d/rv_edge_stats.tc      | 32 +++++++++++
 4 files changed, 90 insertions(+)
 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 7eeecce17e50..09363f79ca90 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -27,6 +27,10 @@
 struct da_monitor {
        bool            monitoring;
        unsigned int    curr_state;
+#ifdef CONFIG_RV_EDGE_STAT
+       /* local_clock() ns when curr_state was entered; 0 = not yet stamped. */
+       u64             state_ns;
+#endif
 };
 
 #ifdef CONFIG_RV_LTL_MONITOR
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 34b8fba9ecd4..59ca9a286c10 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -16,6 +16,8 @@
 
 #include <rv/automata.h>
 #include <linux/rv.h>
+#include <linux/rv_edge_stat.h>
+#include <linux/sched/clock.h>
 #include <linux/stringify.h>
 #include <linux/bug.h>
 #include <linux/sched.h>
@@ -30,6 +32,54 @@
 
 static struct rv_monitor rv_this;
 
+/* per-edge dwell statistics, wired up for per-cpu monitors. */
+#if defined(CONFIG_RV_EDGE_STAT) && RV_MON_TYPE == RV_MON_PER_CPU
+static void
+rv_this_edge_name(unsigned int edge, char *buf, size_t len)
+{
+       snprintf(buf, len, "%s:%s", model_get_state_name(edge / EVENT_MAX),
+                model_get_event_name(edge % EVENT_MAX));
+}
+
+static const struct rv_edge_cfg rv_this_edge_cfg = {
+       .n_edges   = STATE_MAX * EVENT_MAX,
+       .edge_name = rv_this_edge_name,
+};
+
+/* Hand the model's edge descriptor to the core; called from 
da_monitor_init(). */
+static inline void rv_edge_bind(void)
+{
+       rv_this.edge_cfg = &rv_this_edge_cfg;
+}
+
+/* Stamp the moment a state is entered, so its dwell can be timed on exit. */
+static __always_inline void rv_da_edge_enter(struct da_monitor *da_mon)
+{
+       da_mon->state_ns = local_clock();
+}
+
+/* Account the dwell in @curr, then stamp entry into the next state. */
+static __always_inline void
+rv_da_edge_account(struct da_monitor *da_mon, enum states curr, enum events ev)
+{
+       u64 now = local_clock();
+       u64 prev = da_mon->state_ns;
+
+       da_mon->state_ns = now;
+       /*
+        * local_clock() is not guaranteed monotonic; drop the sample if it did
+        * not advance so a backward step cannot underflow into a bogus dwell.
+        */
+       if (rv_this.edge_pcpu && prev && now > prev)
+               rv_edge_account(&rv_this, curr * EVENT_MAX + ev, now - prev);
+}
+#else
+static inline void rv_edge_bind(void) { }
+static inline void rv_da_edge_enter(struct da_monitor *da_mon) { }
+static inline void
+rv_da_edge_account(struct da_monitor *da_mon, enum states curr, enum events 
ev) { }
+#endif /* CONFIG_RV_EDGE_STAT && RV_MON_PER_CPU */
+
 /*
  * Hook to allow the implementation of hybrid automata: define it with a
  * function that takes curr_state, event and next_state and returns true if the
@@ -113,6 +163,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();
+       rv_da_edge_enter(da_mon);
        da_monitor_init_hook(da_mon);
        /* Pairs with smp_load_acquire in da_monitoring(). */
        smp_store_release(&da_mon->monitoring, 1);
@@ -275,6 +326,7 @@ static inline void da_monitor_reset_state_all(void)
  */
 static inline int da_monitor_init(void)
 {
+       rv_edge_bind();
        da_monitor_reset_state_all();
        return 0;
 }
@@ -696,6 +748,7 @@ static inline bool da_event(struct da_monitor *da_mon, enum 
events event, da_id_
                if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, 
next_state))) {
                        if (!da_monitor_event_hook(da_mon, curr_state, event, 
next_state, id))
                                return false;
+                       rv_da_edge_account(da_mon, curr_state, event);
                        da_trace_event(da_mon, model_get_state_name(curr_state),
                                       model_get_event_name(event),
                                       model_get_state_name(next_state),
diff --git a/tools/testing/selftests/verification/config 
b/tools/testing/selftests/verification/config
index 43072c1c38f4..de229777d5c8 100644
--- a/tools/testing/selftests/verification/config
+++ b/tools/testing/selftests/verification/config
@@ -1 +1,2 @@
 CONFIG_RV=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..5fb2384d938c
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/rv_edge_stats.tc
@@ -0,0 +1,32 @@
+#!/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 first 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 max_ns 
sum_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, with sum_ns >= max_ns > 0.
+grep -vE '^#' "monitors/$MON/stats" | \
+       awk '$4 > 0 && $5 > 0 && $6 >= $5 { hit = 1 } END { exit !hit }'
+
+echo 0 > "monitors/$MON/enable"
-- 
2.43.0


Reply via email to