1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | /* |
3 | * bch2_time_stats - collect statistics on events that have a duration, with nicely |
4 | * formatted textual output on demand |
5 | * |
6 | * - percpu buffering of event collection: cheap enough to shotgun |
7 | * everywhere without worrying about overhead |
8 | * |
9 | * tracks: |
10 | * - number of events |
11 | * - maximum event duration ever seen |
12 | * - sum of all event durations |
13 | * - average event duration, standard and weighted |
14 | * - standard deviation of event durations, standard and weighted |
15 | * and analagous statistics for the frequency of events |
16 | * |
17 | * We provide both mean and weighted mean (exponentially weighted), and standard |
18 | * deviation and weighted standard deviation, to give an efficient-to-compute |
19 | * view of current behaviour versus. average behaviour - "did this event source |
20 | * just become wonky, or is this typical?". |
21 | * |
22 | * Particularly useful for tracking down latency issues. |
23 | */ |
24 | #ifndef _BCACHEFS_TIME_STATS_H |
25 | #define _BCACHEFS_TIME_STATS_H |
26 | |
27 | #include <linux/sched/clock.h> |
28 | #include <linux/spinlock_types.h> |
29 | #include <linux/string.h> |
30 | |
31 | #include "mean_and_variance.h" |
32 | |
33 | struct time_unit { |
34 | const char *name; |
35 | u64 nsecs; |
36 | }; |
37 | |
38 | /* |
39 | * given a nanosecond value, pick the preferred time units for printing: |
40 | */ |
41 | const struct time_unit *bch2_pick_time_units(u64 ns); |
42 | |
43 | /* |
44 | * quantiles - do not use: |
45 | * |
46 | * Only enabled if bch2_time_stats->quantiles_enabled has been manually set - don't |
47 | * use in new code. |
48 | */ |
49 | |
50 | #define NR_QUANTILES 15 |
51 | #define QUANTILE_IDX(i) inorder_to_eytzinger0(i, NR_QUANTILES) |
52 | #define QUANTILE_FIRST eytzinger0_first(NR_QUANTILES) |
53 | #define QUANTILE_LAST eytzinger0_last(NR_QUANTILES) |
54 | |
55 | struct quantiles { |
56 | struct quantile_entry { |
57 | u64 m; |
58 | u64 step; |
59 | } entries[NR_QUANTILES]; |
60 | }; |
61 | |
62 | struct time_stat_buffer { |
63 | unsigned nr; |
64 | struct time_stat_buffer_entry { |
65 | u64 start; |
66 | u64 end; |
67 | } entries[31]; |
68 | }; |
69 | |
70 | struct bch2_time_stats { |
71 | spinlock_t lock; |
72 | bool have_quantiles; |
73 | /* all fields are in nanoseconds */ |
74 | u64 min_duration; |
75 | u64 max_duration; |
76 | u64 total_duration; |
77 | u64 max_freq; |
78 | u64 min_freq; |
79 | u64 last_event; |
80 | u64 last_event_start; |
81 | |
82 | struct mean_and_variance duration_stats; |
83 | struct mean_and_variance freq_stats; |
84 | |
85 | /* default weight for weighted mean and variance calculations */ |
86 | #define TIME_STATS_MV_WEIGHT 8 |
87 | |
88 | struct mean_and_variance_weighted duration_stats_weighted; |
89 | struct mean_and_variance_weighted freq_stats_weighted; |
90 | struct time_stat_buffer __percpu *buffer; |
91 | }; |
92 | |
93 | struct bch2_time_stats_quantiles { |
94 | struct bch2_time_stats stats; |
95 | struct quantiles quantiles; |
96 | }; |
97 | |
98 | static inline struct quantiles *time_stats_to_quantiles(struct bch2_time_stats *stats) |
99 | { |
100 | return stats->have_quantiles |
101 | ? &container_of(stats, struct bch2_time_stats_quantiles, stats)->quantiles |
102 | : NULL; |
103 | } |
104 | |
105 | void __bch2_time_stats_clear_buffer(struct bch2_time_stats *, struct time_stat_buffer *); |
106 | void __bch2_time_stats_update(struct bch2_time_stats *stats, u64, u64); |
107 | |
108 | /** |
109 | * time_stats_update - collect a new event being tracked |
110 | * |
111 | * @stats - bch2_time_stats to update |
112 | * @start - start time of event, recorded with local_clock() |
113 | * |
114 | * The end duration of the event will be the current time |
115 | */ |
116 | static inline void bch2_time_stats_update(struct bch2_time_stats *stats, u64 start) |
117 | { |
118 | __bch2_time_stats_update(stats, start, local_clock()); |
119 | } |
120 | |
121 | /** |
122 | * track_event_change - track state change events |
123 | * |
124 | * @stats - bch2_time_stats to update |
125 | * @v - new state, true or false |
126 | * |
127 | * Use this when tracking time stats for state changes, i.e. resource X becoming |
128 | * blocked/unblocked. |
129 | */ |
130 | static inline bool track_event_change(struct bch2_time_stats *stats, bool v) |
131 | { |
132 | if (v != !!stats->last_event_start) { |
133 | if (!v) { |
134 | bch2_time_stats_update(stats, start: stats->last_event_start); |
135 | stats->last_event_start = 0; |
136 | } else { |
137 | stats->last_event_start = local_clock() ?: 1; |
138 | return true; |
139 | } |
140 | } |
141 | |
142 | return false; |
143 | } |
144 | |
145 | void bch2_time_stats_exit(struct bch2_time_stats *); |
146 | void bch2_time_stats_init(struct bch2_time_stats *); |
147 | |
148 | static inline void bch2_time_stats_quantiles_exit(struct bch2_time_stats_quantiles *statq) |
149 | { |
150 | bch2_time_stats_exit(&statq->stats); |
151 | } |
152 | static inline void bch2_time_stats_quantiles_init(struct bch2_time_stats_quantiles *statq) |
153 | { |
154 | bch2_time_stats_init(&statq->stats); |
155 | statq->stats.have_quantiles = true; |
156 | memset(&statq->quantiles, 0, sizeof(statq->quantiles)); |
157 | } |
158 | |
159 | #endif /* _BCACHEFS_TIME_STATS_H */ |
160 | |