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 <[email protected]> --- .../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
