Add CONFIG_RV_EDGE_STAT, an optional facility that records how long a monitor's automaton dwells in a state before leaving it and accounts that dwell to the edge it leaves by.
Keep the state and its entry timestamp in one word so a transition commits both with a single cmpxchg and nested transitions cannot charge a dwell to the wrong edge. This requires a native 64-bit cmpxchg. The counters are per-CPU and lock-free, so a monitor's hot path can update them without disabling interrupts and without perturbing the latency being measured. Only the owning CPU writes them, so a reader snapshots them with local64_read() with no IPI and no locking on the accounting path. Storage is a static per-CPU array of STATE_MAX * EVENT_MAX edges per monitor, exposed through a per-monitor "stats" tracefs file. Report whether the model defines each edge, so a count on an edge that should never be taken is visible. Nothing uses the facility yet. Signed-off-by: Tobias Schaffner <[email protected]> --- include/linux/rv.h | 7 ++ include/rv/edge_stat.h | 211 ++++++++++++++++++++++++++++++++++++++++ kernel/trace/rv/Kconfig | 12 +++ 3 files changed, 230 insertions(+) create mode 100644 include/rv/edge_stat.h diff --git a/include/linux/rv.h b/include/linux/rv.h index 541ba404926a..1dc7c45f476c 100644 --- a/include/linux/rv.h +++ b/include/linux/rv.h @@ -21,6 +21,13 @@ #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. */ diff --git a/include/rv/edge_stat.h b/include/rv/edge_stat.h new file mode 100644 index 000000000000..8b99e8520e1e --- /dev/null +++ b/include/rv/edge_stat.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Per-edge dwell-time statistics for RV monitors. + * + * Copyright (C) 2026 Siemens AG + * Author: Tobias Schaffner <[email protected]> + */ +#ifndef _RV_EDGE_STAT_H +#define _RV_EDGE_STAT_H + +#include <linux/rv.h> + +#ifdef CONFIG_RV_EDGE_STAT + +#include <linux/args.h> +#include <linux/atomic.h> +#include <linux/bitfield.h> +#include <linux/bug.h> +#include <linux/compiler.h> +#include <linux/percpu.h> +#include <linux/sched/clock.h> +#include <linux/seq_file.h> +#include <linux/types.h> +#include <asm/local64.h> +#include <rv/automata.h> +#include "rv.h" + +/* + * Per-CPU counters kept in local64_t so accounting is safe against interrupt + * and NMI nesting on the owning CPU without disabling interrupts. Only the + * owning CPU writes. + */ +struct rv_edge_stat { + local64_t count; + local64_t sum_ns; + local64_t max_ns; +}; + +/* + * State and entry timestamp share one word so a transition commits both with + * one cmpxchg. Comparing the timestamp also detects nested transitions that + * return to the same state. + */ +#define RV_STATE_BITS 8 +#define RV_STATE_MASK GENMASK(RV_STATE_BITS - 1, 0) +#define RV_TS_MASK GENMASK(BITS_PER_LONG - 1, RV_STATE_BITS) + +#define da_state_of(w) FIELD_GET(RV_STATE_MASK, w) +#define da_ts_of(w) FIELD_GET(RV_TS_MASK, w) +#define da_state_pack(s, ts) (FIELD_PREP(RV_TS_MASK, ts) | \ + FIELD_PREP(RV_STATE_MASK, s)) + +static __always_inline +void rv_edge_stat_account(struct rv_edge_stat *s, u64 dwell_ns) +{ + s64 max; + int i; + + local64_inc(&s->count); + local64_add(dwell_ns, &s->sum_ns); + + /* Keep the largest dwell; bound retries if a nested update races us. */ + max = local64_read(&s->max_ns); + for (i = 0; dwell_ns > (u64)max; i++) { + if (i == MAX_DA_RETRY_RACING_EVENTS) { + WARN_ONCE(1, "rv: edge-stat max update exceeded %d retries\n", + MAX_DA_RETRY_RACING_EVENTS); + break; + } + if (local64_try_cmpxchg(&s->max_ns, &max, dwell_ns)) + break; + } +} + +static_assert(STATE_MAX <= (1U << RV_STATE_BITS), + "automaton has more states than the packed state word can hold"); + +#if RV_MON_TYPE == RV_MON_PER_CPU + +/* + * One edge per (state, event) pair. Static storage avoids allocation lifetime + * races. Only the owning CPU writes, so readers can snapshot counters with + * local64_read(). + */ +#define RV_THIS_NR_EDGES (STATE_MAX * EVENT_MAX) + +#define DA_EDGE(state, event) ((state) * EVENT_MAX + (event)) +#define DA_EDGE_STATE(edge) ((edge) / EVENT_MAX) +#define DA_EDGE_EVENT(edge) ((edge) % EVENT_MAX) + +#define DA_MON_EDGES CONCATENATE(da_mon_edges_, MONITOR_NAME) + +static DEFINE_PER_CPU(struct rv_edge_stat[RV_THIS_NR_EDGES], DA_MON_EDGES); +static struct dentry *rv_this_stats_file; + +/* Called before tracepoints are registered, so memset cannot race an update. */ +static void rv_edge_stats_reset(void) +{ + int cpu; + + for_each_possible_cpu(cpu) + memset(per_cpu_ptr(DA_MON_EDGES, cpu), 0, sizeof(DA_MON_EDGES)); +} + +static int rv_edge_stats_show(struct seq_file *seq, void *v) +{ + unsigned int e; + int cpu; + + seq_puts(seq, "# cpu edge label valid count sum_ns max_ns\n"); + /* Include offline CPUs and keep output stable across CPU hotplug. */ + for_each_possible_cpu(cpu) { + struct rv_edge_stat *edges = per_cpu_ptr(DA_MON_EDGES, cpu); + + for (e = 0; e < RV_THIS_NR_EDGES; e++) { + enum states state = DA_EDGE_STATE(e); + enum events event = DA_EDGE_EVENT(e); + + /* Edges the model does not define must stay at zero. */ + seq_printf(seq, "%d %u %s:%s %d %llu %llu %llu\n", + cpu, e, + model_get_state_name(state), + model_get_event_name(event), + model_get_next_state(state, event) != INVALID_STATE, + (u64)local64_read(&edges[e].count), + (u64)local64_read(&edges[e].sum_ns), + (u64)local64_read(&edges[e].max_ns)); + } + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rv_edge_stats); + +static int rv_edge_stats_create(struct dentry *parent) +{ + rv_edge_stats_reset(); + rv_this_stats_file = rv_create_file("stats", RV_MODE_READ, parent, + NULL, &rv_edge_stats_fops); + return rv_this_stats_file ? 0 : -ENOMEM; +} + +static void rv_edge_stats_remove(void) +{ + rv_remove(rv_this_stats_file); + rv_this_stats_file = NULL; +} + +/* A zero timestamp marks resets, which can run on a different CPU. */ +#define da_state_entered(s) da_state_pack((s), local_clock()) + +static __always_inline void +rv_edge_account(da_state_t old, da_state_t new, enum states curr, enum events ev) +{ + struct rv_edge_stat *edges; + u64 prev = da_ts_of(old); + + if (unlikely(!prev)) + return; + + edges = this_cpu_ptr(DA_MON_EDGES); + rv_edge_stat_account(&edges[DA_EDGE(curr, ev)], + (da_ts_of(new) - prev) & FIELD_MAX(RV_TS_MASK)); +} + +#else /* per-cpu accounting off for this monitor type */ + +#define da_state_entered(s) ((da_state_t)(s)) + +static __always_inline void +rv_edge_account(da_state_t old, da_state_t new, enum states curr, enum events ev) { } + +#endif /* RV_MON_TYPE == RV_MON_PER_CPU */ + +static __always_inline bool +da_state_try_commit(da_state_t *word, da_state_t *old, enum states next, + enum events event) +{ + da_state_t prev = *old; + da_state_t new = da_state_entered(next); + + if (!try_cmpxchg(word, old, new)) + return false; + + rv_edge_account(prev, new, da_state_of(prev), event); + return true; +} + +#else /* !CONFIG_RV_EDGE_STAT */ + +/* + * Feature off: only the hooks the DA/HA layer calls are provided, as trivial + * pass-throughs. da_state_t is a plain state word with no packed timestamp. + */ +#define da_state_of(w) (w) +#define da_state_entered(s) (s) + +static __always_inline bool +da_state_try_commit(da_state_t *word, da_state_t *old, enum states next, + enum events event) +{ + return try_cmpxchg(word, old, next); +} + +#if RV_MON_TYPE == RV_MON_PER_CPU +static inline int rv_edge_stats_create(struct dentry *parent) { return 0; } +static inline void rv_edge_stats_remove(void) { } +#endif + +#endif /* CONFIG_RV_EDGE_STAT */ + +#endif /* _RV_EDGE_STAT_H */ diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig index efa930f94ea4..54f7c50a74a8 100644 --- a/kernel/trace/rv/Kconfig +++ b/kernel/trace/rv/Kconfig @@ -59,6 +59,18 @@ 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 && 64BIT && DA_MON_EVENTS_IMPLICIT + help + Record per-edge dwell-time statistics for per-cpu deterministic and + hybrid automaton 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" -- 2.53.0
