| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* |
| * bch2_time_stats - collect statistics on events that have a duration, with nicely |
| * formatted textual output on demand |
| * |
| * - percpu buffering of event collection: cheap enough to shotgun |
| * everywhere without worrying about overhead |
| * |
| * tracks: |
| * - number of events |
| * - maximum event duration ever seen |
| * - sum of all event durations |
| * - average event duration, standard and weighted |
| * - standard deviation of event durations, standard and weighted |
| * and analagous statistics for the frequency of events |
| * |
| * We provide both mean and weighted mean (exponentially weighted), and standard |
| * deviation and weighted standard deviation, to give an efficient-to-compute |
| * view of current behaviour versus. average behaviour - "did this event source |
| * just become wonky, or is this typical?". |
| * |
| * Particularly useful for tracking down latency issues. |
| */ |
| #ifndef _BCACHEFS_TIME_STATS_H |
| #define _BCACHEFS_TIME_STATS_H |
| |
| #include <linux/sched/clock.h> |
| #include <linux/spinlock_types.h> |
| #include <linux/string.h> |
| |
| #include "mean_and_variance.h" |
| |
| struct time_unit { |
| const char *name; |
| u64 nsecs; |
| }; |
| |
| /* |
| * given a nanosecond value, pick the preferred time units for printing: |
| */ |
| const struct time_unit *bch2_pick_time_units(u64 ns); |
| |
| /* |
| * quantiles - do not use: |
| * |
| * Only enabled if bch2_time_stats->quantiles_enabled has been manually set - don't |
| * use in new code. |
| */ |
| |
| #define NR_QUANTILES 15 |
| #define QUANTILE_IDX(i) inorder_to_eytzinger0(i, NR_QUANTILES) |
| #define QUANTILE_FIRST eytzinger0_first(NR_QUANTILES) |
| #define QUANTILE_LAST eytzinger0_last(NR_QUANTILES) |
| |
| struct quantiles { |
| struct quantile_entry { |
| u64 m; |
| u64 step; |
| } entries[NR_QUANTILES]; |
| }; |
| |
| struct time_stat_buffer { |
| unsigned nr; |
| struct time_stat_buffer_entry { |
| u64 start; |
| u64 end; |
| } entries[31]; |
| }; |
| |
| struct bch2_time_stats { |
| spinlock_t lock; |
| bool have_quantiles; |
| /* all fields are in nanoseconds */ |
| u64 min_duration; |
| u64 max_duration; |
| u64 total_duration; |
| u64 max_freq; |
| u64 min_freq; |
| u64 last_event; |
| u64 last_event_start; |
| |
| struct mean_and_variance duration_stats; |
| struct mean_and_variance freq_stats; |
| |
| /* default weight for weighted mean and variance calculations */ |
| #define TIME_STATS_MV_WEIGHT 8 |
| |
| struct mean_and_variance_weighted duration_stats_weighted; |
| struct mean_and_variance_weighted freq_stats_weighted; |
| struct time_stat_buffer __percpu *buffer; |
| }; |
| |
| struct bch2_time_stats_quantiles { |
| struct bch2_time_stats stats; |
| struct quantiles quantiles; |
| }; |
| |
| static inline struct quantiles *time_stats_to_quantiles(struct bch2_time_stats *stats) |
| { |
| return stats->have_quantiles |
| ? &container_of(stats, struct bch2_time_stats_quantiles, stats)->quantiles |
| : NULL; |
| } |
| |
| void __bch2_time_stats_clear_buffer(struct bch2_time_stats *, struct time_stat_buffer *); |
| void __bch2_time_stats_update(struct bch2_time_stats *stats, u64, u64); |
| |
| /** |
| * time_stats_update - collect a new event being tracked |
| * |
| * @stats - bch2_time_stats to update |
| * @start - start time of event, recorded with local_clock() |
| * |
| * The end duration of the event will be the current time |
| */ |
| static inline void bch2_time_stats_update(struct bch2_time_stats *stats, u64 start) |
| { |
| __bch2_time_stats_update(stats, start, local_clock()); |
| } |
| |
| /** |
| * track_event_change - track state change events |
| * |
| * @stats - bch2_time_stats to update |
| * @v - new state, true or false |
| * |
| * Use this when tracking time stats for state changes, i.e. resource X becoming |
| * blocked/unblocked. |
| */ |
| static inline bool track_event_change(struct bch2_time_stats *stats, bool v) |
| { |
| if (v != !!stats->last_event_start) { |
| if (!v) { |
| bch2_time_stats_update(stats, stats->last_event_start); |
| stats->last_event_start = 0; |
| } else { |
| stats->last_event_start = local_clock() ?: 1; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void bch2_time_stats_exit(struct bch2_time_stats *); |
| void bch2_time_stats_init(struct bch2_time_stats *); |
| |
| static inline void bch2_time_stats_quantiles_exit(struct bch2_time_stats_quantiles *statq) |
| { |
| bch2_time_stats_exit(&statq->stats); |
| } |
| static inline void bch2_time_stats_quantiles_init(struct bch2_time_stats_quantiles *statq) |
| { |
| bch2_time_stats_init(&statq->stats); |
| statq->stats.have_quantiles = true; |
| memset(&statq->quantiles, 0, sizeof(statq->quantiles)); |
| } |
| |
| #endif /* _BCACHEFS_TIME_STATS_H */ |