mirror of
https://github.com/netdata/netdata.git
synced 2025-04-23 21:10:22 +00:00
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:
parent
0a78758a11
commit
f857aa35ae
15 changed files with 430 additions and 54 deletions
|
@ -393,6 +393,8 @@ set(API_PLUGIN_FILES
|
||||||
web/api/queries/stddev/stddev.h
|
web/api/queries/stddev/stddev.h
|
||||||
web/api/queries/ses/ses.c
|
web/api/queries/ses/ses.c
|
||||||
web/api/queries/ses/ses.h
|
web/api/queries/ses/ses.h
|
||||||
|
web/api/queries/des/des.c
|
||||||
|
web/api/queries/des/des.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(STREAMING_PLUGIN_FILES
|
set(STREAMING_PLUGIN_FILES
|
||||||
|
|
|
@ -291,6 +291,8 @@ API_PLUGIN_FILES = \
|
||||||
web/api/exporters/shell/allmetrics_shell.h \
|
web/api/exporters/shell/allmetrics_shell.h \
|
||||||
web/api/queries/average/average.c \
|
web/api/queries/average/average.c \
|
||||||
web/api/queries/average/average.h \
|
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.c \
|
||||||
web/api/queries/incremental_sum/incremental_sum.h \
|
web/api/queries/incremental_sum/incremental_sum.h \
|
||||||
web/api/queries/max/max.c \
|
web/api/queries/max/max.c \
|
||||||
|
|
|
@ -609,6 +609,7 @@ AC_CONFIG_FILES([
|
||||||
web/api/exporters/prometheus/Makefile
|
web/api/exporters/prometheus/Makefile
|
||||||
web/api/queries/Makefile
|
web/api/queries/Makefile
|
||||||
web/api/queries/average/Makefile
|
web/api/queries/average/Makefile
|
||||||
|
web/api/queries/des/Makefile
|
||||||
web/api/queries/incremental_sum/Makefile
|
web/api/queries/incremental_sum/Makefile
|
||||||
web/api/queries/max/Makefile
|
web/api/queries/max/Makefile
|
||||||
web/api/queries/median/Makefile
|
web/api/queries/median/Makefile
|
||||||
|
|
|
@ -12,14 +12,14 @@ processors=$(grep -c ^processor /proc/cpuinfo)
|
||||||
base="$(dirname "${0}")"
|
base="$(dirname "${0}")"
|
||||||
[ "${base}" = "." ] && base="${PWD}"
|
[ "${base}" = "." ] && base="${PWD}"
|
||||||
|
|
||||||
cd "${base}/src" || exit 1
|
cd "${base}" || exit 1
|
||||||
|
|
||||||
[ ! -d "cppcheck-build" ] && mkdir "cppcheck-build"
|
[ ! -d "cppcheck-build" ] && mkdir "cppcheck-build"
|
||||||
|
|
||||||
file="${1}"
|
file="${1}"
|
||||||
shift
|
shift
|
||||||
# shellcheck disable=SC2235
|
# shellcheck disable=SC2235
|
||||||
([ "${file}" = "${base}" ] || [ -z "${file}" ]) && file="${base}/src"
|
([ "${file}" = "${base}" ] || [ -z "${file}" ]) && file="${base}"
|
||||||
|
|
||||||
"${cppcheck}" \
|
"${cppcheck}" \
|
||||||
-j ${processors} \
|
-j ${processors} \
|
||||||
|
|
8
web/api/queries/des/Makefile.am
Normal file
8
web/api/queries/des/Makefile.am
Normal 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)
|
1
web/api/queries/des/README.md
Normal file
1
web/api/queries/des/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# double exponential smoothing
|
112
web/api/queries/des/des.c
Normal file
112
web/api/queries/des/des.c
Normal 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
15
web/api/queries/des/des.h
Normal 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
|
|
@ -12,6 +12,7 @@
|
||||||
#include "sum/sum.h"
|
#include "sum/sum.h"
|
||||||
#include "stddev/stddev.h"
|
#include "stddev/stddev.h"
|
||||||
#include "ses/ses.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 }
|
, { "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 }
|
, { "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 }
|
, { "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 }
|
, { "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 }
|
, { 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);
|
info("INTERNAL CHECK: %s: requested gtime %ld secs, is greater than the desired duration %ld secs", st->id, group_time_requested, duration);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
group = points_requested; // use all the points
|
group = available_points; // use all the points
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// the points we should group to satisfy gtime
|
// the points we should group to satisfy gtime
|
||||||
|
|
|
@ -4,15 +4,17 @@
|
||||||
#define NETDATA_API_DATA_QUERY_H
|
#define NETDATA_API_DATA_QUERY_H
|
||||||
|
|
||||||
typedef enum rrdr_grouping {
|
typedef enum rrdr_grouping {
|
||||||
RRDR_GROUPING_UNDEFINED = 0,
|
RRDR_GROUPING_UNDEFINED = 0,
|
||||||
RRDR_GROUPING_AVERAGE = 1,
|
RRDR_GROUPING_AVERAGE,
|
||||||
RRDR_GROUPING_MIN = 2,
|
RRDR_GROUPING_MIN,
|
||||||
RRDR_GROUPING_MAX = 3,
|
RRDR_GROUPING_MAX,
|
||||||
RRDR_GROUPING_SUM = 4,
|
RRDR_GROUPING_SUM,
|
||||||
RRDR_GROUPING_INCREMENTAL_SUM = 5,
|
RRDR_GROUPING_INCREMENTAL_SUM,
|
||||||
RRDR_GROUPING_MEDIAN = 6,
|
RRDR_GROUPING_MEDIAN,
|
||||||
RRDR_GROUPING_STDDEV = 7,
|
RRDR_GROUPING_STDDEV,
|
||||||
RRDR_GROUPING_SES = 8,
|
RRDR_GROUPING_CV,
|
||||||
|
RRDR_GROUPING_SES,
|
||||||
|
RRDR_GROUPING_DES,
|
||||||
} RRDR_GROUPING;
|
} RRDR_GROUPING;
|
||||||
|
|
||||||
extern const char *group_method2string(RRDR_GROUPING group);
|
extern const char *group_method2string(RRDR_GROUPING group);
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -8,15 +8,16 @@
|
||||||
|
|
||||||
struct grouping_ses {
|
struct grouping_ses {
|
||||||
calculated_number alpha;
|
calculated_number alpha;
|
||||||
calculated_number alpha_older;
|
calculated_number alpha_other;
|
||||||
calculated_number level;
|
calculated_number level;
|
||||||
size_t count;
|
size_t count;
|
||||||
size_t has_data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void set_alpha(RRDR *r, struct grouping_ses *g) {
|
static inline void set_alpha(RRDR *r, struct grouping_ses *g) {
|
||||||
g->alpha = 1.0 / r->group;
|
// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
|
||||||
g->alpha_older = 1 - g->alpha;
|
// 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) {
|
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;
|
struct grouping_ses *g = (struct grouping_ses *)r->grouping_data;
|
||||||
g->level = 0.0;
|
g->level = 0.0;
|
||||||
g->count = 0;
|
g->count = 0;
|
||||||
g->has_data = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void grouping_free_ses(RRDR *r) {
|
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;
|
struct grouping_ses *g = (struct grouping_ses *)r->grouping_data;
|
||||||
|
|
||||||
if(isnormal(value)) {
|
if(isnormal(value)) {
|
||||||
if(unlikely(!g->has_data)) {
|
if(unlikely(!g->count))
|
||||||
g->level = value;
|
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++;
|
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) {
|
calculated_number grouping_flush_ses(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) {
|
||||||
struct grouping_ses *g = (struct grouping_ses *)r->grouping_data;
|
struct grouping_ses *g = (struct grouping_ses *)r->grouping_data;
|
||||||
|
|
||||||
calculated_number value;
|
|
||||||
|
|
||||||
if(unlikely(!g->count || !isnormal(g->level))) {
|
if(unlikely(!g->count || !isnormal(g->level))) {
|
||||||
value = 0.0;
|
|
||||||
*rrdr_value_options_ptr |= RRDR_VALUE_EMPTY;
|
*rrdr_value_options_ptr |= RRDR_VALUE_EMPTY;
|
||||||
}
|
return 0.0;
|
||||||
else {
|
|
||||||
value = g->level;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g->count = 0;
|
return g->level;
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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).
|
|
@ -6,28 +6,26 @@
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// stddev
|
// stddev
|
||||||
|
|
||||||
struct grouping_stddev {
|
// this implementation comes from:
|
||||||
size_t series_size;
|
// https://www.johndcook.com/blog/standard_deviation/
|
||||||
size_t next_pos;
|
|
||||||
|
|
||||||
LONG_DOUBLE series[];
|
struct grouping_stddev {
|
||||||
|
long count;
|
||||||
|
calculated_number m_oldM, m_newM, m_oldS, m_newS;
|
||||||
};
|
};
|
||||||
|
|
||||||
void *grouping_init_stddev(RRDR *r) {
|
void *grouping_init_stddev(RRDR *r) {
|
||||||
long entries = (r->group > r->group_points) ? r->group : r->group_points;
|
long entries = (r->group > r->group_points) ? r->group : r->group_points;
|
||||||
if(entries < 0) entries = 0;
|
if(entries < 0) entries = 0;
|
||||||
|
|
||||||
struct grouping_stddev *g = (struct grouping_stddev *)callocz(1, sizeof(struct grouping_stddev) + entries * sizeof(LONG_DOUBLE));
|
return callocz(1, sizeof(struct grouping_stddev) + entries * sizeof(LONG_DOUBLE));
|
||||||
g->series_size = (size_t)entries;
|
|
||||||
|
|
||||||
return g;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// resets when switches dimensions
|
// resets when switches dimensions
|
||||||
// so, clear everything to restart
|
// so, clear everything to restart
|
||||||
void grouping_reset_stddev(RRDR *r) {
|
void grouping_reset_stddev(RRDR *r) {
|
||||||
struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data;
|
struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data;
|
||||||
g->next_pos = 0;
|
g->count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void grouping_free_stddev(RRDR *r) {
|
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) {
|
void grouping_add_stddev(RRDR *r, calculated_number value) {
|
||||||
struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data;
|
struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data;
|
||||||
|
|
||||||
if(unlikely(g->next_pos >= g->series_size)) {
|
if(isnormal(value)) {
|
||||||
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);
|
g->count++;
|
||||||
}
|
|
||||||
else {
|
// See Knuth TAOCP vol 2, 3rd edition, page 232
|
||||||
if(isnormal(value))
|
if (g->count == 1) {
|
||||||
g->series[g->next_pos++] = (LONG_DOUBLE)value;
|
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) {
|
calculated_number grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) {
|
||||||
struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data;
|
struct grouping_stddev *g = (struct grouping_stddev *)r->grouping_data;
|
||||||
|
|
||||||
calculated_number value;
|
calculated_number value;
|
||||||
|
|
||||||
if(unlikely(!g->next_pos)) {
|
if(unlikely(!g->count)) {
|
||||||
value = 0.0;
|
value = 0.0;
|
||||||
*rrdr_value_options_ptr |= RRDR_VALUE_EMPTY;
|
*rrdr_value_options_ptr |= RRDR_VALUE_EMPTY;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
value = standard_deviation(g->series, g->next_pos);
|
value = stddev(g);
|
||||||
|
|
||||||
if(!isnormal(value)) {
|
if(!isnormal(value)) {
|
||||||
value = 0.0;
|
value = 0.0;
|
||||||
*rrdr_value_options_ptr |= RRDR_VALUE_EMPTY;
|
*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;
|
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;
|
||||||
|
}
|
||||||
|
*/
|
|
@ -11,5 +11,8 @@ extern void grouping_reset_stddev(RRDR *r);
|
||||||
extern void grouping_free_stddev(RRDR *r);
|
extern void grouping_free_stddev(RRDR *r);
|
||||||
extern void grouping_add_stddev(RRDR *r, calculated_number value);
|
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_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
|
#endif //NETDATA_API_QUERIES_STDDEV_H
|
||||||
|
|
Loading…
Add table
Reference in a new issue