Commit b42c45d8 authored by Shuhei Matsumoto's avatar Shuhei Matsumoto Committed by Tomasz Zawadzki
Browse files

histogram_data: add API to specify min and max values



In default histogram settings low and high ranges
are most likely not used. However, they still require plenty of
memory. Patch intoduce extended API to histogram creation that
allows to specify min and max values. That cuts memory usage
by sacrificing accuracy in unnecessary time ranges.

Change-Id: I1bd142af119542b915bf2a7d0959e076425ec1d3
Signed-off-by: default avatarVasilii Ivanov <iwanovvvasilij@gmail.com>
Reviewed-on: https://review.spdk.io/c/spdk/spdk/+/25383


Reviewed-by: default avatarJim Harris <jim.harris@nvidia.com>
Tested-by: default avatarSPDK Automated Test System <spdkbot@gmail.com>
Reviewed-by: default avatarShuhei Matsumoto <smatsumoto@nvidia.com>
parent b367de3d
Loading
Loading
Loading
Loading
+53 −11
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ extern "C" {
#define SPDK_HISTOGRAM_BUCKET_LSB(h)		(64 - SPDK_HISTOGRAM_GRANULARITY(h))
#define SPDK_HISTOGRAM_NUM_BUCKETS_PER_RANGE(h)	(1ULL << SPDK_HISTOGRAM_GRANULARITY(h))
#define SPDK_HISTOGRAM_BUCKET_MASK(h)		(SPDK_HISTOGRAM_NUM_BUCKETS_PER_RANGE(h) - 1)
#define SPDK_HISTOGRAM_NUM_BUCKET_RANGES(h)	(SPDK_HISTOGRAM_BUCKET_LSB(h) + 1)
#define SPDK_HISTOGRAM_NUM_BUCKET_RANGES(h)	(h->max_range - h->min_range + 1)
#define SPDK_HISTOGRAM_NUM_BUCKETS(h)		(SPDK_HISTOGRAM_NUM_BUCKETS_PER_RANGE(h) * \
						 SPDK_HISTOGRAM_NUM_BUCKET_RANGES(h))

@@ -54,13 +54,16 @@ extern "C" {
 *
 * Buckets can be made more granular by increasing SPDK_HISTOGRAM_GRANULARITY.  This
 * comes at the cost of additional storage per namespace context to store the bucket data.
 * In order to lower number of ranges to shrink unnecessary low and high datapoints
 * min_val and max_val can be specified with spdk_histogram_data_alloc_sized_ext().
 * It will limit the values in histogram to a range [min_val, max_val).
 */

struct spdk_histogram_data {

	uint32_t	granularity;
	uint32_t	min_range;
	uint32_t	max_range;
	uint64_t	*bucket;

};

static inline void
@@ -68,20 +71,20 @@ __spdk_histogram_increment(struct spdk_histogram_data *h, uint32_t range, uint32
{
	uint64_t *count;

	count = &h->bucket[(range << SPDK_HISTOGRAM_GRANULARITY(h)) + index];
	count = &h->bucket[((range - h->min_range) << SPDK_HISTOGRAM_GRANULARITY(h)) + index];
	(*count)++;
}

static inline uint64_t
__spdk_histogram_get_count(const struct spdk_histogram_data *h, uint32_t range, uint32_t index)
{
	return h->bucket[(range << SPDK_HISTOGRAM_GRANULARITY(h)) + index];
	return h->bucket[((range - h->min_range) << SPDK_HISTOGRAM_GRANULARITY(h)) + index];
}

static inline uint64_t *
__spdk_histogram_get_bucket(const struct spdk_histogram_data *h, uint32_t range, uint32_t index)
{
	return &h->bucket[(range << SPDK_HISTOGRAM_GRANULARITY(h)) + index];
	return &h->bucket[((range - h->min_range) << SPDK_HISTOGRAM_GRANULARITY(h)) + index];
}

static inline void
@@ -124,8 +127,19 @@ __spdk_histogram_data_get_bucket_index(struct spdk_histogram_data *h, uint64_t d
static inline void
spdk_histogram_data_tally(struct spdk_histogram_data *histogram, uint64_t datapoint)
{
	uint32_t range = __spdk_histogram_data_get_bucket_range(histogram, datapoint);
	uint32_t index = __spdk_histogram_data_get_bucket_index(histogram, datapoint, range);
	uint32_t range, index;

	range = __spdk_histogram_data_get_bucket_range(histogram, datapoint);

	if (range < histogram->min_range) {
		range = histogram->min_range;
		index = 0;
	} else if (range > histogram->max_range) {
		range = histogram->max_range;
		index = SPDK_HISTOGRAM_NUM_BUCKETS_PER_RANGE(histogram) - 1;
	} else {
		index = __spdk_histogram_data_get_bucket_index(histogram, datapoint, range);
	}

	__spdk_histogram_increment(histogram, range, index);
}
@@ -159,7 +173,7 @@ spdk_histogram_data_iterate(const struct spdk_histogram_data *histogram,

	total = 0;

	for (i = 0; i < SPDK_HISTOGRAM_NUM_BUCKET_RANGES(histogram); i++) {
	for (i = histogram->min_range; i <= histogram->max_range; i++) {
		for (j = 0; j < SPDK_HISTOGRAM_NUM_BUCKETS_PER_RANGE(histogram); j++) {
			total += __spdk_histogram_get_count(histogram, i, j);
		}
@@ -168,7 +182,7 @@ spdk_histogram_data_iterate(const struct spdk_histogram_data *histogram,
	so_far = 0;
	bucket = 0;

	for (i = 0; i < SPDK_HISTOGRAM_NUM_BUCKET_RANGES(histogram); i++) {
	for (i = histogram->min_range; i <= histogram->max_range; i++) {
		for (j = 0; j < SPDK_HISTOGRAM_NUM_BUCKETS_PER_RANGE(histogram); j++) {
			count = __spdk_histogram_get_count(histogram, i, j);
			so_far += count;
@@ -193,6 +207,11 @@ spdk_histogram_data_merge(const struct spdk_histogram_data *dst,
		return -EINVAL;
	}

	/* Histogram with different size cannot be simply merged. */
	if (dst->min_range != src->min_range || dst->max_range != src->max_range) {
		return -EINVAL;
	}

	for (i = 0; i < SPDK_HISTOGRAM_NUM_BUCKETS(dst); i++) {
		dst->bucket[i] += src->bucket[i];
	}
@@ -200,17 +219,34 @@ spdk_histogram_data_merge(const struct spdk_histogram_data *dst,
	return 0;
}

/**
 * Allocate a histogram data structure with specified granularity. It tracks datapoints
 * from min_val (inclusive) to max_val (exclusive).
 *
 * \param granularity Granularity of the histogram buckets. Each power-of-2 range is
 *                    split into (1 << granularity) buckets.
 * \param min_val The minimum value to be tracked, inclusive.
 * \param max_val The maximum value to be tracked, exclusive.
 *
 * \return A histogram data structure.
 */
static inline struct spdk_histogram_data *
spdk_histogram_data_alloc_sized(uint32_t granularity)
spdk_histogram_data_alloc_sized_ext(uint32_t granularity, uint64_t min_val, uint64_t max_val)
{
	struct spdk_histogram_data *h;

	if (min_val >= max_val) {
		return NULL;
	}

	h = (struct spdk_histogram_data *)calloc(1, sizeof(*h));
	if (h == NULL) {
		return NULL;
	}

	h->granularity = granularity;
	h->min_range = __spdk_histogram_data_get_bucket_range(h, min_val);
	h->max_range = __spdk_histogram_data_get_bucket_range(h, max_val - 1);
	h->bucket = (uint64_t *)calloc(SPDK_HISTOGRAM_NUM_BUCKETS(h), sizeof(uint64_t));
	if (h->bucket == NULL) {
		free(h);
@@ -220,6 +256,12 @@ spdk_histogram_data_alloc_sized(uint32_t granularity)
	return h;
}

static inline struct spdk_histogram_data *
spdk_histogram_data_alloc_sized(uint32_t granularity)
{
	return spdk_histogram_data_alloc_sized_ext(granularity, 0, UINT64_MAX);
}

static inline struct spdk_histogram_data *
spdk_histogram_data_alloc(void)
{
+77 −1
Original line number Diff line number Diff line
@@ -112,6 +112,81 @@ histogram_merge(void)
	spdk_histogram_data_free(h2);
}

struct value_with_count {
	uint64_t value;
	uint64_t count;
};

static void
check_values_with_count(void *ctx, uint64_t start, uint64_t end, uint64_t count,
			uint64_t total, uint64_t so_far)
{
	struct value_with_count **values = ctx;

	if (count == 0) {
		return;
	}

	CU_ASSERT((**values).count == count);

	/*
	 * The bucket for this iteration does not include end, but
	 *  subtract one anyways to account for the last bucket
	 *  which will have end = 0x0 (UINT64_MAX + 1).
	 */
	end--;

	CU_ASSERT((**values).value >= start);
	/*
	 * We subtracted one from end above, so it's OK here for
	 *  value to equal end.
	 */
	CU_ASSERT((*values)->value <= end);
	(*values)++;
}

#define TEST_TALLY_COUNT 3
#define TEST_MIN_VAL (1ULL << 9)
#define TEST_MAX_VAL (1ULL << 30)
#define TEST_BELOW_MIN_VAL (TEST_MIN_VAL >> 1)
#define TEST_IN_MIDDLE_VAL ((TEST_MIN_VAL + TEST_MAX_VAL) >> 2)
#define TEST_ABOVE_MAX_VAL (TEST_MAX_VAL << 1)

struct value_with_count g_value_with_count[] = {
	{.value = TEST_MIN_VAL, .count = 2 * TEST_TALLY_COUNT},
	{.value = TEST_IN_MIDDLE_VAL, .count = TEST_TALLY_COUNT},
	{.value = TEST_MAX_VAL - 1, .count = 2 * TEST_TALLY_COUNT},
};

static void
histogram_min_max_range_test(void)
{
	struct spdk_histogram_data *h1, *h2;
	struct value_with_count *values = g_value_with_count;
	int i;

	h1 = spdk_histogram_data_alloc();

	CU_ASSERT(h1->min_range == 0);
	CU_ASSERT(h1->max_range == SPDK_HISTOGRAM_BUCKET_LSB(h1));

	h2 = spdk_histogram_data_alloc_sized_ext(SPDK_HISTOGRAM_GRANULARITY_DEFAULT, TEST_MIN_VAL,
			TEST_MAX_VAL);

	for (i = 0; i < TEST_TALLY_COUNT; i++) {
		spdk_histogram_data_tally(h2, TEST_BELOW_MIN_VAL);
		spdk_histogram_data_tally(h2, TEST_MIN_VAL);
		spdk_histogram_data_tally(h2, TEST_IN_MIDDLE_VAL);
		spdk_histogram_data_tally(h2, TEST_MAX_VAL);
		spdk_histogram_data_tally(h2, TEST_ABOVE_MAX_VAL);
	}

	spdk_histogram_data_iterate(h2, check_values_with_count, &values);

	spdk_histogram_data_free(h1);
	spdk_histogram_data_free(h2);
}

int
main(int argc, char **argv)
{
@@ -130,7 +205,8 @@ main(int argc, char **argv)

	if (
		CU_add_test(suite, "histogram_test", histogram_test) == NULL ||
		CU_add_test(suite, "histogram_merge", histogram_merge) == NULL
		CU_add_test(suite, "histogram_merge", histogram_merge) == NULL ||
		CU_add_test(suite, "histogram_min_max_range_test", histogram_min_max_range_test) == NULL
	) {
		CU_cleanup_registry();
		return CU_get_error();