summaryrefslogtreecommitdiff
path: root/libbcachefs/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'libbcachefs/util.c')
-rw-r--r--libbcachefs/util.c201
1 files changed, 169 insertions, 32 deletions
diff --git a/libbcachefs/util.c b/libbcachefs/util.c
index fa853750..1f2c23b9 100644
--- a/libbcachefs/util.c
+++ b/libbcachefs/util.c
@@ -13,12 +13,15 @@
#include <linux/kthread.h>
#include <linux/log2.h>
#include <linux/math64.h>
+#include <linux/percpu.h>
+#include <linux/preempt.h>
#include <linux/random.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/sched/clock.h>
+#include "eytzinger.h"
#include "util.h"
#define simple_strtoint(c, end, base) simple_strtol(c, end, base)
@@ -200,59 +203,189 @@ bool bch2_is_zero(const void *_p, size_t n)
return true;
}
-void bch2_time_stats_clear(struct time_stats *stats)
+void bch2_quantiles_update(struct quantiles *q, u64 v)
{
- spin_lock(&stats->lock);
+ 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);
- stats->count = 0;
- stats->last_duration = 0;
- stats->max_duration = 0;
- stats->average_duration = 0;
- stats->average_frequency = 0;
- stats->last = 0;
+ if (v >= e->m)
+ break;
- spin_unlock(&stats->lock);
+ i = eytzinger0_child(i, v > e->m);
+ }
}
-void __bch2_time_stats_update(struct time_stats *stats, u64 start_time)
+/* time stats: */
+
+static void bch2_time_stats_update_one(struct time_stats *stats,
+ u64 start, u64 end)
{
- u64 now, duration, last;
+ u64 duration, freq;
+
+ duration = time_after64(end, start)
+ ? end - start : 0;
+ freq = time_after64(end, stats->last_event)
+ ? end - stats->last_event : 0;
stats->count++;
- now = local_clock();
- duration = time_after64(now, start_time)
- ? now - start_time : 0;
- last = time_after64(now, stats->last)
- ? now - stats->last : 0;
+ stats->average_duration = stats->average_duration
+ ? ewma_add(stats->average_duration, duration, 6)
+ : duration;
+
+ stats->average_frequency = stats->average_frequency
+ ? ewma_add(stats->average_frequency, freq, 6)
+ : freq;
- stats->last_duration = duration;
stats->max_duration = max(stats->max_duration, duration);
- if (stats->last) {
- stats->average_duration = ewma_add(stats->average_duration,
- duration << 8, 3);
+ stats->last_event = end;
- if (stats->average_frequency)
- stats->average_frequency =
- ewma_add(stats->average_frequency,
- last << 8, 3);
- else
- stats->average_frequency = last << 8;
+ bch2_quantiles_update(&stats->quantiles, duration);
+}
+
+void __bch2_time_stats_update(struct time_stats *stats, u64 start, u64 end)
+{
+ unsigned long flags;
+
+ if (!stats->buffer) {
+ spin_lock_irqsave(&stats->lock, flags);
+ bch2_time_stats_update_one(stats, start, end);
+
+ if (stats->average_frequency < 32 &&
+ stats->count > 1024)
+ stats->buffer =
+ alloc_percpu_gfp(struct time_stat_buffer,
+ GFP_ATOMIC);
+ spin_unlock_irqrestore(&stats->lock, flags);
} else {
- stats->average_duration = duration << 8;
+ struct time_stat_buffer_entry *i;
+ 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 (b->nr == ARRAY_SIZE(b->entries)) {
+ spin_lock_irqsave(&stats->lock, flags);
+ for (i = b->entries;
+ i < b->entries + ARRAY_SIZE(b->entries);
+ i++)
+ bch2_time_stats_update_one(stats, i->start, i->end);
+ spin_unlock_irqrestore(&stats->lock, flags);
+
+ b->nr = 0;
+ }
+
+ preempt_enable();
+ }
+}
+
+static const struct time_unit {
+ const char *name;
+ u32 nsecs;
+} time_units[] = {
+ { "ns", 1 },
+ { "us", NSEC_PER_USEC },
+ { "ms", NSEC_PER_MSEC },
+ { "sec", NSEC_PER_SEC },
+};
+
+static const struct time_unit *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 size_t pr_time_units(char *buf, size_t len, u64 ns)
+{
+ const struct time_unit *u = pick_time_units(ns);
+
+ return scnprintf(buf, len, "%llu %s", div_u64(ns, u->nsecs), u->name);
+}
+
+size_t bch2_time_stats_print(struct time_stats *stats, char *buf, size_t len)
+{
+ char *out = buf, *end = buf + len;
+ const struct time_unit *u;
+ u64 freq = READ_ONCE(stats->average_frequency);
+ u64 q, last_q = 0;
+ int i;
+
+ out += scnprintf(out, end - out, "count:\t\t%llu\n",
+ stats->count);
+ out += scnprintf(out, end - out, "rate:\t\t%llu/sec\n",
+ freq ? div64_u64(NSEC_PER_SEC, freq) : 0);
+
+ out += scnprintf(out, end - out, "frequency:\t");
+ out += pr_time_units(out, end - out, freq);
+
+ out += scnprintf(out, end - out, "\navg duration:\t");
+ out += pr_time_units(out, end - out, stats->average_duration);
+
+ out += scnprintf(out, end - out, "\nmax duration:\t");
+ out += pr_time_units(out, end - out, stats->max_duration);
+
+ i = eytzinger0_first(NR_QUANTILES);
+ u = pick_time_units(stats->quantiles.entries[i].m);
+
+ out += scnprintf(out, end - out, "\nquantiles (%s):\t", u->name);
+ eytzinger0_for_each(i, NR_QUANTILES) {
+ bool is_last = eytzinger0_next(i, NR_QUANTILES) == -1;
+
+ q = max(stats->quantiles.entries[i].m, last_q);
+ out += scnprintf(out, end - out, "%llu%s",
+ div_u64(q, u->nsecs),
+ is_last ? "\n" : " ");
+ last_q = q;
}
- stats->last = now ?: 1;
+ return out - buf;
}
-void bch2_time_stats_update(struct time_stats *stats, u64 start_time)
+void bch2_time_stats_exit(struct time_stats *stats)
{
- spin_lock(&stats->lock);
- __bch2_time_stats_update(stats, start_time);
- spin_unlock(&stats->lock);
+ free_percpu(stats->buffer);
}
+void bch2_time_stats_init(struct time_stats *stats)
+{
+ memset(stats, 0, sizeof(*stats));
+ spin_lock_init(&stats->lock);
+}
+
+/* ratelimit: */
+
/**
* bch2_ratelimit_delay() - return how long to delay until the next time to do
* some work
@@ -310,6 +443,8 @@ int bch2_ratelimit_wait_freezable_stoppable(struct bch_ratelimit *d)
}
}
+/* pd controller: */
+
/*
* Updates pd_controller. Attempts to scale inputed values to units per second.
* @target: desired value
@@ -404,6 +539,8 @@ size_t bch2_pd_controller_print_debug(struct bch_pd_controller *pd, char *buf)
derivative, change, next_io);
}
+/* misc: */
+
void bch2_bio_map(struct bio *bio, void *base)
{
size_t size = bio->bi_iter.bi_size;