This change introduces the histogram_print_buckets_rcu(). This API is used for implementing e.g. procfs or sysfs files exposing histograms. print_buckets can be combined with e.g. seq_file to provide a read interface for such a file. This is a squashed, refactored, and modified version of a previously-internal implementation. Thanks to the following individuals for portions of the implementation: Junaid Shahid <junaids@xxxxxxxxxx> - Original implementation Yu Zhao <yuzhao@xxxxxxxxxx> - Simplification Signed-off-by: Axel Rasmussen <axelrasmussen@xxxxxxxxxx> --- include/linux/histogram.h | 23 ++++++++++++++++ lib/histogram.c | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/include/linux/histogram.h b/include/linux/histogram.h index 137930ca933f..7a1dc33fff5e 100644 --- a/include/linux/histogram.h +++ b/include/linux/histogram.h @@ -65,6 +65,12 @@ * histogram_destroy_rcu(&hrcu); */ +/* + * Max number of digits in decimal needed to represent U64_MAX threshold, + * plus one space. + */ +#define HISTO_MAX_CHARS_PER_THRESHOLD (20 + 1) + struct histogram { struct rcu_head rcu; u64 __percpu *buckets; @@ -189,6 +195,23 @@ static inline void histogram_record_rcu(struct histogram_rcu *hrcu, u64 val, ssize_t histogram_read_rcu(const struct histogram_rcu *hrcu, u64 *buckets, u64 *thresholds, size_t nr_thresholds); +/** + * histogram_print_buckets_rcu() - helper function to print histogram buckets + * @hrcu: histogram + * @buffer: output buffer to fill + * @len: length of the output buffer + * + * Reads buckets by calling histogram_read_rcu(), then fills the output buffer. + * + * Context: Performs allocation with GFP_ATOMIC. + * + * Returns: The number of characters written to @buffer, or a negative error + * code on failure. If the buffer isn't large enough to contain the output, + * -EINVAL is returned. + */ +int histogram_print_buckets_rcu(struct histogram_rcu *hrcu, char *buffer, + int len); + /** * histogram_set_thresholds_rcu() - set RCU-protected histogram thresholds * @hrcu: histogram diff --git a/lib/histogram.c b/lib/histogram.c index b68334275a46..1a23590fc6e9 100644 --- a/lib/histogram.c +++ b/lib/histogram.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/cache.h> +#include <linux/ctype.h> #include <linux/cpumask.h> #include <linux/err.h> #include <linux/export.h> @@ -133,6 +134,60 @@ ssize_t histogram_read_rcu(const struct histogram_rcu *hrcu, u64 *buckets, } EXPORT_SYMBOL_GPL(histogram_read_rcu); +int histogram_print_buckets_rcu(struct histogram_rcu *hrcu, char *buffer, + int len) +{ + const struct histogram *hist; + u64 *buckets = NULL; + u64 lower = 0; + size_t nr_buckets; + size_t i; + int ret; + int remaining = len; + + rcu_read_lock_sched(); + hist = rcu_dereference_sched(hrcu->hist); + + nr_buckets = hist->nr_thresholds; + if (!nr_buckets) { + ret = -EINVAL; + goto out; + } + + buckets = kmalloc_array(nr_buckets, sizeof(*hist->buckets), GFP_ATOMIC); + if (!buckets) { + ret = -ENOMEM; + goto out; + } + + histogram_read_buckets(hist, buckets); + + for (i = 0; i < nr_buckets; i++) { + if (i == nr_buckets - 1) + ret = snprintf(buffer, remaining, "%llu-inf %llu\n", + lower, buckets[i]); + else + ret = snprintf(buffer, remaining, "%llu-%llu %llu\n", + lower, hist->thresholds[i], buckets[i]); + if (ret >= remaining) { + ret = -EINVAL; + goto out; + } + + buffer += ret; + remaining -= ret; + + lower = hist->thresholds[i] + 1; + } + + ret = len - remaining; +out: + rcu_read_unlock_sched(); + kfree(buckets); + return ret; +} +EXPORT_SYMBOL_GPL(histogram_print_buckets_rcu); + int histogram_set_thresholds_rcu(struct histogram_rcu *hrcu, const u64 *thresholds, size_t nr_thresholds) { -- 2.27.0.rc0.183.gde8f92d652-goog