// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include "eytzinger.h" #include "time_stats.h" static const struct time_unit time_units[] = { { "ns", 1 }, { "us", NSEC_PER_USEC }, { "ms", NSEC_PER_MSEC }, { "s", NSEC_PER_SEC }, { "m", (u64) NSEC_PER_SEC * 60}, { "h", (u64) NSEC_PER_SEC * 3600}, { "d", (u64) NSEC_PER_SEC * 3600 * 24}, { "w", (u64) NSEC_PER_SEC * 3600 * 24 * 7}, { "y", (u64) NSEC_PER_SEC * ((3600 * 24 * 7 * 365) + (3600 * (24 / 4) * 7))}, /* 365.25d */ { "eon", U64_MAX }, }; const struct time_unit *bch2_pick_time_units(u64 ns) { const struct time_unit *u; for (u = time_units; u + 1 < time_units + ARRAY_SIZE(time_units) && ns >= u[1].nsecs << 1; u++) ; return u; } static void quantiles_update(struct quantiles *q, u64 v) { unsigned i = 0; while (i < ARRAY_SIZE(q->entries)) { struct quantile_entry *e = q->entries + i; if (unlikely(!e->step)) { e->m = v; e->step = max_t(unsigned, v / 2, 1024); } else if (e->m > v) { e->m = e->m >= e->step ? e->m - e->step : 0; } else if (e->m < v) { e->m = e->m + e->step > e->m ? e->m + e->step : U32_MAX; } if ((e->m > v ? e->m - v : v - e->m) < e->step) e->step = max_t(unsigned, e->step / 2, 1); if (v >= e->m) break; i = eytzinger0_child(i, v > e->m); } } static inline void time_stats_update_one(struct bch2_time_stats *stats, u64 start, u64 end) { u64 duration, freq; bool initted = stats->last_event != 0; if (time_after64(end, start)) { struct quantiles *quantiles = time_stats_to_quantiles(stats); duration = end - start; mean_and_variance_update(&stats->duration_stats, duration); mean_and_variance_weighted_update(&stats->duration_stats_weighted, duration, initted, TIME_STATS_MV_WEIGHT); stats->max_duration = max(stats->max_duration, duration); stats->min_duration = min(stats->min_duration, duration); stats->total_duration += duration; if (quantiles) quantiles_update(quantiles, duration); } if (stats->last_event && time_after64(end, stats->last_event)) { freq = end - stats->last_event; mean_and_variance_update(&stats->freq_stats, freq); mean_and_variance_weighted_update(&stats->freq_stats_weighted, freq, initted, TIME_STATS_MV_WEIGHT); stats->max_freq = max(stats->max_freq, freq); stats->min_freq = min(stats->min_freq, freq); } stats->last_event = end; } void __bch2_time_stats_clear_buffer(struct bch2_time_stats *stats, struct time_stat_buffer *b) { for (struct time_stat_buffer_entry *i = b->entries; i < b->entries + ARRAY_SIZE(b->entries); i++) time_stats_update_one(stats, i->start, i->end); b->nr = 0; } static noinline void time_stats_clear_buffer(struct bch2_time_stats *stats, struct time_stat_buffer *b) { unsigned long flags; spin_lock_irqsave(&stats->lock, flags); __bch2_time_stats_clear_buffer(stats, b); spin_unlock_irqrestore(&stats->lock, flags); } void __bch2_time_stats_update(struct bch2_time_stats *stats, u64 start, u64 end) { unsigned long flags; if (!stats->buffer) { spin_lock_irqsave(&stats->lock, flags); time_stats_update_one(stats, start, end); if (mean_and_variance_weighted_get_mean(stats->freq_stats_weighted, TIME_STATS_MV_WEIGHT) < 32 && stats->duration_stats.n > 1024) stats->buffer = alloc_percpu_gfp(struct time_stat_buffer, GFP_ATOMIC); spin_unlock_irqrestore(&stats->lock, flags); } else { struct time_stat_buffer *b; preempt_disable(); b = this_cpu_ptr(stats->buffer); BUG_ON(b->nr >= ARRAY_SIZE(b->entries)); b->entries[b->nr++] = (struct time_stat_buffer_entry) { .start = start, .end = end }; if (unlikely(b->nr == ARRAY_SIZE(b->entries))) time_stats_clear_buffer(stats, b); preempt_enable(); } } void bch2_time_stats_reset(struct bch2_time_stats *stats) { spin_lock_irq(&stats->lock); unsigned offset = offsetof(struct bch2_time_stats, min_duration); memset((void *) stats + offset, 0, sizeof(*stats) - offset); if (stats->buffer) { int cpu; for_each_possible_cpu(cpu) per_cpu_ptr(stats->buffer, cpu)->nr = 0; } spin_unlock_irq(&stats->lock); } void bch2_time_stats_exit(struct bch2_time_stats *stats) { free_percpu(stats->buffer); } void bch2_time_stats_init(struct bch2_time_stats *stats) { memset(stats, 0, sizeof(*stats)); stats->min_duration = U64_MAX; stats->min_freq = U64_MAX; spin_lock_init(&stats->lock); }