From 253f4294e36799a6a4d094b5ff2e4cd85a9fd39b Mon Sep 17 00:00:00 2001 From: Josef 'Jeff' Sipek Date: Thu, 5 Mar 2020 07:20:53 -0500 Subject: [PATCH] stats: Add support for exponential stats group-by fxn One can specify the quantization parameters in the config file as: :exponential::: Currently, only base 2 and base 10 are supported. For example: group_by = bytes_out:exponential:1:4:10 Which will quantize the bytes_out values into the buckets: (-inf, 10], (10,100], (100,1000], (1000, 10000], (10000, +inf). --- src/config/Makefile.am | 6 ++-- src/stats/Makefile.am | 6 ++-- src/stats/stats-settings.c | 54 ++++++++++++++++++++++++++++ src/stats/test-stats-metrics.c | 64 ++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/config/Makefile.am b/src/config/Makefile.am index 149d7e6025..ba39c146ac 100644 --- a/src/config/Makefile.am +++ b/src/config/Makefile.am @@ -23,14 +23,16 @@ AM_CPPFLAGS = \ config_LDADD = \ $(LIBDOVECOT) \ $(RAND_LIBS) \ - $(BINARY_LDFLAGS) + $(BINARY_LDFLAGS) \ + -lm config_DEPENDENCIES = $(LIBDOVECOT_DEPS) doveconf_LDADD = \ $(LIBDOVECOT) \ $(RAND_LIBS) \ - $(BINARY_LDFLAGS) + $(BINARY_LDFLAGS) \ + -lm doveconf_DEPENDENCIES = $(LIBDOVECOT_DEPS) diff --git a/src/stats/Makefile.am b/src/stats/Makefile.am index b036b7a72b..edc9c55ea6 100644 --- a/src/stats/Makefile.am +++ b/src/stats/Makefile.am @@ -17,7 +17,8 @@ stats_LDADD = \ $(noinst_LTLIBRARIES) \ $(LIBDOVECOT) \ $(DOVECOT_SSL_LIBS) \ - $(BINARY_LDFLAGS) + $(BINARY_LDFLAGS) \ + -lm stats_DEPENDENCIES = \ $(noinst_LTLIBRARIES) \ @@ -54,7 +55,8 @@ test_libs = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT) \ - $(BINARY_LDFLAGS) + $(BINARY_LDFLAGS) \ + -lm test_deps = \ $(noinst_LTLIBRARIES) \ diff --git a/src/stats/stats-settings.c b/src/stats/stats-settings.c index ed6464f524..3e8e045851 100644 --- a/src/stats/stats-settings.c +++ b/src/stats/stats-settings.c @@ -7,6 +7,10 @@ #include "stats-settings.h" #include "array.h" +/* */ +#include +/* */ + static bool stats_metric_settings_check(void *_set, pool_t pool, const char **error_r); static bool stats_exporter_settings_check(void *_set, pool_t pool, const char **error_r); static bool stats_settings_check(void *_set, pool_t pool, const char **error_r); @@ -318,6 +322,52 @@ static bool parse_metric_group_by_common(const char *func, return TRUE; } +static bool parse_metric_group_by_exp(pool_t pool, struct stats_metric_settings_group_by *group_by, + const char *const *params, const char **error_r) +{ + intmax_t min, max, base; + + if (!parse_metric_group_by_common("exponential", params, &min, &max, &base, error_r)) + return FALSE; + + if ((base != 2) && (base != 10)) { + *error_r = t_strdup_printf("group_by 'exponential' aggregate function " + "base must be one of: 2, 10 (base=%ju)", + base); + return FALSE; + } + + group_by->func = STATS_METRIC_GROUPBY_QUANTIZED; + + /* + * Allocate the bucket range array and fill it in + * + * The first bucket is special - it contains everything less than or + * equal to 'base^min'. The last bucket is also special - it + * contains everything greater than 'base^max'. + * + * The second bucket begins at 'base^min + 1', the third bucket + * begins at 'base^(min + 1) + 1', and so on. + */ + group_by->num_ranges = max - min + 2; + group_by->ranges = p_new(pool, struct stats_metric_settings_bucket_range, + group_by->num_ranges); + + /* set up min & max buckets */ + group_by->ranges[0].min = INTMAX_MIN; + group_by->ranges[0].max = pow(base, min); + group_by->ranges[group_by->num_ranges - 1].min = pow(base, max); + group_by->ranges[group_by->num_ranges - 1].max = INTMAX_MAX; + + /* remaining buckets */ + for (unsigned int i = 1; i < group_by->num_ranges - 1; i++) { + group_by->ranges[i].min = pow(base, min + (i - 1)); + group_by->ranges[i].max = pow(base, min + i); + } + + return TRUE; +} + static bool parse_metric_group_by_lin(pool_t pool, struct stats_metric_settings_group_by *group_by, const char *const *params, const char **error_r) { @@ -395,6 +445,10 @@ static bool parse_metric_group_by(struct stats_metric_settings *set, "does not take any args"; return FALSE; } + } else if (strcmp(params[1], "exponential") == 0) { + /* :exponential::: */ + if (!parse_metric_group_by_exp(pool, &group_by, ¶ms[2], error_r)) + return FALSE; } else if (strcmp(params[1], "linear") == 0) { /* :linear::: */ if (!parse_metric_group_by_lin(pool, &group_by, ¶ms[2], error_r)) diff --git a/src/stats/test-stats-metrics.c b/src/stats/test-stats-metrics.c index 183e81155a..380c49b229 100644 --- a/src/stats/test-stats-metrics.c +++ b/src/stats/test-stats-metrics.c @@ -268,6 +268,70 @@ static const struct quantized_test quantized_tests[] = { { { 1000, INTMAX_MAX }, 2 }, } }, + { + /* start at 0 */ + "exponential:0:6:10", + 12, + { 0, 5, 10, 11, 100, 101, 500, 1000, 1001, 1000000, 1000001, 2000000 }, + 7, + 8, + { { { INTMAX_MIN, 1 }, 1 }, + { { 1, 10 }, 2 }, + { { 10, 100 }, 2 }, + { { 100, 1000 }, 3 }, + { { 1000, 10000 }, 1 }, + { { 10000, 100000 }, 0 }, + { { 100000, 1000000 }, 1 }, + { { 1000000, INTMAX_MAX }, 2 }, + } + }, + { + /* start at 0 */ + "exponential:0:6:2", + 9, + { 0, 1, 2, 4, 5, 20, 64, 65, 100 }, + 7, + 8, + { { { INTMAX_MIN, 1 }, 2 }, + { { 1, 2 }, 1 }, + { { 2, 4 }, 1 }, + { { 4, 8 }, 1 }, + { { 8, 16 }, 0 }, + { { 16, 32 }, 1 }, + { { 32, 64 }, 1 }, + { { 64, INTMAX_MAX }, 2 }, + } + }, + { + /* start at >0 */ + "exponential:2:6:10", + 12, + { 0, 5, 10, 11, 100, 101, 500, 1000, 1001, 1000000, 1000001, 2000000 }, + 5, + 6, + { { { INTMAX_MIN, 100 }, 5 }, + { { 100, 1000 }, 3 }, + { { 1000, 10000 }, 1 }, + { { 10000, 100000 }, 0 }, + { { 100000, 1000000 }, 1 }, + { { 1000000, INTMAX_MAX }, 2 }, + } + }, + { + /* start at >0 */ + "exponential:2:6:2", + 9, + { 0, 1, 2, 4, 5, 20, 64, 65, 100 }, + 5, + 6, + { { { INTMAX_MIN, 4 }, 4 }, + { { 4, 8 }, 1 }, + { { 8, 16 }, 0 }, + { { 16, 32 }, 1 }, + { { 32, 64 }, 1 }, + { { 64, INTMAX_MAX }, 2 }, + } + }, }; static void test_stats_metrics_group_by_quantized_real(const struct quantized_test *test) -- 2.47.3