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