mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 05:32:00 +00:00
fdbc9c390a
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
180 lines
4.5 KiB
C
180 lines
4.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/jiffies.h>
|
|
#include <linux/module.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/preempt.h>
|
|
#include <linux/time.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#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);
|
|
}
|