diff --git a/CMakeLists.txt b/CMakeLists.txt index 0914e8480a..80e759760a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -393,6 +393,8 @@ set(API_PLUGIN_FILES web/api/queries/stddev/stddev.h web/api/queries/ses/ses.c web/api/queries/ses/ses.h + web/api/queries/des/des.c + web/api/queries/des/des.h ) set(STREAMING_PLUGIN_FILES diff --git a/Makefile.am b/Makefile.am index cbe05c5da8..45be88f5ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -291,6 +291,8 @@ API_PLUGIN_FILES = \ web/api/exporters/shell/allmetrics_shell.h \ web/api/queries/average/average.c \ web/api/queries/average/average.h \ + web/api/queries/des/des.c \ + web/api/queries/des/des.h \ web/api/queries/incremental_sum/incremental_sum.c \ web/api/queries/incremental_sum/incremental_sum.h \ web/api/queries/max/max.c \ diff --git a/configure.ac b/configure.ac index e0313dcd91..474e5b7295 100644 --- a/configure.ac +++ b/configure.ac @@ -609,6 +609,7 @@ AC_CONFIG_FILES([ web/api/exporters/prometheus/Makefile web/api/queries/Makefile web/api/queries/average/Makefile + web/api/queries/des/Makefile web/api/queries/incremental_sum/Makefile web/api/queries/max/Makefile web/api/queries/median/Makefile diff --git a/cppcheck.sh b/cppcheck.sh index aba93c269b..ebbeeaf8f9 100755 --- a/cppcheck.sh +++ b/cppcheck.sh @@ -12,14 +12,14 @@ processors=$(grep -c ^processor /proc/cpuinfo) base="$(dirname "${0}")" [ "${base}" = "." ] && base="${PWD}" -cd "${base}/src" || exit 1 +cd "${base}" || exit 1 [ ! -d "cppcheck-build" ] && mkdir "cppcheck-build" file="${1}" shift # shellcheck disable=SC2235 -([ "${file}" = "${base}" ] || [ -z "${file}" ]) && file="${base}/src" +([ "${file}" = "${base}" ] || [ -z "${file}" ]) && file="${base}" "${cppcheck}" \ -j ${processors} \ diff --git a/web/api/queries/des/Makefile.am b/web/api/queries/des/Makefile.am new file mode 100644 index 0000000000..19554bed8e --- /dev/null +++ b/web/api/queries/des/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/web/api/queries/des/README.md b/web/api/queries/des/README.md new file mode 100644 index 0000000000..d2765ecc03 --- /dev/null +++ b/web/api/queries/des/README.md @@ -0,0 +1 @@ +# double exponential smoothing diff --git a/web/api/queries/des/des.c b/web/api/queries/des/des.c new file mode 100644 index 0000000000..bc9f6d81bb --- /dev/null +++ b/web/api/queries/des/des.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "des.h" + + +// ---------------------------------------------------------------------------- +// single exponential smoothing + +struct grouping_des { + calculated_number alpha; + calculated_number alpha_other; + calculated_number beta; + calculated_number beta_other; + + calculated_number level; + calculated_number trend; + + size_t count; +}; + +#define MAX_WINDOW_SIZE 10 + +static inline void set_alpha(RRDR *r, struct grouping_des *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + calculated_number window = (r->group > MAX_WINDOW_SIZE) ? MAX_WINDOW_SIZE : r->group; + + g->alpha = 2.0 / ((calculated_number)window + 1.0); + g->alpha_other = 1.0 - g->alpha; + + //info("alpha for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->alpha); +} + +static inline void set_beta(RRDR *r, struct grouping_des *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + calculated_number window = (r->group > MAX_WINDOW_SIZE) ? MAX_WINDOW_SIZE : r->group; + + g->beta = 2.0 / ((calculated_number)window + 1.0); + g->beta_other = 1.0 - g->beta; + + //info("beta for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->beta); +} + +void *grouping_init_des(RRDR *r) { + struct grouping_des *g = (struct grouping_des *)malloc(sizeof(struct grouping_des)); + set_alpha(r, g); + set_beta(r, g); + g->level = 0.0; + g->trend = 0.0; + g->count = 0; + return g; +} + +// resets when switches dimensions +// so, clear everything to restart +void grouping_reset_des(RRDR *r) { + struct grouping_des *g = (struct grouping_des *)r->grouping_data; + g->level = 0.0; + g->trend = 0.0; + g->count = 0; + + // fprintf(stderr, "\nDES: "); + +} + +void grouping_free_des(RRDR *r) { + freez(r->grouping_data); + r->grouping_data = NULL; +} + +void grouping_add_des(RRDR *r, calculated_number value) { + struct grouping_des *g = (struct grouping_des *)r->grouping_data; + + if(isnormal(value)) { + if(likely(g->count > 0)) { + // we have at least a number so far + + if(unlikely(g->count == 1)) { + // the second value we got + g->trend = value - g->trend; + g->level = value; + } + + // for the values, except the first + calculated_number last_level = g->level; + g->level = (g->alpha * value) + (g->alpha_other * (g->level + g->trend)); + g->trend = (g->beta * (g->level - last_level)) + (g->beta_other * g->trend); + } + else { + // the first value we got + g->level = g->trend = value; + } + + g->count++; + } + + //fprintf(stderr, "value: " CALCULATED_NUMBER_FORMAT ", level: " CALCULATED_NUMBER_FORMAT ", trend: " CALCULATED_NUMBER_FORMAT "\n", value, g->level, g->trend); +} + +calculated_number grouping_flush_des(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_des *g = (struct grouping_des *)r->grouping_data; + + if(unlikely(!g->count || !isnormal(g->level))) { + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + return 0.0; + } + + //fprintf(stderr, " RESULT for %zu values = " CALCULATED_NUMBER_FORMAT " \n", g->count, g->level); + + return g->level; +} diff --git a/web/api/queries/des/des.h b/web/api/queries/des/des.h new file mode 100644 index 0000000000..83edd7ef63 --- /dev/null +++ b/web/api/queries/des/des.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_API_QUERIES_DES_H +#define NETDATA_API_QUERIES_DES_H + +#include "../query.h" +#include "../rrdr.h" + +extern void *grouping_init_des(RRDR *r); +extern void grouping_reset_des(RRDR *r); +extern void grouping_free_des(RRDR *r); +extern void grouping_add_des(RRDR *r, calculated_number value); +extern calculated_number grouping_flush_des(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + +#endif //NETDATA_API_QUERIES_DES_H diff --git a/web/api/queries/query.c b/web/api/queries/query.c index 85a06993c9..62d9084b14 100644 --- a/web/api/queries/query.c +++ b/web/api/queries/query.c @@ -12,6 +12,7 @@ #include "sum/sum.h" #include "stddev/stddev.h" #include "ses/ses.h" +#include "des/des.h" // ---------------------------------------------------------------------------- @@ -31,9 +32,23 @@ static struct { , { "median" , 0, RRDR_GROUPING_MEDIAN , grouping_init_median , grouping_reset_median , grouping_free_median , grouping_add_median , grouping_flush_median } , { "min" , 0, RRDR_GROUPING_MIN , grouping_init_min , grouping_reset_min , grouping_free_min , grouping_add_min , grouping_flush_min } , { "max" , 0, RRDR_GROUPING_MAX , grouping_init_max , grouping_reset_max , grouping_free_max , grouping_add_max , grouping_flush_max } - , { "ses" , 0, RRDR_GROUPING_SES , grouping_init_ses , grouping_reset_ses , grouping_free_ses , grouping_add_ses , grouping_flush_ses } - , { "stddev" , 0, RRDR_GROUPING_STDDEV , grouping_init_stddev , grouping_reset_stddev , grouping_free_stddev , grouping_add_stddev , grouping_flush_stddev } , { "sum" , 0, RRDR_GROUPING_SUM , grouping_init_sum , grouping_reset_sum , grouping_free_sum , grouping_add_sum , grouping_flush_sum } + + // stddev module provides mean, variance and coefficient of variation + , { "stddev" , 0, RRDR_GROUPING_STDDEV , grouping_init_stddev , grouping_reset_stddev , grouping_free_stddev , grouping_add_stddev , grouping_flush_stddev } + , { "cv" , 0, RRDR_GROUPING_CV , grouping_init_stddev , grouping_reset_stddev , grouping_free_stddev , grouping_add_stddev , grouping_flush_coefficient_of_variation } + //, { "mean" , 0, RRDR_GROUPING_MEAN , grouping_init_stddev , grouping_reset_stddev , grouping_free_stddev , grouping_add_stddev , grouping_flush_mean } + //, { "variance" , 0, RRDR_GROUPING_VARIANCE , grouping_init_stddev , grouping_reset_stddev , grouping_free_stddev , grouping_add_stddev , grouping_flush_variance } + + // single exponential smoothing or exponential weighted moving average + , { "ses" , 0, RRDR_GROUPING_SES , grouping_init_ses , grouping_reset_ses , grouping_free_ses , grouping_add_ses , grouping_flush_ses } + , { "ema" , 0, RRDR_GROUPING_SES , grouping_init_ses , grouping_reset_ses , grouping_free_ses , grouping_add_ses , grouping_flush_ses } + , { "ewma" , 0, RRDR_GROUPING_SES , grouping_init_ses , grouping_reset_ses , grouping_free_ses , grouping_add_ses , grouping_flush_ses } + + // double exponential smoothing + , { "des" , 0, RRDR_GROUPING_DES , grouping_init_des , grouping_reset_des , grouping_free_des , grouping_add_des , grouping_flush_des } + + // terminator , { NULL , 0, RRDR_GROUPING_UNDEFINED , grouping_init_average , grouping_reset_average , grouping_free_average , grouping_add_average , grouping_flush_average } }; @@ -431,7 +446,7 @@ RRDR *rrd2rrdr( info("INTERNAL CHECK: %s: requested gtime %ld secs, is greater than the desired duration %ld secs", st->id, group_time_requested, duration); #endif - group = points_requested; // use all the points + group = available_points; // use all the points } else { // the points we should group to satisfy gtime diff --git a/web/api/queries/query.h b/web/api/queries/query.h index c4cbe347c6..6b8a51c583 100644 --- a/web/api/queries/query.h +++ b/web/api/queries/query.h @@ -4,15 +4,17 @@ #define NETDATA_API_DATA_QUERY_H typedef enum rrdr_grouping { - RRDR_GROUPING_UNDEFINED = 0, - RRDR_GROUPING_AVERAGE = 1, - RRDR_GROUPING_MIN = 2, - RRDR_GROUPING_MAX = 3, - RRDR_GROUPING_SUM = 4, - RRDR_GROUPING_INCREMENTAL_SUM = 5, - RRDR_GROUPING_MEDIAN = 6, - RRDR_GROUPING_STDDEV = 7, - RRDR_GROUPING_SES = 8, + RRDR_GROUPING_UNDEFINED = 0, + RRDR_GROUPING_AVERAGE, + RRDR_GROUPING_MIN, + RRDR_GROUPING_MAX, + RRDR_GROUPING_SUM, + RRDR_GROUPING_INCREMENTAL_SUM, + RRDR_GROUPING_MEDIAN, + RRDR_GROUPING_STDDEV, + RRDR_GROUPING_CV, + RRDR_GROUPING_SES, + RRDR_GROUPING_DES, } RRDR_GROUPING; extern const char *group_method2string(RRDR_GROUPING group); diff --git a/web/api/queries/ses/README.md b/web/api/queries/ses/README.md index 740b18e443..3e8be70c98 100644 --- a/web/api/queries/ses/README.md +++ b/web/api/queries/ses/README.md @@ -1 +1,45 @@ -# single exponential smoothing +# Single (or Simple) Exponential Smoothing (`ses`) + +> This query is also available as `ema` and `ewma`. + +An exponential moving average (`ema`), also known as an exponentially weighted moving average (`ewma`) +is a first-order infinite impulse response filter that applies weighting factors which decrease +exponentially. The weighting for each older datum decreases exponentially, never reaching zero. + +In simple terms, this is like an average value, but more recent values are given more weight. + +Netdata automatically adjusts the weight based on the number of values processed, using the formula: + +``` +alpha = 2 / (number_of_values + 1) +``` + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: ses -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`ses` does not change the units. For example, if the chart units is `requests/sec`, the exponential +moving average will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=ses` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +-  +-  +-  +-  + +## References + +- [https://en.wikipedia.org/wiki/Moving_average#exponential-moving-average](https://en.wikipedia.org/wiki/Moving_average#exponential-moving-average) +- [https://en.wikipedia.org/wiki/Exponential_smoothing](https://en.wikipedia.org/wiki/Exponential_smoothing). diff --git a/web/api/queries/ses/ses.c b/web/api/queries/ses/ses.c index 7a46e1b55c..04df738f0d 100644 --- a/web/api/queries/ses/ses.c +++ b/web/api/queries/ses/ses.c @@ -8,15 +8,16 @@ struct grouping_ses { calculated_number alpha; - calculated_number alpha_older; + calculated_number alpha_other; calculated_number level; size_t count; - size_t has_data; }; static inline void set_alpha(RRDR *r, struct grouping_ses *g) { - g->alpha = 1.0 / r->group; - g->alpha_older = 1 - g->alpha; + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + g->alpha = 2.0 / ((calculated_number)r->group + 1.0); + g->alpha_other = 1 - g->alpha; } void *grouping_init_ses(RRDR *r) { @@ -32,7 +33,6 @@ void grouping_reset_ses(RRDR *r) { struct grouping_ses *g = (struct grouping_ses *)r->grouping_data; g->level = 0.0; g->count = 0; - g->has_data = 0; } void grouping_free_ses(RRDR *r) { @@ -44,13 +44,10 @@ void grouping_add_ses(RRDR *r, calculated_number value) { struct grouping_ses *g = (struct grouping_ses *)r->grouping_data; if(isnormal(value)) { - if(unlikely(!g->has_data)) { + if(unlikely(!g->count)) g->level = value; - g->has_data = 1; - } - - g->level = g->alpha * value + g->alpha_older * g->level; + g->level = g->alpha * value + g->alpha_other * g->level; g->count++; } } @@ -58,17 +55,10 @@ void grouping_add_ses(RRDR *r, calculated_number value) { calculated_number grouping_flush_ses(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { struct grouping_ses *g = (struct grouping_ses *)r->grouping_data; - calculated_number value; - if(unlikely(!g->count || !isnormal(g->level))) { - value = 0.0; *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else { - value = g->level; + return 0.0; } - g->count = 0; - - return value; + return g->level; } diff --git a/web/api/queries/stddev/README.md b/web/api/queries/stddev/README.md index e69de29bb2..a404b49025 100644 --- a/web/api/queries/stddev/README.md +++ b/web/api/queries/stddev/README.md @@ -0,0 +1,85 @@ + +# standard deviation (`stddev`) + +The standard deviation is a measure that is used to quantify the amount of variation or dispersion +of a set of data values. + +A low standard deviation indicates that the data points tend to be close to the mean (also called the +expected value) of the set, while a high standard deviation indicates that the data points are spread +out over a wider range of values. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: stddev -1m unaligned of my_dimension + warn: $this > 1000 +``` + +`stdev` does not change the units. For example, if the chart units is `requests/sec`, the standard +deviation will be again expressed in the same units. + +It can also be used in APIs and badges as `&group=stddev` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +-  +-  +-  +-  + +## References + +Check [https://en.wikipedia.org/wiki/Standard_deviation](https://en.wikipedia.org/wiki/Standard_deviation). + +--- + +# Coefficient of variation (`cv`) + +The coefficient of variation (`cv``), also known as relative standard deviation (RSD), +is a standardized measure of dispersion of a probability distribution or frequency distribution. + +It is defined as the ratio of the **standard deviation** to the **mean**. + +In simple terms, it gives the percentage of change. So, if the average value of a metric is 1000 +and its standard deviation is 100 (meaning that it variates from 900 to 1100), then `cv` is 10%. + +This is an easy way to check the % variation, without using absolute values. + +For example, you may trigger an alarm if your web server requests/sec `cv` is above 20 (`%`) +over the last minute. So if your web server was serving 1000 reqs/sec over the last minute, +it will trigger the alarm if had spikes below 800/sec or above 1200/sec. + +## how to use + +Use it in alarms like this: + +``` + alarm: my_alarm + on: my_chart +lookup: cv -1m unaligned of my_dimension + units: % + warn: $this > 20 +``` + +The units reported by `cv` is always `%`. + +It can also be used in APIs and badges as `&group=cv` in the URL. + +## Examples + +Examining last 1 minute `successful` web server responses: + +-  +-  +-  +-  + +## References + +Check [https://en.wikipedia.org/wiki/Coefficient_of_variation](https://en.wikipedia.org/wiki/Coefficient_of_variation). diff --git a/web/api/queries/stddev/stddev.c b/web/api/queries/stddev/stddev.c index e88a853740..d1e6bb47eb 100644 --- a/web/api/queries/stddev/stddev.c +++ b/web/api/queries/stddev/stddev.c @@ -6,28 +6,26 @@ // ---------------------------------------------------------------------------- // stddev -struct grouping_stddev { - size_t series_size; - size_t next_pos; +// this implementation comes from: +// https://www.johndcook.com/blog/standard_deviation/ - LONG_DOUBLE series[]; +struct grouping_stddev { + long count; + calculated_number m_oldM, m_newM, m_oldS, m_newS; }; void *grouping_init_stddev(RRDR *r) { long entries = (r->group > r->group_points) ? r->group : r->group_points; if(entries < 0) entries = 0; - struct grouping_stddev *g = (struct grouping_stddev *)callocz(1, sizeof(struct grouping_stddev) + entries * sizeof(LONG_DOUBLE)); - g->series_size = (size_t)entries; - - return g; + return callocz(1, sizeof(struct grouping_stddev) + entries * sizeof(LONG_DOUBLE)); } // resets when switches dimensions // so, clear everything to restart void grouping_reset_stddev(RRDR *r) { struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data; - g->next_pos = 0; + g->count = 0; } void grouping_free_stddev(RRDR *r) { @@ -37,37 +35,135 @@ void grouping_free_stddev(RRDR *r) { void grouping_add_stddev(RRDR *r, calculated_number value) { struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data; - if(unlikely(g->next_pos >= g->series_size)) { - error("INTERNAL ERROR: stddev buffer overflow on chart '%s' - next_pos = %zu, series_size = %zu, r->group = %ld, r->group_points = %ld.", r->st->name, g->next_pos, g->series_size, r->group, r->group_points); - } - else { - if(isnormal(value)) - g->series[g->next_pos++] = (LONG_DOUBLE)value; + if(isnormal(value)) { + g->count++; + + // See Knuth TAOCP vol 2, 3rd edition, page 232 + if (g->count == 1) { + g->m_oldM = g->m_newM = value; + g->m_oldS = 0.0; + } + else { + g->m_newM = g->m_oldM + (value - g->m_oldM) / g->count; + g->m_newS = g->m_oldS + (value - g->m_oldM) * (value - g->m_newM); + + // set up for next iteration + g->m_oldM = g->m_newM; + g->m_oldS = g->m_newS; + } } } +static inline calculated_number mean(struct grouping_stddev *g) { + return (g->count > 0) ? g->m_newM : 0.0; +} + +static inline calculated_number variance(struct grouping_stddev *g) { + return ( (g->count > 1) ? g->m_newS/(g->count - 1) : 0.0 ); +} +static inline calculated_number stddev(struct grouping_stddev *g) { + return sqrtl(variance(g)); +} + calculated_number grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data; calculated_number value; - if(unlikely(!g->next_pos)) { + if(unlikely(!g->count)) { value = 0.0; *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; } else { - value = standard_deviation(g->series, g->next_pos); + value = stddev(g); if(!isnormal(value)) { value = 0.0; *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; } - - //log_series_to_stderr(g->series, g->next_pos, value, "stddev"); } - g->next_pos = 0; + grouping_reset_stddev(r); return value; } +// https://en.wikipedia.org/wiki/Coefficient_of_variation +calculated_number grouping_flush_coefficient_of_variation(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + calculated_number m = mean(g); + value = 100.0 * stddev(g) / ((m < 0)? -m : m); + + if(!isnormal(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + + grouping_reset_stddev(r); + + return value; +} + + +/* + * Mean = average + * +calculated_number grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = mean(g); + + if(!isnormal(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + + grouping_reset_stddev(r); + + return value; +} + */ + +/* + * It is not advised to use this version of variance directly + * +calculated_number grouping_flush_variance(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data; + + calculated_number value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = variance(g); + + if(!isnormal(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + + grouping_reset_stddev(r); + + return value; +} +*/ \ No newline at end of file diff --git a/web/api/queries/stddev/stddev.h b/web/api/queries/stddev/stddev.h index 476fca6da2..c6279324b9 100644 --- a/web/api/queries/stddev/stddev.h +++ b/web/api/queries/stddev/stddev.h @@ -11,5 +11,8 @@ extern void grouping_reset_stddev(RRDR *r); extern void grouping_free_stddev(RRDR *r); extern void grouping_add_stddev(RRDR *r, calculated_number value); extern calculated_number grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +extern calculated_number grouping_flush_coefficient_of_variation(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +// extern calculated_number grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +// extern calculated_number grouping_flush_variance(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); #endif //NETDATA_API_QUERIES_STDDEV_H