fix health sum lookup for incremental dimensions (#22554)
* fix health sum lookup for incremental dimensions * Restore globals in incremental sum test
Aryan Katiyar committed
May 28, 2026 at 12:54 UTC
d8e74ebd39c5d36760bb9fc397f1ec02f6fcd166
4 files changed
+90
-1
src/daemon/unit_test.c
+68
@@ -1,6 +1,7 @@
1
// SPDX-License-Identifier: GPL-3.0-or-later
2
3
#include "common.h"
4
+#include "web/api/formatters/rrd2json.h"
5
6
static bool cmd_arg_sanitization_test(const char *expected, const char *src, char *dst, size_t dst_size) {
7
bool ok = sanitize_command_argument_string(dst, src, dst_size);
@@ -1453,6 +1454,70 @@ int check_strdupz_path_subpath() {
1454
return 0;
1455
}
1456
1457
+static int test_incremental_sum_lookup_respects_update_every(void) {
1458
+ fprintf(stderr, "%s() running...\n", __FUNCTION__);
1459
+
1460
+ const time_t update_every = 10;
1461
+ const collected_number increment = 100;
1462
+ const size_t samples = 6;
1463
+ RRD_DB_MODE old_default_rrd_memory_mode = default_rrd_memory_mode;
1464
+ time_t old_update_every = nd_profile.update_every;
1465
+
1466
+ default_rrd_memory_mode = RRD_DB_MODE_ALLOC;
1467
+ nd_profile.update_every = update_every;
1468
+
1469
+ char name[101];
1470
+ snprintfz(name, sizeof(name) - 1, "unittest-incremental-sum-lookup");
1471
+
1472
+ RRDSET *st = rrdset_create_localhost(
1473
+ "netdata", name, name, "netdata", NULL, "Unit Testing", "requests", "unittest", NULL, 1,
1474
+ update_every, RRDSET_TYPE_LINE);
1475
+ RRDDIM *rd = rrddim_add(st, "requests", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1476
+
1477
+ time_t first_update_s = MAX(2 * API_RELATIVE_TIME_MAX, 200000000);
1478
+ st->last_collected_time.tv_sec = st->last_updated.tv_sec = first_update_s - update_every;
1479
+ st->last_collected_time.tv_usec = st->last_updated.tv_usec = 0;
1480
+ rd->collector.last_collected_time = st->last_collected_time;
1481
+
1482
+ for(size_t i = 0; i <= samples; i++) {
1483
+ struct timeval now = {
1484
+ .tv_sec = first_update_s + (time_t)(i * update_every),
1485
+ .tv_usec = 0,
1486
+ };
1487
+
1488
+ st->usec_since_last_update = update_every * USEC_PER_SEC;
1489
+ rrddim_timed_set_by_pointer(st, rd, now, (collected_number)(i * increment));
1490
+ rrdset_timed_done(st, now, false);
1491
+ }
1492
+
1493
+ time_t before = rrdset_last_entry_s(st);
1494
+ time_t after = before - (time_t)(samples * update_every);
1495
+
1496
+ ONEWAYALLOC *owa = onewayalloc_create(0);
1497
+ NETDATA_DOUBLE value = NAN;
1498
+ int value_is_null = 0;
1499
+ int ret = rrdset2value_api_v1_with_owa(
1500
+ owa, st, NULL, &value, "requests", 1, after, before, RRDR_GROUPING_SUM, NULL, 0,
1501
+ RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_SELECTED_TIER | RRDR_OPTION_MATCH_IDS, NULL, NULL, NULL, NULL,
1502
+ NULL, &value_is_null, NULL, 0, 0, QUERY_SOURCE_UNITTEST, STORAGE_PRIORITY_SYNCHRONOUS);
1503
+ onewayalloc_destroy(owa);
1504
+
1505
+ NETDATA_DOUBLE expected = (NETDATA_DOUBLE)(samples * increment);
1506
+ int rc = 0;
1507
+ if(ret != HTTP_RESP_OK || value_is_null || fabsndd(value - expected) > 0.000001) {
1508
+ fprintf(
1509
+ stderr,
1510
+ "incremental sum lookup failed: ret=%d, null=%d, expected " NETDATA_DOUBLE_FORMAT
1511
+ ", got " NETDATA_DOUBLE_FORMAT "\n",
1512
+ ret, value_is_null, expected, value);
1513
+ rc = 1;
1514
+ }
1515
+
1516
+ default_rrd_memory_mode = old_default_rrd_memory_mode;
1517
+ nd_profile.update_every = old_update_every;
1518
+ return rc;
1519
+}
1520
+
1521
int run_all_mockup_tests(void)
1522
{
1523
fprintf(stderr, "%s() running...\n", __FUNCTION__ );
@@ -1465,6 +1530,9 @@ int run_all_mockup_tests(void)
1530
if(check_rrdcalc_comparisons())
1531
return 1;
1532
1533
+ if(test_incremental_sum_lookup_respects_update_every())
1534
+ return 1;
1535
+
1536
if(!test_variable_renames())
1537
return 1;
1538
src/database/contexts/query_target.c
+2
@@ -270,6 +270,7 @@ static bool query_metric_add(QUERY_TARGET_LOCALS *qtl, QUERY_NODE *qn, QUERY_CON
270
} tier_retention[nd_profile.storage_tiers];
271
272
RRDDIM *rd = rrdmetric_rrddim_get_and_lock(rm);
273
+ bool values_stored_as_rates = rd && rd->algorithm == RRD_ALGORITHM_INCREMENTAL;
274
275
for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
276
STORAGE_ENGINE *eng = qn->rrdhost->db[tier].eng;
@@ -343,6 +344,7 @@ static bool query_metric_add(QUERY_TARGET_LOCALS *qtl, QUERY_NODE *qn, QUERY_CON
344
memset(qm, 0, sizeof(*qm));
345
346
qm->status = options;
347
+ qm->values_stored_as_rates = values_stored_as_rates;
348
349
qm->link.query_node_id = qn->slot;
350
qm->link.query_context_id = qc->slot;
src/database/contexts/rrdcontext.h
+1
@@ -214,6 +214,7 @@ typedef struct _query_dimension {
214
215
typedef struct _query_metric {
216
RRDR_DIMENSION_FLAGS status;
217
+ bool values_stored_as_rates;
218
219
struct query_metric_tier {
220
STORAGE_METRIC_HANDLE *smh;
src/web/api/queries/query-execute.c
+19
-1
@@ -30,6 +30,22 @@ static long rrdr_line_init(RRDR *r __maybe_unused, time_t t __maybe_unused, long
30
return rrdr_line;
31
}
32
33
+ALWAYS_INLINE
34
+static NETDATA_DOUBLE query_point_grouping_value(
35
+ QUERY_POINT point, QUERY_ENGINE_OPS *ops, RRDR_TIME_GROUPING add_flush) {
36
+ if(likely(add_flush != RRDR_GROUPING_SUM || !ops->qm->values_stored_as_rates))
37
+ return point.value;
38
+
39
+ if(unlikely(storage_point_is_unset(point.sp) || storage_point_is_gap(point.sp) || !point.sp.count))
40
+ return point.value;
41
+
42
+ time_t duration = point.sp.end_time_s - point.sp.start_time_s;
43
+ if(unlikely(duration <= 0))
44
+ return point.value;
45
+
46
+ return point.value * (NETDATA_DOUBLE)duration / (NETDATA_DOUBLE)point.sp.count;
47
+}
48
+
49
// ----------------------------------------------------------------------------
50
// dimension level query engine
51
@@ -59,7 +75,9 @@ static long rrdr_line_init(RRDR *r __maybe_unused, time_t t __maybe_unused, long
75
if(unlikely((point).sp.flags & SN_FLAG_RESET)) \
76
(ops)->group_value_flags |= RRDR_VALUE_RESET; \
77
\
62
- time_grouping_add(r, (point).value, add_flush); \
78
+ NETDATA_DOUBLE grouping_value = \
79
+ query_point_grouping_value(point, ops, add_flush); \
80
+ time_grouping_add(r, grouping_value, add_flush); \
81
\
82
storage_point_merge_to((ops)->group_point, (point).sp); \
83
if(!(point).added) \