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:
+
+- ![](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).
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:
+
+- ![](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).
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