@cryptotaxi247 / netdata-1 / commits / b63f741d2

add interpolation to median and percentile (#19606)

Costa Tsaousis committed Feb 10, 2025 at 12:32 UTC b63f741d27d4e2172346a8296905464e3d966664
3 files changed +24 -20
src/collectors/statsd.plugin/statsd.c
+2 -7
@@ -1983,15 +1983,10 @@ static inline void statsd_flush_timer_or_histogram(STATSD_METRIC *m, const char
1983 m->histogram.ext->last_min = (collected_number)roundndd(series[0] * statsd.decimal_detail);
1984 m->histogram.ext->last_max = (collected_number)roundndd(series[len - 1] * statsd.decimal_detail);
1985 m->last = (collected_number)roundndd(average(series, len) * statsd.decimal_detail);
1986 - m->histogram.ext->last_median = (collected_number)roundndd(median_on_sorted_series(series, len) * statsd.decimal_detail);
1986 m->histogram.ext->last_stddev = (collected_number)roundndd(standard_deviation(series, len) * statsd.decimal_detail);
1987 m->histogram.ext->last_sum = (collected_number)roundndd(sum(series, len) * statsd.decimal_detail);
1989 -
1990 - size_t pct_len = (size_t)floor((double)len * statsd.histogram_percentile / 100.0);
1991 - if(pct_len < 1)
1992 - m->histogram.ext->last_percentile = (collected_number)(series[0] * statsd.decimal_detail);
1993 - else
1994 - m->histogram.ext->last_percentile = (collected_number)roundndd(series[pct_len - 1] * statsd.decimal_detail);
1988 + m->histogram.ext->last_median = (collected_number)roundndd(median_on_sorted_series(series, len) * statsd.decimal_detail);
1989 + m->histogram.ext->last_percentile = (collected_number)roundndd(percentile_on_sorted_series(series, len, statsd.histogram_percentile / 100) * statsd.decimal_detail);
1990
1991 netdata_mutex_unlock(&m->histogram.ext->mutex);
1992
src/libnetdata/statistical/statistical.c
+21 -13
@@ -112,21 +112,29 @@ inline NETDATA_DOUBLE *copy_series(const NETDATA_DOUBLE *series, size_t entries)
112 return copy;
113 }
114
115 -NETDATA_DOUBLE median_on_sorted_series(const NETDATA_DOUBLE *series, size_t entries) {
116 - if(unlikely(entries == 0)) return NAN;
117 - if(unlikely(entries == 1)) return series[0];
118 - if(unlikely(entries == 2)) return (series[0] + series[1]) / 2;
115 +NETDATA_DOUBLE percentile_on_sorted_series(const NETDATA_DOUBLE *series, size_t entries, double percentile) {
116 + if (unlikely(entries == 0)) return NAN;
117 + if (unlikely(entries == 1)) return series[0];
118
120 - NETDATA_DOUBLE average;
121 - if(entries % 2 == 0) {
122 - size_t m = entries / 2;
123 - average = (series[m] + series[m + 1]) / 2;
124 - }
125 - else {
126 - average = series[entries / 2];
127 - }
119 + // Clamp percentile between 0.0 and 1.0
120 + percentile = fmax(0.0, fmin(1.0, percentile));
121
129 - return average;
122 + // Compute fractional index
123 + NETDATA_DOUBLE index = percentile * (NETDATA_DOUBLE)(entries - 1);
124 + size_t low_idx = (size_t)floor(index);
125 + size_t high_idx = (size_t)ceil(index);;
126 +
127 + // If index is an integer or at the last element, return directly
128 + if (high_idx >= entries || low_idx == high_idx || considered_equal_ndd(index, (NETDATA_DOUBLE)low_idx))
129 + return series[low_idx];
130 +
131 + // Linear interpolation
132 + NETDATA_DOUBLE weight = index - (NETDATA_DOUBLE)low_idx;
133 + return series[low_idx] + weight * (series[high_idx] - series[low_idx]);
134 +}
135 +
136 +NETDATA_DOUBLE median_on_sorted_series(const NETDATA_DOUBLE *series, size_t entries) {
137 + return percentile_on_sorted_series(series, entries, 0.5);
138 }
139
140 NETDATA_DOUBLE median(const NETDATA_DOUBLE *series, size_t entries) {
src/libnetdata/statistical/statistical.h
+1
@@ -28,6 +28,7 @@ NETDATA_DOUBLE holtwinters(const NETDATA_DOUBLE *series, size_t entries,
28 NETDATA_DOUBLE sum_and_count(const NETDATA_DOUBLE *series, size_t entries, size_t *count);
29 NETDATA_DOUBLE sum(const NETDATA_DOUBLE *series, size_t entries);
30 NETDATA_DOUBLE median_on_sorted_series(const NETDATA_DOUBLE *series, size_t entries);
31 +NETDATA_DOUBLE percentile_on_sorted_series(const NETDATA_DOUBLE *series, size_t entries, double percentile);
32 NETDATA_DOUBLE *copy_series(const NETDATA_DOUBLE *series, size_t entries);
33 void sort_series(NETDATA_DOUBLE *series, size_t entries);
34