optimized ses and added des (#4470)

* optimized ses and added des

* added coefficient of variation

* fix bug identified by @vlvkobal: use all available points when resampling is required and the timeframe is not enough for a single point
This commit is contained in:
Costa Tsaousis 2018-10-24 03:03:57 +03:00 committed by GitHub
parent 0a78758a11
commit f857aa35ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 430 additions and 54 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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

View File

@ -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} \

View File

@ -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)

View File

@ -0,0 +1 @@
# double exponential smoothing

112
web/api/queries/des/des.c Normal file
View File

@ -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;
}

15
web/api/queries/des/des.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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:
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=min&after=-60&label=min)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=average&after=-60&label=average&value_color=yellow)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=ses&after=-60&label=single+exponential+smoothing&value_color=orange)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=max&after=-60&label=max)
## 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).

View File

@ -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;
}

View File

@ -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:
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=min&after=-60&label=min)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=average&after=-60&label=average&value_color=yellow)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=stddev&after=-60&label=standard+deviation&value_color=orange)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=max&after=-60&label=max)
## 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:
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=min&after=-60&label=min)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=average&after=-60&label=average&value_color=yellow)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=cv&after=-60&label=coefficient+of+variation&value_color=orange&units=pcent)
- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&dimensions=success&group=max&after=-60&label=max)
## References
Check [https://en.wikipedia.org/wiki/Coefficient_of_variation](https://en.wikipedia.org/wiki/Coefficient_of_variation).

View File

@ -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;
}
*/

View File

@ -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