Skip to content

Commit

Permalink
optimized ses and added des (#4470)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ktsaou committed Oct 24, 2018
1 parent 0a78758 commit f857aa3
Show file tree
Hide file tree
Showing 15 changed files with 428 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions cppcheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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} \
Expand Down
8 changes: 8 additions & 0 deletions web/api/queries/des/Makefile.am
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions web/api/queries/des/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# double exponential smoothing
112 changes: 112 additions & 0 deletions web/api/queries/des/des.c
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions web/api/queries/des/des.h
Original file line number Diff line number Diff line change
@@ -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
21 changes: 18 additions & 3 deletions web/api/queries/query.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "sum/sum.h"
#include "stddev/stddev.h"
#include "ses/ses.h"
#include "des/des.h"

// ----------------------------------------------------------------------------

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

Expand Down Expand Up @@ -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
Expand Down
20 changes: 11 additions & 9 deletions web/api/queries/query.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
46 changes: 45 additions & 1 deletion web/api/queries/ses/README.md
Original file line number Diff line number Diff line change
@@ -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).
28 changes: 9 additions & 19 deletions web/api/queries/ses/ses.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -44,31 +44,21 @@ 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++;
}
}

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;
return 0.0;
}
else {
value = g->level;
}

g->count = 0;

return value;
return g->level;
}
Loading

0 comments on commit f857aa3

Please sign in to comment.