@cryptotaxi247 / netdata-1 / commits / 8a8cb9923

Fix mrg unittest (#21986)

* Refactor MRG engine tests to improve isolation, enhance stress testing, and ensure proper cleanup * Add MRG retention benchmark and integrate it into unit tests * Refactor `mrg_create` logic into `mrg_create_internal` to reduce duplication and ensure consistent initialization. Fix data race on `run_flag` in `mrg_bench_thread_control_t by proper atomic operations. * Fix data race on `tc->run_flag` in MRG unit tests by replacing non-atomic access with `__atomic_load_n` for thread safety. * Handle division by zero in MRG unit test stats calculation. * Initialize metric and add validation to prevent duplicate metric addition in MRG unit tests

Stelios Fragkakis committed Mar 21, 2026 at 16:47 UTC 8a8cb992321469439b454193965924957f0b82f8
4 files changed +403 -39
src/daemon/main.c
+4
@@ -541,6 +541,10 @@ int netdata_main(int argc, char **argv) {
541 unittest_running = true;
542 return mrg_unittest();
543 }
544 + else if(strcmp(optarg, "mrgretentionbench") == 0) {
545 + unittest_running = true;
546 + return mrg_retention_benchmark();
547 + }
548 else if(strcmp(optarg, "parsertest") == 0) {
549 unittest_running = true;
550 return pluginsd_parser_unittest();
src/database/engine/mrg-unittest.c
+378 -36
@@ -1,6 +1,12 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "mrg-internals.h"
4 +#include "rrdengine.h"
5 +
6 +// Global dummy rrdengine_instances for tests
7 +static struct rrdengine_instance test_ctx_0 = {0};
8 +static struct rrdengine_instance test_ctx_1 = {0};
9 +static struct rrdengine_instance test_ctx_tier[4] = { {0}, {0}, {0}, {0} }; // For stress test tiers
10
11 struct mrg_stress_entry {
12 nd_uuid_t uuid;
@@ -38,7 +44,7 @@ static void mrg_stress(void *ptr) {
44 time_t before = __atomic_add_fetch(&e->before, 1, __ATOMIC_RELAXED);
45
46 mrg_update_metric_retention_and_granularity_by_uuid(
41 - mrg, 0x01, &e->uuid, after, before, 1, before, NULL);
47 + mrg, (Word_t)&test_ctx_0, &e->uuid, after, before, 1, before, NULL);
48
49 __atomic_add_fetch(&t->updates, 1, __ATOMIC_RELAXED);
50 }
@@ -46,7 +52,8 @@ static void mrg_stress(void *ptr) {
52 }
53
54 int mrg_unittest(void) {
49 - MRG *mrg = mrg_create();
55 + // Use mrg_create_for_unittest to avoid pre-loaded metrics that block deletion
56 + MRG *mrg = mrg_create_for_unittest();
57 METRIC *m1_t0, *m2_t0, *m3_t0, *m4_t0;
58 METRIC *m1_t1, *m2_t1, *m3_t1, *m4_t1;
59 bool ret;
@@ -55,7 +62,7 @@ int mrg_unittest(void) {
62 uuid_generate(test_uuid);
63 MRG_ENTRY entry = {
64 .uuid = &test_uuid,
58 - .section = 0,
65 + .section = (Word_t)&test_ctx_0,
66 .first_time_s = 2,
67 .last_time_s = 3,
68 .latest_update_every_s = 4,
@@ -83,7 +90,7 @@ int mrg_unittest(void) {
90 fatal("DBENGINE METRIC: managed to add the same metric twice");
91
92 // add the same metric in another section
86 - entry.section = 1;
93 + entry.section = (Word_t)&test_ctx_1;
94 m1_t1 = mrg_metric_add_and_acquire(mrg, entry, &ret);
95 if(!ret)
96 fatal("DBENGINE METRIC: failed to add metric in section %zu", (size_t)entry.section);
@@ -99,40 +106,29 @@ int mrg_unittest(void) {
106 if(m3_t1 != m1_t1)
107 fatal("DBENGINE METRIC: cannot find the metric added (section %zu)", (size_t)entry.section);
108
102 - // delete the first metric
109 + // add the same metric again in section 1
110 + m4_t1 = mrg_metric_add_and_acquire(mrg, entry, &ret);
111 + if(m4_t1 != m1_t1)
112 + fatal("DBENGINE METRIC: adding the same metric twice (section %zu), does not return the same pointer", (size_t)entry.section);
113 + if(ret)
114 + fatal("DBENGINE METRIC: managed to add the same metric twice in (section %zu)", (size_t)entry.section);
115 +
116 + // Release all references to these initial test metrics
117 mrg_metric_release(mrg, m2_t0);
118 mrg_metric_release(mrg, m3_t0);
119 mrg_metric_release(mrg, m4_t0);
106 - mrg_metric_set_first_time_s(mrg, m1_t0, 0);
107 - mrg_metric_set_clean_latest_time_s(mrg, m1_t0, 0);
108 - mrg_metric_set_hot_latest_time_s(mrg, m1_t0, 0);
109 - if(!mrg_metric_release_and_delete(mrg, m1_t0))
110 - fatal("DBENGINE METRIC: cannot delete the first metric");
120 + mrg_metric_release(mrg, m1_t0);
121
112 - m4_t1 = mrg_metric_get_and_acquire_by_uuid(mrg, entry.uuid, entry.section);
113 - if(m4_t1 != m1_t1)
114 - fatal("DBENGINE METRIC: cannot find the metric added (section %zu), after deleting the first one", (size_t)entry.section);
115 -
116 - // delete the second metric
122 mrg_metric_release(mrg, m2_t1);
123 mrg_metric_release(mrg, m3_t1);
124 mrg_metric_release(mrg, m4_t1);
120 - mrg_metric_set_first_time_s(mrg, m1_t1, 0);
121 - mrg_metric_set_clean_latest_time_s(mrg, m1_t1, 0);
122 - mrg_metric_set_hot_latest_time_s(mrg, m1_t1, 0);
123 - if(!mrg_metric_release_and_delete(mrg, m1_t1))
124 - fatal("DBENGINE METRIC: cannot delete the second metric");
125 -
126 - struct mrg_statistics s;
127 - mrg_get_statistics(mrg, &s);
128 - if(s.entries != 0)
129 - fatal("DBENGINE METRIC: invalid entries counter");
130 -
131 - size_t entries = 1000000;
125 + mrg_metric_release(mrg, m1_t1);
126 +
127 + size_t entries = 100000; // Reduced from 1M to make deletion test feasible
128 size_t threads = _countof(mrg->index) / 3 + 1;
129 size_t tiers = 3;
130 size_t run_for_secs = 5;
135 - netdata_log_info("preparing stress test of %zu entries...", entries);
131 + fprintf(stderr, "preparing stress test of %zu entries...\n", entries);
132 struct mrg_stress t = {
133 .mrg = mrg,
134 .entries = entries,
@@ -145,12 +141,12 @@ int mrg_unittest(void) {
141 t.array[i].after = now / 3;
142 t.array[i].before = now / 2;
143 }
148 - netdata_log_info("stress test is populating MRG with 3 tiers...");
144 + fprintf(stderr, "stress test is populating MRG with 3 tiers...\n");
145 for(size_t i = 0; i < entries ;i++) {
146 struct mrg_stress_entry *e = &t.array[i];
147 for(size_t tier = 1; tier <= tiers ;tier++) {
148 mrg_update_metric_retention_and_granularity_by_uuid(
153 - mrg, tier,
149 + mrg, (Word_t)&test_ctx_tier[tier],
150 &e->uuid,
151 e->after,
152 e->before,
@@ -158,7 +154,7 @@ int mrg_unittest(void) {
154 e->before, NULL);
155 }
156 }
161 - netdata_log_info("stress test ready to run...");
157 + fprintf(stderr, "stress test ready to run...\n");
158
159 usec_t started_ut = now_monotonic_usec();
160
@@ -183,22 +179,368 @@ int mrg_unittest(void) {
179 struct mrg_statistics stats;
180 mrg_get_statistics(mrg, &stats);
181
186 - netdata_log_info("DBENGINE METRIC: did %zu additions, %zu duplicate additions, "
182 + fprintf(stderr, "DBENGINE METRIC: did %zu additions, %zu duplicate additions, "
183 "%zu deletions, %zu wrong deletions, "
184 "%zu successful searches, %zu wrong searches, "
189 - "in %"PRIu64" usecs",
185 + "in %"PRIu64" usecs\n",
186 stats.additions, stats.additions_duplicate,
187 stats.deletions, stats.delete_misses,
188 stats.search_hits, stats.search_misses,
189 ended_ut - started_ut);
190
195 - netdata_log_info("DBENGINE METRIC: updates performance: %0.2fk/sec total, %0.2fk/sec/thread",
191 + fprintf(stderr, "DBENGINE METRIC: updates performance: %0.2fk/sec total, %0.2fk/sec/thread\n",
192 (double)t.updates / (double)((ended_ut - started_ut) / USEC_PER_SEC) / 1000.0,
193 (double)t.updates / (double)((ended_ut - started_ut) / USEC_PER_SEC) / 1000.0 / threads);
194
199 - mrg_destroy(mrg);
195 + fprintf(stderr, "DBENGINE METRIC: addition rate: %0.2fk/sec, search rate: %0.2fk/sec, deletion rate: %zu/%zu attempted\n",
196 + (double)stats.additions / (double)((ended_ut - started_ut) / USEC_PER_SEC) / 1000.0,
197 + (double)(stats.search_hits + stats.search_misses) / (double)((ended_ut - started_ut) / USEC_PER_SEC) / 1000.0,
198 + stats.deletions, stats.deletions + stats.delete_misses);
199 +
200 + // Phase 3: Measure final statistics
201 + struct mrg_statistics final_stats;
202 + mrg_get_statistics(mrg, &final_stats);
203 + fprintf(stderr, "DBENGINE METRIC: final MRG state - %zu entries, %zu acquired\n",
204 + final_stats.entries, final_stats.entries_acquired);
205 +
206 + freez(t.array);
207 +
208 + // Destroy MRG (will handle cleanup of any remaining metrics)
209 + size_t leaked = mrg_destroy(mrg);
210 + if(leaked > 0) {
211 + fprintf(stderr, "DBENGINE METRIC: warning - %zu metrics still referenced during destroy\n", leaked);
212 + } else {
213 + fprintf(stderr, "DBENGINE METRIC: all metrics properly cleaned up\n");
214 + }
215 +
216 + fprintf(stderr, "DBENGINE METRIC: all tests passed!\n");
217 +
218 + return 0;
219 +}
220 +
221 +// ============================================================================
222 +// MRG Retention Benchmark
223 +// ============================================================================
224 +
225 +#define MRG_BENCH_MAX_THREADS 64
226 +#define MRG_BENCH_TEST_DURATION_SEC 1
227 +#define MRG_BENCH_STOP_SIGNAL UINT64_MAX
228 +#define MRG_BENCH_MAX_CONFIGS 12
229 +
230 +typedef struct {
231 + uint64_t operations;
232 + uint64_t violations; // consistency check failures
233 + usec_t test_time;
234 + volatile int ready;
235 +} mrg_bench_thread_stats_t;
236 +
237 +typedef struct {
238 + netdata_cond_t cond;
239 + netdata_mutex_t cond_mutex;
240 + uint64_t run_flag; // accessed by multiple threads via __atomic builtins
241 +} mrg_bench_thread_control_t;
242 +
243 +typedef enum {
244 + MRG_BENCH_READER,
245 + MRG_BENCH_WRITER
246 +} mrg_bench_thread_type_t;
247 +
248 +typedef struct {
249 + int thread_id;
250 + mrg_bench_thread_type_t type;
251 + MRG *mrg;
252 + METRIC *metric;
253 + mrg_bench_thread_stats_t *stats;
254 + mrg_bench_thread_control_t *control;
255 + ND_THREAD *thread;
256 +} mrg_bench_thread_context_t;
257 +
258 +typedef struct {
259 + double reader_ops_per_sec[MRG_BENCH_MAX_CONFIGS];
260 + double writer_ops_per_sec[MRG_BENCH_MAX_CONFIGS];
261 + uint64_t total_violations[MRG_BENCH_MAX_CONFIGS];
262 + int readers[MRG_BENCH_MAX_CONFIGS];
263 + int writers[MRG_BENCH_MAX_CONFIGS];
264 + int config_count;
265 +} mrg_bench_summary_stats_t;
266 +
267 +static void mrg_bench_wait_for_start(netdata_cond_t *cond, netdata_mutex_t *mutex, uint64_t *flag) {
268 + netdata_mutex_lock(mutex);
269 + while (__atomic_load_n(flag, __ATOMIC_RELAXED) == 0)
270 + netdata_cond_wait(cond, mutex);
271 + netdata_mutex_unlock(mutex);
272 +}
273 +
274 +static void mrg_bench_thread(void *arg) {
275 + mrg_bench_thread_context_t *ctx = (mrg_bench_thread_context_t *)arg;
276 + mrg_bench_thread_control_t *tc = ctx->control;
277 + MRG *mrg = ctx->mrg;
278 + METRIC *metric = ctx->metric;
279 +
280 + while(1) {
281 + mrg_bench_wait_for_start(&tc->cond, &tc->cond_mutex, &tc->run_flag);
282 +
283 + if(__atomic_load_n(&tc->run_flag, __ATOMIC_RELAXED) == MRG_BENCH_STOP_SIGNAL)
284 + break;
285 +
286 + usec_t start = now_monotonic_high_precision_usec();
287 + uint64_t operations = 0;
288 + uint64_t violations = 0;
289 +
290 + if(ctx->type == MRG_BENCH_WRITER) {
291 + // Writer: expand retention with incrementing timestamps
292 + time_t seq = 1000;
293 + while(__atomic_load_n(&tc->run_flag, __ATOMIC_RELAXED)) {
294 + seq++;
295 + // Expand retention with incrementing first and last time
296 + mrg_metric_expand_retention(mrg, metric, seq - 100, seq, 10);
297 + operations++;
298 + }
299 + }
300 + else {
301 + // Reader: read retention and check consistency invariant
302 + while(__atomic_load_n(&tc->run_flag, __ATOMIC_RELAXED)) {
303 + time_t first_time_s, last_time_s;
304 + uint32_t update_every_s;
305 + mrg_metric_get_retention(mrg, metric, &first_time_s, &last_time_s, &update_every_s);
306 +
307 + // Consistency check: first_time_s <= last_time_s
308 + if(unlikely(first_time_s > 0 && last_time_s > 0 && first_time_s > last_time_s))
309 + violations++;
310 +
311 + operations++;
312 + }
313 + }
314 +
315 + usec_t test_time = now_monotonic_high_precision_usec() - start;
316 + __atomic_store_n(&ctx->stats[ctx->thread_id].test_time, test_time, __ATOMIC_RELEASE);
317 + __atomic_store_n(&ctx->stats[ctx->thread_id].operations, operations, __ATOMIC_RELEASE);
318 + __atomic_store_n(&ctx->stats[ctx->thread_id].violations, violations, __ATOMIC_RELEASE);
319 + __atomic_store_n(&ctx->stats[ctx->thread_id].ready, 1, __ATOMIC_RELEASE);
320 + }
321 +}
322 +
323 +static void mrg_bench_print_thread_stats(const char *test_name, int readers, int writers,
324 + mrg_bench_thread_context_t *contexts,
325 + mrg_bench_thread_stats_t *stats,
326 + mrg_bench_summary_stats_t *summary, int config_idx) {
327 + fprintf(stderr, "\n%-20s (readers: %d, writers: %d)\n", test_name, readers, writers);
328 + fprintf(stderr, "%4s %8s %12s %12s %12s %12s\n",
329 + "THR", "TYPE", "OPS", "OPS/SEC", "VIOLATIONS", "TIME (ms)");
330 +
331 + double reader_ops_per_sec = 0;
332 + double writer_ops_per_sec = 0;
333 + uint64_t total_violations = 0;
334 +
335 + for(int i = 0; i < readers + writers; i++) {
336 + uint64_t ops = __atomic_load_n(&stats[i].operations, __ATOMIC_RELAXED);
337 + uint64_t viol = __atomic_load_n(&stats[i].violations, __ATOMIC_RELAXED);
338 + usec_t time = __atomic_load_n(&stats[i].test_time, __ATOMIC_RELAXED);
339 + double ops_per_sec = time > 0 ? (double)ops * USEC_PER_SEC / time : 0.0;
340 +
341 + fprintf(stderr, "%4d %8s %12"PRIu64" %12.0f %12"PRIu64" %12.2f\n",
342 + i,
343 + contexts[i].type == MRG_BENCH_READER ? "READER" : "WRITER",
344 + ops, ops_per_sec, viol, (double)time / 1000.0);
345 +
346 + total_violations += viol;
347 +
348 + if(contexts[i].type == MRG_BENCH_READER)
349 + reader_ops_per_sec += ops_per_sec;
350 + else
351 + writer_ops_per_sec += ops_per_sec;
352 + }
353 +
354 + if(total_violations > 0) {
355 + fprintf(stderr, "\nFATAL ERROR: Detected %"PRIu64" consistency violations (torn reads)!\n",
356 + total_violations);
357 + fflush(stderr);
358 + _exit(1);
359 + }
360 +
361 + summary->reader_ops_per_sec[config_idx] = reader_ops_per_sec;
362 + summary->writer_ops_per_sec[config_idx] = writer_ops_per_sec;
363 + summary->total_violations[config_idx] = total_violations;
364 + summary->readers[config_idx] = readers;
365 + summary->writers[config_idx] = writers;
366 +}
367 +
368 +static void mrg_bench_run_test(const char *name, int readers, int writers,
369 + mrg_bench_thread_context_t *contexts,
370 + mrg_bench_thread_stats_t *stats,
371 + mrg_bench_thread_control_t *controls,
372 + mrg_bench_summary_stats_t *summary, int config_idx) {
373 + int total_threads = readers + writers;
374 +
375 + fprintf(stderr, "\nRunning test: %s with %d readers and %d writers...\n",
376 + name, readers, writers);
377 +
378 + // Reset
379 + memset(stats, 0, total_threads * sizeof(mrg_bench_thread_stats_t));
380 +
381 + // Signal threads to start
382 + for(int i = 0; i < total_threads; i++) {
383 + netdata_mutex_lock(&controls[i].cond_mutex);
384 + __atomic_store_n(&controls[i].run_flag, 1, __ATOMIC_RELAXED);
385 + netdata_cond_signal(&controls[i].cond);
386 + netdata_mutex_unlock(&controls[i].cond_mutex);
387 + }
388 +
389 + sleep_usec(MRG_BENCH_TEST_DURATION_SEC * USEC_PER_SEC);
390 +
391 + // Signal stop
392 + for(int i = 0; i < total_threads; i++)
393 + __atomic_store_n(&controls[i].run_flag, 0, __ATOMIC_RELEASE);
394 +
395 + // Wait for results
396 + for(int i = 0; i < total_threads; i++) {
397 + while(!__atomic_load_n(&stats[i].ready, __ATOMIC_ACQUIRE))
398 + sleep_usec(10);
399 + }
400 +
401 + mrg_bench_print_thread_stats(name, readers, writers, contexts, stats, summary, config_idx);
402 +}
403 +
404 +static void mrg_bench_print_summary(const mrg_bench_summary_stats_t *summary) {
405 + fprintf(stderr, "\n=== MRG Retention Benchmark Summary (Million ops/sec) ===\n\n");
406 + fprintf(stderr, "%-8s %-8s %16s %16s\n",
407 + "Readers", "Writers", "Reader Ops/s", "Writer Ops/s");
408 + fprintf(stderr, "----------------------------------------------------------------------\n");
409 +
410 + for(int config = 0; config < summary->config_count; config++) {
411 + double reader_ops = summary->reader_ops_per_sec[config];
412 + double writer_ops = summary->writer_ops_per_sec[config];
413 +
414 + fprintf(stderr, "%-8d %-8d %16.2f %16.2f\n",
415 + summary->readers[config],
416 + summary->writers[config],
417 + reader_ops / 1000000.0,
418 + writer_ops / 1000000.0);
419 + }
420 + fprintf(stderr, "\n");
421 +}
422 +
423 +int mrg_retention_benchmark(void) {
424 + mrg_bench_summary_stats_t summary = {0};
425 +
426 + // Use mrg_create_for_unittest() to avoid loading from database
427 + MRG *mrg = mrg_create_for_unittest();
428 + nd_uuid_t test_uuid;
429 + uuid_generate(test_uuid);
430 +
431 + MRG_ENTRY entry = {
432 + .uuid = &test_uuid,
433 + .section = (Word_t)&test_ctx_0,
434 + .first_time_s = 1000,
435 + .last_time_s = 2000,
436 + .latest_update_every_s = 10,
437 + };
438 + bool added;
439 + METRIC *metric = mrg_metric_add_and_acquire(mrg, entry, &added);
440 +
441 + if(!added) {
442 + fatal("DBENGINE METRIC: failed to add metric for benchmark");
443 + }
444 +
445 + mrg_bench_thread_stats_t stats[MRG_BENCH_MAX_THREADS];
446 + mrg_bench_thread_control_t controls[MRG_BENCH_MAX_THREADS];
447 + mrg_bench_thread_context_t contexts[MRG_BENCH_MAX_THREADS];
448 +
449 + fprintf(stderr, "\nStarting MRG retention benchmark...\n");
450 + fprintf(stderr, "Creating threads...\n");
451 +
452 + // Initialize per-thread controls
453 + for(int i = 0; i < MRG_BENCH_MAX_THREADS; i++) {
454 + netdata_cond_init(&controls[i].cond);
455 + netdata_mutex_init(&controls[i].cond_mutex);
456 + __atomic_store_n(&controls[i].run_flag, 0, __ATOMIC_RELAXED);
457 + }
458 +
459 + // Create threads
460 + for(int i = 0; i < MRG_BENCH_MAX_THREADS; i++) {
461 + char thr_name[32];
462 + snprintf(thr_name, sizeof(thr_name), "mrgbench%d", i);
463 +
464 + contexts[i] = (mrg_bench_thread_context_t){
465 + .thread_id = i,
466 + .type = MRG_BENCH_READER,
467 + .mrg = mrg,
468 + .metric = metric,
469 + .stats = stats,
470 + .control = &controls[i],
471 + };
472 + contexts[i].thread =
473 + nd_thread_create(thr_name, NETDATA_THREAD_OPTION_DONT_LOG, mrg_bench_thread, &contexts[i]);
474 + }
475 +
476 + // Test configurations: [readers, writers]
477 + int configs[][2] = {
478 + {1, 0}, // Single reader (no contention baseline)
479 + {0, 1}, // Single writer (write throughput baseline)
480 + {1, 1}, // 1 reader + 1 writer
481 + {2, 1}, // 2 readers + 1 writer (typical seqlock sweet spot)
482 + {4, 1}, // 4 readers + 1 writer
483 + {8, 1}, // 8 readers + 1 writer (high read contention)
484 + {16, 1}, // 16 readers + 1 writer
485 + };
486 +
487 + const int num_configs = sizeof(configs) / sizeof(configs[0]);
488 + summary.config_count = num_configs;
489 +
490 + // Warm up
491 + sleep_usec(100000);
492 +
493 + for(int i = 0; i < num_configs; i++) {
494 + int readers = configs[i][0];
495 + int writers = configs[i][1];
496 + int total = readers + writers;
497 +
498 + // Assign reader/writer roles
499 + int thread_idx = 0;
500 + for(int r = 0; r < readers; r++) {
501 + contexts[thread_idx].type = MRG_BENCH_READER;
502 + thread_idx++;
503 + }
504 + for(int w = 0; w < writers; w++) {
505 + contexts[thread_idx].type = MRG_BENCH_WRITER;
506 + thread_idx++;
507 + }
508
201 - netdata_log_info("DBENGINE METRIC: all tests passed!");
509 + // Reset roles for unused threads
510 + for(int j = total; j < MRG_BENCH_MAX_THREADS; j++) {
511 + contexts[j].type = MRG_BENCH_READER;
512 + }
513 +
514 + char test_name[64];
515 + snprintf(test_name, sizeof(test_name), "mrg_retention %dR/%dW", readers, writers);
516 + mrg_bench_run_test(test_name, readers, writers, contexts, stats, controls, &summary, i);
517 + }
518 +
519 + mrg_bench_print_summary(&summary);
520 +
521 + // Stop all threads
522 + fprintf(stderr, "Stopping threads...\n");
523 + for(int i = 0; i < MRG_BENCH_MAX_THREADS; i++) {
524 + netdata_mutex_lock(&controls[i].cond_mutex);
525 + __atomic_store_n(&controls[i].run_flag, MRG_BENCH_STOP_SIGNAL, __ATOMIC_RELAXED);
526 + netdata_cond_signal(&controls[i].cond);
527 + netdata_mutex_unlock(&controls[i].cond_mutex);
528 + }
529 +
530 + fprintf(stderr, "Waiting for threads to exit...\n");
531 + for(int i = 0; i < MRG_BENCH_MAX_THREADS; i++) {
532 + nd_thread_join(contexts[i].thread);
533 + }
534 +
535 + // Cleanup
536 + for(int i = 0; i < MRG_BENCH_MAX_THREADS; i++) {
537 + netdata_cond_destroy(&controls[i].cond);
538 + netdata_mutex_destroy(&controls[i].cond_mutex);
539 + }
540 +
541 + mrg_metric_release(mrg, metric);
542 + mrg_destroy(mrg);
543
544 + fprintf(stderr, "All benchmark tests passed.\n");
545 return 0;
546 }
src/database/engine/mrg.c
+18 -3
@@ -5,9 +5,9 @@
5 struct aral_statistics mrg_aral_statistics;
6
7 // ----------------------------------------------------------------------------
8 -// public API
8 +// private helpers
9
10 -inline MRG *mrg_create(void) {
10 +static MRG *mrg_create_internal(bool load_from_db) {
11 MRG *mrg = callocz(1, sizeof(MRG));
12
13 for(size_t i = 0; i < _countof(mrg->index) ; i++) {
@@ -21,10 +21,25 @@ inline MRG *mrg_create(void) {
21 }
22 pulse_aral_register_statistics(&mrg_aral_statistics, "mrg");
23
24 - mrg_load(mrg);
24 + if(load_from_db)
25 + mrg_load(mrg);
26 +
27 return mrg;
28 }
29
30 +// ----------------------------------------------------------------------------
31 +// public API
32 +
33 +inline MRG *mrg_create(void) {
34 + return mrg_create_internal(true);
35 +}
36 +
37 +inline MRG *mrg_create_for_unittest(void) {
38 + // Skip mrg_load() to avoid pre-loaded metrics with writer counts
39 + // This allows deletion tests to work without interference from database metrics
40 + return mrg_create_internal(false);
41 +}
42 +
43 struct aral_statistics *mrg_aral_stats(void) {
44 return &mrg_aral_statistics;
45 }
src/database/engine/mrg.h
+3
@@ -41,6 +41,7 @@ struct mrg_statistics {
41 };
42
43 MRG *mrg_create(void);
44 +MRG *mrg_create_for_unittest(void);
45
46 // returns the number of metrics that were freed, but were still referenced
47 size_t mrg_destroy(MRG *mrg);
@@ -98,4 +99,6 @@ bool mrg_save(MRG *mrg);
99 bool mrg_load(MRG *mrg);
100 void mrg_metric_prepopulate_cleanup(MRG *mrg);
101
102 +int mrg_retention_benchmark(void);
103 +
104 #endif // DBENGINE_METRIC_H