@cryptotaxi247 / netdata / commits / d8e74ebd3

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) \