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
