master
c 546 lines 19.6 KB
Raw
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 }; // For stress test tiers
10
11 struct mrg_stress_entry {
12 nd_uuid_t uuid;
13 time_t after;
14 time_t before;
15 };
16
17 struct mrg_stress {
18 MRG *mrg;
19 bool stop;
20 size_t entries;
21 struct mrg_stress_entry *array;
22 size_t updates;
23 };
24
25 static void mrg_stress(void *ptr) {
26 struct mrg_stress *t = ptr;
27 MRG *mrg = t->mrg;
28
29 ssize_t start = 0;
30 ssize_t end = (ssize_t)t->entries;
31 ssize_t step = 1;
32
33 if(gettid_cached() % 2) {
34 start = (ssize_t)t->entries - 1;
35 end = -1;
36 step = -1;
37 }
38
39 while(!__atomic_load_n(&t->stop, __ATOMIC_RELAXED) && !nd_thread_signaled_to_cancel()) {
40 for (ssize_t i = start; i != end; i += step) {
41 struct mrg_stress_entry *e = &t->array[i];
42
43 time_t after = __atomic_sub_fetch(&e->after, 1, __ATOMIC_RELAXED);
44 time_t before = __atomic_add_fetch(&e->before, 1, __ATOMIC_RELAXED);
45
46 mrg_update_metric_retention_and_granularity_by_uuid(
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 }
51 }
52 }
53
54 int mrg_unittest(void) {
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;
60
61 nd_uuid_t test_uuid;
62 uuid_generate(test_uuid);
63 MRG_ENTRY entry = {
64 .uuid = &test_uuid,
65 .section = (Word_t)&test_ctx_0,
66 .first_time_s = 2,
67 .last_time_s = 3,
68 .latest_update_every_s = 4,
69 };
70 m1_t0 = mrg_metric_add_and_acquire(mrg, entry, &ret);
71 if(!ret)
72 fatal("DBENGINE METRIC: failed to add metric");
73
74 // add the same metric again
75 m2_t0 = mrg_metric_add_and_acquire(mrg, entry, &ret);
76 if(m2_t0 != m1_t0)
77 fatal("DBENGINE METRIC: adding the same metric twice, does not return the same pointer");
78 if(ret)
79 fatal("DBENGINE METRIC: managed to add the same metric twice");
80
81 m3_t0 = mrg_metric_get_and_acquire_by_uuid(mrg, entry.uuid, entry.section);
82 if(m3_t0 != m1_t0)
83 fatal("DBENGINE METRIC: cannot find the metric added");
84
85 // add the same metric again
86 m4_t0 = mrg_metric_add_and_acquire(mrg, entry, &ret);
87 if(m4_t0 != m1_t0)
88 fatal("DBENGINE METRIC: adding the same metric twice, does not return the same pointer");
89 if(ret)
90 fatal("DBENGINE METRIC: managed to add the same metric twice");
91
92 // add the same metric in another section
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);
97
98 // add the same metric again
99 m2_t1 = mrg_metric_add_and_acquire(mrg, entry, &ret);
100 if(m2_t1 != m1_t1)
101 fatal("DBENGINE METRIC: adding the same metric twice (section %zu), does not return the same pointer", (size_t)entry.section);
102 if(ret)
103 fatal("DBENGINE METRIC: managed to add the same metric twice in (section 0)");
104
105 m3_t1 = mrg_metric_get_and_acquire_by_uuid(mrg, entry.uuid, entry.section);
106 if(m3_t1 != m1_t1)
107 fatal("DBENGINE METRIC: cannot find the metric added (section %zu)", (size_t)entry.section);
108
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);
120 mrg_metric_release(mrg, m1_t0);
121
122 mrg_metric_release(mrg, m2_t1);
123 mrg_metric_release(mrg, m3_t1);
124 mrg_metric_release(mrg, m4_t1);
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;
131 fprintf(stderr, "preparing stress test of %zu entries...\n", entries);
132 struct mrg_stress t = {
133 .mrg = mrg,
134 .entries = entries,
135 .array = callocz(entries, sizeof(struct mrg_stress_entry)),
136 };
137
138 time_t now = max_acceptable_collected_time();
139 for(size_t i = 0; i < entries ;i++) {
140 uuid_generate_random(t.array[i].uuid);
141 t.array[i].after = now / 3;
142 t.array[i].before = now / 2;
143 }
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(
149 mrg, (Word_t)&test_ctx_tier[tier],
150 &e->uuid,
151 e->after,
152 e->before,
153 1,
154 e->before, NULL);
155 }
156 }
157 fprintf(stderr, "stress test ready to run...\n");
158
159 usec_t started_ut = now_monotonic_usec();
160
161 ND_THREAD *th[threads];
162 for(size_t i = 0; i < threads ; i++) {
163 char buf[15 + 1];
164 snprintfz(buf, sizeof(buf) - 1, "TH[%zu]", i);
165 th[i] = nd_thread_create(buf, NETDATA_THREAD_OPTION_DONT_LOG, mrg_stress, &t);
166 }
167
168 sleep_usec(run_for_secs * USEC_PER_SEC);
169 __atomic_store_n(&t.stop, true, __ATOMIC_RELAXED);
170
171 for(size_t i = 0; i < threads ; i++)
172 nd_thread_signal_cancel(th[i]);
173
174 for(size_t i = 0; i < threads ; i++)
175 nd_thread_join(th[i]);
176
177 usec_t ended_ut = now_monotonic_usec();
178
179 struct mrg_statistics stats;
180 mrg_get_statistics(mrg, &stats);
181
182 fprintf(stderr, "DBENGINE METRIC: did %zu additions, %zu duplicate additions, "
183 "%zu deletions, %zu wrong deletions, "
184 "%zu successful searches, %zu wrong searches, "
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
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
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, %zd 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
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 }