master
c 2,108 lines 76.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "rrd.h"
4
5 // Key OF HS ARRRAY
6
7 struct {
8 int count;
9 Pvoid_t JudyHS;
10 SPINLOCK spinlock;
11 } global_labels = {
12 .JudyHS = (Pvoid_t) NULL,
13 .spinlock = SPINLOCK_INITIALIZER};
14
15 typedef struct label_registry_idx {
16 STRING *key;
17 STRING *value;
18 } LABEL_REGISTRY_IDX;
19
20 typedef struct labels_registry_entry {
21 LABEL_REGISTRY_IDX index;
22 } RRDLABEL;
23
24 // Value of HS array
25 typedef struct labels_registry_idx_entry {
26 RRDLABEL label;
27 size_t refcount;
28 } RRDLABEL_IDX;
29
30 typedef struct rrdlabels {
31 SPINLOCK spinlock;
32 uint32_t version;
33 Pvoid_t JudyL;
34 } RRDLABELS;
35
36 #define lfe_start_nolock(label_list, label, ls) \
37 do { \
38 bool _first_then_next = true; \
39 Pvoid_t *_PValue; \
40 Word_t _Index = 0; \
41 while ((_PValue = JudyLFirstThenNext((label_list)->JudyL, &_Index, &_first_then_next))) { \
42 (ls) = *(RRDLABEL_SRC *)_PValue; \
43 (void)(ls); \
44 (label) = (void *)_Index;
45
46 #define lfe_done_nolock() \
47 } \
48 } \
49 while (0)
50
51 #define lfe_start_read(label_list, label, ls) \
52 do { \
53 spinlock_lock(&(label_list)->spinlock); \
54 bool _first_then_next = true; \
55 Pvoid_t *_PValue; \
56 Word_t _Index = 0; \
57 while ((_PValue = JudyLFirstThenNext((label_list)->JudyL, &_Index, &_first_then_next))) { \
58 (ls) = *(RRDLABEL_SRC *)_PValue; \
59 (void)(ls); \
60 (label) = (void *)_Index;
61
62 #define lfe_done(label_list) \
63 } \
64 spinlock_unlock(&(label_list)->spinlock); \
65 } \
66 while (0)
67
68 static inline void RRDLABELS_MEMORY_DELTA(struct dictionary_stats *stats, int64_t judy_mem, int64_t item_size) {
69 __atomic_fetch_add(&stats->memory.index, judy_mem, __ATOMIC_RELAXED);
70 __atomic_fetch_add(&stats->memory.dict, item_size, __ATOMIC_RELAXED);
71 }
72
73 __attribute__((constructor)) void initialize_label_stats(void) {
74 dictionary_stats_category_rrdlabels.memory.dict = 0;
75 dictionary_stats_category_rrdlabels.memory.index = 0;
76 dictionary_stats_category_rrdlabels.memory.values = 0;
77 }
78
79
80 static ARAL *labels_aral;
81
82 static struct aral_statistics label_aral_statistics = { 0 };
83
84 void rrdlabels_aral_init(bool with_stats)
85 {
86 labels_aral =
87 aral_create("label_stat", sizeof(RRDLABELS), 1, 0, &label_aral_statistics, NULL, NULL, false, false, false);
88
89 if (with_stats)
90 pulse_aral_register_statistics(&label_aral_statistics, "labels");
91 }
92
93 void rrdlabels_aral_destroy(bool with_stats)
94 {
95 aral_destroy(labels_aral);
96 if (with_stats)
97 pulse_aral_unregister_statistics(&label_aral_statistics);
98 }
99
100
101 // ----------------------------------------------------------------------------
102 // rrdlabels_create()
103
104 RRDLABELS *rrdlabels_create(void)
105 {
106 RRDLABELS *labels = aral_mallocz(labels_aral);
107 spinlock_init(&labels->spinlock);
108 labels->version = 0;
109 labels->JudyL = NULL;
110 return labels;
111 }
112
113 static void dup_label(RRDLABEL *label_index)
114 {
115 if (!label_index)
116 return;
117
118 spinlock_lock(&global_labels.spinlock);
119
120 Pvoid_t *PValue = JudyHSGet(global_labels.JudyHS, (void *)label_index, sizeof(*label_index));
121 if (PValue && *PValue) {
122 RRDLABEL_IDX *rrdlabel = *PValue;
123 __atomic_add_fetch(&rrdlabel->refcount, 1, __ATOMIC_RELAXED);
124 }
125
126 spinlock_unlock(&global_labels.spinlock);
127 }
128
129 static RRDLABEL *add_label_name_value(const char *name, const char *value)
130 {
131 RRDLABEL_IDX *rrdlabel = NULL;
132 LABEL_REGISTRY_IDX label_index;
133 label_index.key = string_strdupz(name);
134 label_index.value = string_strdupz(value);
135
136 spinlock_lock(&global_labels.spinlock);
137
138 JudyAllocThreadPulseReset();
139
140 Pvoid_t *PValue = JudyHSIns(&global_labels.JudyHS, (void *)&label_index, sizeof(label_index), PJE0);
141
142 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
143
144 if(unlikely(!PValue || PValue == PJERR))
145 fatal("RRDLABELS: corrupted judyHS array");
146
147 if (*PValue) {
148 rrdlabel = *PValue;
149 string_freez(label_index.key);
150 string_freez(label_index.value);
151 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
152 } else {
153 rrdlabel = callocz(1, sizeof(*rrdlabel));
154 rrdlabel->label.index = label_index;
155 *PValue = rrdlabel;
156 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, sizeof(RRDLABEL_IDX));
157 global_labels.count++;
158 }
159 __atomic_add_fetch(&rrdlabel->refcount, 1, __ATOMIC_RELAXED);
160
161 spinlock_unlock(&global_labels.spinlock);
162 return &rrdlabel->label;
163 }
164
165 static void delete_label(RRDLABEL *label)
166 {
167 spinlock_lock(&global_labels.spinlock);
168
169 Pvoid_t *PValue = JudyHSGet(global_labels.JudyHS, &label->index, sizeof(label->index));
170 if (PValue && *PValue) {
171 RRDLABEL_IDX *rrdlabel = *PValue;
172 size_t refcount = __atomic_sub_fetch(&rrdlabel->refcount, 1, __ATOMIC_RELAXED);
173 if (refcount == 0) {
174 JudyAllocThreadPulseReset();
175
176 int rc = JudyHSDel(&global_labels.JudyHS, (void *)&label->index, sizeof(label->index), PJE0);
177 if(rc)
178 global_labels.count--;
179
180 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
181
182 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, -(int64_t)sizeof(*rrdlabel));
183 string_freez(label->index.key);
184 string_freez(label->index.value);
185 freez(rrdlabel);
186 }
187 }
188 spinlock_unlock(&global_labels.spinlock);
189 }
190
191 int rrdlabels_registry_count(void) {
192 spinlock_lock(&global_labels.spinlock);
193 int count = global_labels.count;
194 spinlock_unlock(&global_labels.spinlock);
195 return count;
196 }
197
198 // ----------------------------------------------------------------------------
199 // rrdlabels_destroy()
200
201 void rrdlabels_flush(RRDLABELS *labels) {
202 if (unlikely(!labels))
203 return;
204
205 spinlock_lock(&labels->spinlock);
206
207 Pvoid_t *PValue;
208 Word_t Index = 0;
209 bool first_then_next = true;
210 while ((PValue = JudyLFirstThenNext(labels->JudyL, &Index, &first_then_next))) {
211 delete_label((RRDLABEL *)Index);
212 }
213 JudyAllocThreadPulseReset();
214 JudyLFreeArray(&labels->JudyL, PJE0);
215 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
216 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
217 spinlock_unlock(&labels->spinlock);
218 }
219
220 void rrdlabels_destroy(RRDLABELS *labels)
221 {
222 if (unlikely(!labels))
223 return;
224
225 rrdlabels_flush(labels);
226 aral_freez(labels_aral, labels);
227 }
228
229 //
230 // Check in labels to see if we have the key specified in label
231 // same_value indicates if the value should also be matched
232 //
233 static RRDLABEL *rrdlabels_find_label_with_key_unsafe(RRDLABELS *labels, RRDLABEL *label, bool same_value)
234 {
235 if (unlikely(!labels))
236 return NULL;
237
238 Pvoid_t *PValue;
239 Word_t Index = 0;
240 bool first_then_next = true;
241 RRDLABEL *found = NULL;
242 while ((PValue = JudyLFirstThenNext(labels->JudyL, &Index, &first_then_next))) {
243 RRDLABEL *lb = (RRDLABEL *)Index;
244 if (lb->index.key == label->index.key && ((lb == label) == same_value)) {
245 found = (RRDLABEL *)Index;
246 break;
247 }
248 }
249 return found;
250 }
251
252 // ----------------------------------------------------------------------------
253 // rrdlabels_add()
254
255 static bool labels_add_already_sanitized(RRDLABELS *labels, const char *key, const char *value, RRDLABEL_SRC ls)
256 {
257 if (unlikely(!labels || !key)) return false;
258
259 RRDLABEL *new_label = add_label_name_value(key, value);
260
261 spinlock_lock(&labels->spinlock);
262
263 RRDLABEL_SRC new_ls = (ls & ~(RRDLABEL_FLAG_NEW | RRDLABEL_FLAG_OLD));
264
265 JudyAllocThreadPulseReset();
266
267 Pvoid_t *PValue = JudyLIns(&labels->JudyL, (Word_t)new_label, PJE0);
268 if (!PValue || PValue == PJERR)
269 fatal("RRDLABELS: corrupted labels JudyL array");
270
271 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
272 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
273
274 bool changed;
275 if(*PValue) {
276 new_ls |= RRDLABEL_FLAG_OLD;
277 *((RRDLABEL_SRC *)PValue) = new_ls;
278
279 delete_label(new_label);
280 changed = false;
281 }
282 else {
283 new_ls |= RRDLABEL_FLAG_NEW;
284 *((RRDLABEL_SRC *)PValue) = new_ls;
285
286 RRDLABEL *old_label_with_same_key = rrdlabels_find_label_with_key_unsafe(labels, new_label, false);
287 if (old_label_with_same_key) {
288 int del_result = JudyLDel(&labels->JudyL, (Word_t) old_label_with_same_key, PJE0);
289 (void)del_result;
290 judy_mem = JudyAllocThreadPulseGetAndReset();
291 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
292 delete_label(old_label_with_same_key);
293 }
294 changed = true;
295 }
296
297 labels->version++;
298
299 // judy_mem = JudyAllocThreadPulseGetAndReset();
300 // RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
301
302 spinlock_unlock(&labels->spinlock);
303
304 return changed;
305 }
306
307 void rrdlabels_add(RRDLABELS *labels, const char *name, const char *value, RRDLABEL_SRC ls)
308 {
309 (void)rrdlabels_add_changed(labels, name, value, ls);
310 }
311
312 bool rrdlabels_add_changed(RRDLABELS *labels, const char *name, const char *value, RRDLABEL_SRC ls)
313 {
314 if(!labels) {
315 netdata_log_error("%s(): called with NULL dictionary.", __FUNCTION__ );
316 return false;
317 }
318
319 char n[RRDLABELS_MAX_NAME_LENGTH + 1], v[RRDLABELS_MAX_VALUE_LENGTH + 1];
320 rrdlabels_sanitize_name(n, name, RRDLABELS_MAX_NAME_LENGTH);
321 rrdlabels_sanitize_value(v, value, RRDLABELS_MAX_VALUE_LENGTH);
322
323 if(!*n) {
324 netdata_log_error("%s: cannot add name '%s' (value '%s') which is sanitized as empty string", __FUNCTION__, name, value);
325 return false;
326 }
327
328 return labels_add_already_sanitized(labels, n, v, ls);
329 }
330
331 bool rrdlabels_exist(RRDLABELS *labels, const char *key)
332 {
333 if (!labels)
334 return false;
335
336 STRING *this_key = string_strdupz(key);
337
338 RRDLABEL *lb;
339 RRDLABEL_SRC ls;
340
341 bool found = false;
342 lfe_start_read(labels, lb, ls)
343 {
344 if (lb->index.key == this_key) {
345 found = true;
346 break;
347 }
348 }
349 lfe_done(labels);
350 string_freez(this_key);
351 return found;
352 }
353
354 static const char *get_quoted_string_up_to(char *dst, size_t dst_size, const char *string, char upto1, char upto2) {
355 size_t len = 0;
356 char *d = dst, quote = 0;
357 while(*string && len++ < dst_size) {
358 if(unlikely(!quote && (*string == '\'' || *string == '"'))) {
359 quote = *string++;
360 continue;
361 }
362
363 if(unlikely(quote && *string == quote)) {
364 quote = 0;
365 string++;
366 continue;
367 }
368
369 if(unlikely(quote && *string == '\\' && string[1])) {
370 string++;
371 *d++ = *string++;
372 continue;
373 }
374
375 if(unlikely(!quote && (*string == upto1 || *string == upto2))) break;
376
377 *d++ = *string++;
378 }
379 *d = '\0';
380
381 if(*string) string++;
382
383 return string;
384 }
385
386 void rrdlabels_add_pair(RRDLABELS *labels, const char *string, RRDLABEL_SRC ls)
387 {
388 if(!labels) {
389 netdata_log_error("%s(): called with NULL dictionary.", __FUNCTION__ );
390 return;
391 }
392
393 char name[RRDLABELS_MAX_NAME_LENGTH + 1];
394 string = get_quoted_string_up_to(name, RRDLABELS_MAX_NAME_LENGTH, string, '=', ':');
395
396 char value[RRDLABELS_MAX_VALUE_LENGTH + 1];
397 get_quoted_string_up_to(value, RRDLABELS_MAX_VALUE_LENGTH, string, '\0', '\0');
398
399 rrdlabels_add(labels, name, value, ls);
400 }
401
402 // ----------------------------------------------------------------------------
403
404 void rrdlabels_value_to_buffer_array_item_or_null(RRDLABELS *labels, BUFFER *wb, const char *key) {
405 if(!labels) return;
406
407 STRING *this_key = string_strdupz(key);
408
409 RRDLABEL *lb;
410 RRDLABEL_SRC ls;
411 lfe_start_read(labels, lb, ls)
412 {
413 if (lb->index.key == this_key) {
414 if (lb->index.value)
415 buffer_json_add_array_item_string(wb, string2str(lb->index.value));
416 else
417 buffer_json_add_array_item_string(wb, NULL);
418 break;
419 }
420 }
421 lfe_done(labels);
422 string_freez(this_key);
423 }
424
425 void rrdlabels_key_to_buffer_array_item(RRDLABELS *labels, BUFFER *wb)
426 {
427 if(!labels) return;
428
429 RRDLABEL *lb;
430 RRDLABEL_SRC ls;
431 lfe_start_read(labels, lb, ls) {
432 buffer_json_add_array_item_string(wb, string2str(lb->index.key));
433 }
434 lfe_done(labels);
435 }
436
437 void rrdlabels_key_to_buffer_array_or_string_or_null(RRDLABELS *labels, BUFFER *wb)
438 {
439 if(!labels) {
440 buffer_json_add_array_item_string(wb, NULL);
441 return;
442 }
443
444 size_t items = rrdlabels_entries(labels);
445 if(!items) {
446 buffer_json_add_array_item_string(wb, NULL);
447 return;
448 }
449
450 if(items > 1)
451 buffer_json_add_array_item_array(wb);
452
453 RRDLABEL *lb;
454 RRDLABEL_SRC ls;
455 lfe_start_read(labels, lb, ls) {
456 buffer_json_add_array_item_string(wb, string2str(lb->index.key));
457 if(items == 1)
458 break;
459 }
460 lfe_done(labels);
461
462 if(items > 1)
463 buffer_json_array_close(wb);
464
465 }
466
467 // ----------------------------------------------------------------------------
468
469 void rrdlabels_get_value_strcpyz(RRDLABELS *labels, char *dst, size_t dst_len, const char *key) {
470 if(!dst || !dst_len)
471 return;
472
473 // make sure the output is empty if we can't find it
474 *dst = '\0';
475
476 if(!labels)
477 return;
478
479 STRING *this_key = string_strdupz(key);
480
481 RRDLABEL *lb;
482 RRDLABEL_SRC ls;
483
484 lfe_start_read(labels, lb, ls)
485 {
486 if (lb->index.key == this_key) {
487 if (lb->index.value)
488 strncpyz(dst, string2str(lb->index.value), dst_len - 1);
489 else
490 dst[0] = '\0';
491 break;
492 }
493 }
494 lfe_done(labels);
495 string_freez(this_key);
496 }
497
498 void rrdlabels_get_value_strdup_or_null(RRDLABELS *labels, char **value, const char *key)
499 {
500 if(!labels) return;
501
502 STRING *this_key = string_strdupz(key);
503
504 RRDLABEL *lb;
505 RRDLABEL_SRC ls;
506 lfe_start_read(labels, lb, ls)
507 {
508 if (lb->index.key == this_key) {
509 *value = (lb->index.value) ? strdupz(string2str(lb->index.value)) : NULL;
510 break;
511 }
512 }
513 lfe_done(labels);
514 string_freez(this_key);
515 }
516
517 void rrdlabels_get_value_to_buffer_or_unset(RRDLABELS *labels, BUFFER *wb, const char *key, const char *unset)
518 {
519 if(!labels || !key || !wb) return;
520
521 STRING *this_key = string_strdupz(key);
522 RRDLABEL *lb;
523 RRDLABEL_SRC ls;
524
525 bool set = false;
526 lfe_start_read(labels, lb, ls)
527 {
528 if (lb->index.key == this_key) {
529 if (lb->index.value)
530 buffer_strcat(wb, string2str(lb->index.value));
531 else
532 buffer_strcat(wb, unset);
533 set = true;
534 break;
535 }
536 }
537 lfe_done(labels);
538
539 if(!set)
540 buffer_strcat(wb, unset);
541
542 string_freez(this_key);
543 }
544
545 static void rrdlabels_unmark_all_unsafe(RRDLABELS *labels)
546 {
547 Pvoid_t *PValue;
548 Word_t Index = 0;
549 bool first_then_next = true;
550 while ((PValue = JudyLFirstThenNext(labels->JudyL, &Index, &first_then_next)))
551 *((RRDLABEL_SRC *)PValue) &= ~(RRDLABEL_FLAG_OLD | RRDLABEL_FLAG_NEW);
552 }
553
554 void rrdlabels_unmark_all(RRDLABELS *labels)
555 {
556 spinlock_lock(&labels->spinlock);
557
558 rrdlabels_unmark_all_unsafe(labels);
559
560 spinlock_unlock(&labels->spinlock);
561 }
562
563 static size_t rrdlabels_remove_all_unmarked_unsafe(RRDLABELS *labels)
564 {
565 Pvoid_t *PValue;
566 Word_t Index = 0;
567 bool first_then_next = true;
568 size_t removed = 0;
569
570 while ((PValue = JudyLFirstThenNext(labels->JudyL, &Index, &first_then_next))) {
571 if (!((*((RRDLABEL_SRC *)PValue)) & (RRDLABEL_FLAG_INTERNAL))) {
572
573 JudyAllocThreadPulseReset();
574 (void)JudyLDel(&labels->JudyL, Index, PJE0);
575 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
576
577 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
578
579 delete_label((RRDLABEL *)Index);
580 removed++;
581 if (labels->JudyL != (Pvoid_t) NULL) {
582 Index = 0;
583 first_then_next = true;
584 }
585 }
586 }
587
588 return removed;
589 }
590
591 void rrdlabels_remove_all_unmarked(RRDLABELS *labels)
592 {
593 spinlock_lock(&labels->spinlock);
594 (void)rrdlabels_remove_all_unmarked_unsafe(labels);
595 spinlock_unlock(&labels->spinlock);
596 }
597
598 void rrdlabels_mark_source_as_old(RRDLABELS *labels, RRDLABEL_SRC src_match)
599 {
600 if (!labels) return;
601
602 Pvoid_t *PValue;
603 Word_t Index = 0;
604 bool first_then_next = true;
605
606 spinlock_lock(&labels->spinlock);
607
608 while ((PValue = JudyLFirstThenNext(labels->JudyL, &Index, &first_then_next))) {
609 if ((*((RRDLABEL_SRC *)PValue)) & src_match)
610 *((RRDLABEL_SRC *)PValue) |= RRDLABEL_FLAG_OLD;
611 }
612
613 spinlock_unlock(&labels->spinlock);
614 }
615
616 bool rrdlabels_remove_all_unmarked_and_changed(RRDLABELS *labels)
617 {
618 if(!labels) return false;
619
620 spinlock_lock(&labels->spinlock);
621
622 size_t added = 0;
623 Pvoid_t *PValue;
624 Word_t Index = 0;
625 bool first_then_next = true;
626 while ((PValue = JudyLFirstThenNext(labels->JudyL, &Index, &first_then_next))) {
627 if ((*((RRDLABEL_SRC *)PValue)) & RRDLABEL_FLAG_NEW)
628 added++;
629 }
630
631 size_t removed = rrdlabels_remove_all_unmarked_unsafe(labels);
632
633 spinlock_unlock(&labels->spinlock);
634
635 return (added > 0) || (removed > 0);
636 }
637
638 // ----------------------------------------------------------------------------
639 // rrdlabels_walkthrough_read()
640
641 int rrdlabels_walkthrough_read(RRDLABELS *labels, int (*callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *data)
642 {
643 int ret = 0;
644
645 if(unlikely(!labels || !callback)) return 0;
646
647 RRDLABEL *lb;
648 RRDLABEL_SRC ls;
649 lfe_start_read(labels, lb, ls)
650 {
651 ret = callback(string2str(lb->index.key), string2str(lb->index.value), ls, data);
652 if (ret < 0)
653 break;
654 }
655 lfe_done(labels);
656
657 return ret;
658 }
659
660 int rrdlabels_walkthrough_read_string(RRDLABELS *labels, int (*callback)(STRING *name, STRING *value, RRDLABEL_SRC ls, void *data), void *data)
661 {
662 int ret = 0;
663
664 if(unlikely(!labels || !callback)) return 0;
665
666 RRDLABEL *lb;
667 RRDLABEL_SRC ls;
668 lfe_start_read(labels, lb, ls)
669 {
670 ret = callback(lb->index.key, lb->index.value, ls, data);
671 if (ret < 0)
672 break;
673 }
674 lfe_done(labels);
675
676 return ret;
677 }
678
679 static SIMPLE_PATTERN_RESULT rrdlabels_walkthrough_read_sp(RRDLABELS *labels, SIMPLE_PATTERN_RESULT (*callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *data)
680 {
681 SIMPLE_PATTERN_RESULT ret = SP_NOT_MATCHED;
682
683 if(unlikely(!labels || !callback)) return 0;
684
685 RRDLABEL *lb;
686 RRDLABEL_SRC ls;
687 lfe_start_read(labels, lb, ls)
688 {
689 ret = callback(string2str(lb->index.key), string2str(lb->index.value), ls, data);
690 if (ret != SP_NOT_MATCHED)
691 break;
692 }
693 lfe_done(labels);
694
695 return ret;
696 }
697
698 // ----------------------------------------------------------------------------
699 // rrdlabels_migrate_to_these()
700 // migrate an existing label list to a new list
701
702 bool rrdlabels_migrate_to_these(RRDLABELS *dst, RRDLABELS *src) {
703 if (!dst || !src || (dst == src))
704 return false;
705
706 spinlock_lock(&dst->spinlock);
707 spinlock_lock(&src->spinlock);
708
709 rrdlabels_unmark_all_unsafe(dst);
710
711 RRDLABEL *label;
712 Pvoid_t *PValue;
713 size_t added = 0;
714 size_t cleaned = 0;
715
716 RRDLABEL_SRC ls;
717 lfe_start_nolock(src, label, ls)
718 {
719 JudyAllocThreadPulseGetAndReset();
720
721 // labels->JudyL is keyed by the deduplicated RRDLABEL pointer produced by
722 // add_label_name_value(), which encodes BOTH key and value. A same-key
723 // value change in src therefore yields a different pointer than the one
724 // dst already has, so JudyLIns lands on an empty slot.
725 PValue = JudyLIns(&dst->JudyL, (Word_t)label, PJE0);
726 if(unlikely(!PValue || PValue == PJERR))
727 fatal("RRDLABELS migrate: corrupted labels array");
728
729 if (!*PValue) {
730 // Write through PValue BEFORE any subsequent JudyLDel: Judy
731 // invalidates previously-returned PValue pointers when the array
732 // is modified. Same ordering as labels_add_already_sanitized().
733 *((RRDLABEL_SRC *)PValue) = (ls & ~(RRDLABEL_FLAG_OLD | RRDLABEL_FLAG_NEW)) | RRDLABEL_FLAG_NEW;
734 dup_label(label);
735 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
736 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
737 added++;
738 }
739 else
740 *((RRDLABEL_SRC *)PValue) |= RRDLABEL_FLAG_OLD;
741
742 // Ensure at most one entry per key. The remove-unmarked sweep below
743 // preserves RRDLABEL_FLAG_DONT_DELETE, so a stale (key, *) entry
744 // would otherwise survive next to the desired (key, value) entry.
745 // Runs in BOTH branches because the stale entry may pre-date this
746 // iteration (e.g. a duplicate left by a prior buggy path) and is
747 // independent of whether the current src label is a fresh insert
748 // or an already-present (key, value). The find helper skips the
749 // just-inserted/just-found entry via same_value=false.
750 for (;;) {
751 RRDLABEL *old_label_with_same_key = rrdlabels_find_label_with_key_unsafe(dst, label, false);
752 if (!old_label_with_same_key)
753 break;
754 int del_result = JudyLDel(&dst->JudyL, (Word_t)old_label_with_same_key, PJE0);
755 (void)del_result;
756 int64_t old_judy_mem = JudyAllocThreadPulseGetAndReset();
757 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, old_judy_mem, 0);
758 delete_label(old_label_with_same_key);
759 cleaned++;
760 }
761 }
762 lfe_done_nolock();
763
764 size_t removed = rrdlabels_remove_all_unmarked_unsafe(dst);
765 dst->version = src->version;
766
767 spinlock_unlock(&src->spinlock);
768 spinlock_unlock(&dst->spinlock);
769
770 // cleaned counts duplicates dropped by the same-key cleanup loop above.
771 // Without it, a stale (key,*) duplicate removed while the desired
772 // (key,value) was already present would mutate dst silently -- callers
773 // gating on this return (e.g. rrdset_update_rrdlabels setting
774 // RRDSET_FLAG_PENDING_LABEL_RECHECK) would miss the change.
775 return (added > 0) || (removed > 0) || (cleaned > 0);
776 }
777
778 //
779 //
780 // Return the common labels count in labels1, labels2
781 //
782 size_t rrdlabels_common_count(RRDLABELS *labels1, RRDLABELS *labels2)
783 {
784 if (!labels1 || !labels2)
785 return 0;
786
787 if (labels1 == labels2)
788 return rrdlabels_entries(labels1);
789
790 RRDLABEL *label;
791 RRDLABEL_SRC ls;
792
793 spinlock_lock(&labels1->spinlock);
794 spinlock_lock(&labels2->spinlock);
795
796 size_t count = 0;
797 lfe_start_nolock(labels2, label, ls)
798 {
799 RRDLABEL *old_label_with_key = rrdlabels_find_label_with_key_unsafe(labels1, label, true);
800 if (old_label_with_key)
801 count++;
802 }
803 lfe_done_nolock();
804
805 spinlock_unlock(&labels2->spinlock);
806 spinlock_unlock(&labels1->spinlock);
807 return count;
808 }
809
810
811 void rrdlabels_copy(RRDLABELS *dst, RRDLABELS *src)
812 {
813 if (!dst || !src || (dst == src))
814 return;
815
816 RRDLABEL *label;
817 RRDLABEL_SRC ls;
818
819 spinlock_lock(&dst->spinlock);
820 spinlock_lock(&src->spinlock);
821
822 JudyAllocThreadPulseReset();
823
824 bool update_statistics = false;
825 lfe_start_nolock(src, label, ls)
826 {
827 Pvoid_t *PValue = JudyLIns(&dst->JudyL, (Word_t)label, PJE0);
828 if(unlikely(!PValue || PValue == PJERR))
829 fatal("RRDLABELS: corrupted labels array");
830
831 if (!*PValue) {
832 // Write through PValue BEFORE any subsequent JudyLDel: Judy
833 // invalidates previously-returned PValue pointers when the array
834 // is modified. Same ordering as labels_add_already_sanitized().
835 *((RRDLABEL_SRC *)PValue) = (ls & ~(RRDLABEL_FLAG_OLD)) | RRDLABEL_FLAG_NEW;
836 dup_label(label);
837 dst->version++;
838 update_statistics = true;
839 }
840 else
841 *((RRDLABEL_SRC *)PValue) = (ls & ~(RRDLABEL_FLAG_NEW)) | RRDLABEL_FLAG_OLD;
842
843 // Drop any other entry sharing this key. Runs in BOTH branches so
844 // pre-existing same-key duplicates (e.g. left behind by a prior
845 // buggy path) get cleaned up even when the current src label is
846 // already present in dst. Loop because the find helper returns
847 // the first match only. The find skips the just-inserted /
848 // just-updated entry via same_value=false.
849 for (;;) {
850 RRDLABEL *old_label_with_key = rrdlabels_find_label_with_key_unsafe(dst, label, false);
851 if (!old_label_with_key)
852 break;
853 (void)JudyLDel(&dst->JudyL, (Word_t)old_label_with_key, PJE0);
854 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
855 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
856 delete_label((RRDLABEL *)old_label_with_key);
857 // Cleanup is itself a state mutation: bump version and request
858 // tail-stats accounting so version-based consumers and the
859 // memory pulse stay correct even when no new insert happened.
860 dst->version++;
861 update_statistics = true;
862 }
863 }
864 lfe_done_nolock();
865 if (update_statistics) {
866 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
867 RRDLABELS_MEMORY_DELTA(&dictionary_stats_category_rrdlabels, judy_mem, 0);
868 }
869
870 spinlock_unlock(&src->spinlock);
871 spinlock_unlock(&dst->spinlock);
872 }
873
874
875 // ----------------------------------------------------------------------------
876 // rrdlabels_match_simple_pattern()
877 // returns true when there are keys in the dictionary matching a simple pattern
878
879 struct simple_pattern_match_name_value {
880 size_t searches;
881 SIMPLE_PATTERN *pattern;
882 char equal;
883 };
884
885 static SIMPLE_PATTERN_RESULT simple_pattern_match_name_only_callback(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
886 struct simple_pattern_match_name_value *t = (struct simple_pattern_match_name_value *)data;
887 (void)value;
888
889 // we return -1 to stop the walkthrough on first match
890 t->searches++;
891 SIMPLE_PATTERN_RESULT ret = simple_pattern_matches_extract(t->pattern, name, NULL, 0);
892 if (ret == SP_MATCHED_NEGATIVE)
893 ret = SP_NOT_MATCHED;
894 return ret;
895 }
896
897 static SIMPLE_PATTERN_RESULT simple_pattern_match_name_and_value_callback(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
898 struct simple_pattern_match_name_value *t = (struct simple_pattern_match_name_value *)data;
899
900 // we return -1 to stop the walkthrough on first match
901 t->searches++;
902 if(simple_pattern_matches(t->pattern, name)) return -1;
903
904 char tmp[RRDLABELS_MAX_NAME_LENGTH + RRDLABELS_MAX_VALUE_LENGTH + 2], *dst = &tmp[0];
905 const char *v = value;
906
907 // copy the name
908 while(*name) *dst++ = *name++;
909
910 // add the equal
911 *dst++ = t->equal;
912
913 // add the value
914 while(*v) *dst++ = *v++;
915
916 // terminate it
917 *dst = '\0';
918
919 t->searches++;
920 return simple_pattern_matches_length_extract(t->pattern, tmp, dst - tmp, NULL, 0);
921 }
922
923 SIMPLE_PATTERN_RESULT rrdlabels_match_simple_pattern_parsed(RRDLABELS *labels, SIMPLE_PATTERN *pattern, char equal, size_t *searches) {
924 if (!labels) return false;
925
926 struct simple_pattern_match_name_value t = {
927 .searches = 0,
928 .pattern = pattern,
929 .equal = equal
930 };
931
932 SIMPLE_PATTERN_RESULT ret = rrdlabels_walkthrough_read_sp(labels, equal?simple_pattern_match_name_and_value_callback:simple_pattern_match_name_only_callback, &t);
933
934 if(searches)
935 *searches = t.searches;
936
937 return ret;
938 }
939
940 bool rrdlabels_match_simple_pattern(RRDLABELS *labels, const char *simple_pattern_txt) {
941 if (!labels) return false;
942
943 SIMPLE_PATTERN *pattern = simple_pattern_create(simple_pattern_txt, " ,|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
944 char equal = '\0';
945
946 const char *s;
947 for(s = simple_pattern_txt; *s ; s++) {
948 if (*s == '=' || *s == ':') {
949 equal = *s;
950 break;
951 }
952 }
953
954 SIMPLE_PATTERN_RESULT ret = rrdlabels_match_simple_pattern_parsed(labels, pattern, equal, NULL);
955
956 simple_pattern_free(pattern);
957
958 return ret == SP_MATCHED_POSITIVE;
959 }
960
961
962 // ----------------------------------------------------------------------------
963 // Log all labels
964
965 static int rrdlabels_log_label_to_buffer_callback(const char *name, const char *value, void *data) {
966 BUFFER *wb = (BUFFER *)data;
967
968 buffer_sprintf(wb, "Label: %s: \"%s\" (", name, value);
969 buffer_strcat(wb, "unknown");
970 buffer_strcat(wb, ")\n");
971
972 return 1;
973 }
974
975 void rrdlabels_log_to_buffer(RRDLABELS *labels, BUFFER *wb)
976 {
977 RRDLABEL *lb;
978 RRDLABEL_SRC ls;
979 lfe_start_read(labels, lb, ls)
980 rrdlabels_log_label_to_buffer_callback((void *) string2str(lb->index.key), (void *) string2str(lb->index.value), wb);
981 lfe_done(labels);
982 }
983
984
985 // ----------------------------------------------------------------------------
986 // rrdlabels_to_buffer()
987
988 struct labels_to_buffer {
989 BUFFER *wb;
990 bool (*filter_callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data);
991 void *filter_data;
992 void (*name_sanitizer)(char *dst, const char *src, size_t dst_size);
993 void (*value_sanitizer)(char *dst, const char *src, size_t dst_size);
994 const char *before_each;
995 const char *quote;
996 const char *equal;
997 const char *between_them;
998 size_t count;
999 };
1000
1001 static int label_to_buffer_callback(const RRDLABEL *lb, void *value __maybe_unused, RRDLABEL_SRC ls, void *data)
1002 {
1003
1004 struct labels_to_buffer *t = (struct labels_to_buffer *)data;
1005
1006 size_t n_size = (t->name_sanitizer ) ? ( RRDLABELS_MAX_NAME_LENGTH * 2 ) : 1;
1007 size_t v_size = (t->value_sanitizer) ? ( RRDLABELS_MAX_VALUE_LENGTH * 2 ) : 1;
1008
1009 char n[RRDLABELS_MAX_NAME_LENGTH * 2];
1010 char v[RRDLABELS_MAX_VALUE_LENGTH * 2];
1011
1012 const char *name = string2str(lb->index.key);
1013
1014 const char *nn = name, *vv = string2str(lb->index.value);
1015
1016 if(t->name_sanitizer) {
1017 t->name_sanitizer(n, name, n_size);
1018 nn = n;
1019 }
1020
1021 if(t->value_sanitizer) {
1022 t->value_sanitizer(v, string2str(lb->index.value), v_size);
1023 vv = v;
1024 }
1025
1026 if(!t->filter_callback || t->filter_callback(name, string2str(lb->index.value), ls, t->filter_data)) {
1027 buffer_sprintf(t->wb, "%s%s%s%s%s%s%s%s%s", t->count++?t->between_them:"", t->before_each, t->quote, nn, t->quote, t->equal, t->quote, vv, t->quote);
1028 return 1;
1029 }
1030
1031 return 0;
1032 }
1033
1034
1035 int label_walkthrough_read(RRDLABELS *labels, int (*callback)(const RRDLABEL *item, void *entry, RRDLABEL_SRC ls, void *data), void *data)
1036 {
1037 int ret = 0;
1038
1039 if(unlikely(!labels || !callback)) return 0;
1040
1041 RRDLABEL *lb;
1042 RRDLABEL_SRC ls;
1043 lfe_start_read(labels, lb, ls)
1044 {
1045 ret = callback((const RRDLABEL *)lb, (void *)string2str(lb->index.value), ls, data);
1046 if (ret < 0)
1047 break;
1048 }
1049 lfe_done(labels);
1050 return ret;
1051 }
1052
1053 int rrdlabels_to_buffer(RRDLABELS *labels, BUFFER *wb, const char *before_each, const char *equal, const char *quote, const char *between_them, bool (*filter_callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *filter_data, void (*name_sanitizer)(char *dst, const char *src, size_t dst_size), void (*value_sanitizer)(char *dst, const char *src, size_t dst_size)) {
1054 struct labels_to_buffer tmp = {
1055 .wb = wb,
1056 .filter_callback = filter_callback,
1057 .filter_data = filter_data,
1058 .name_sanitizer = name_sanitizer,
1059 .value_sanitizer = value_sanitizer,
1060 .before_each = before_each,
1061 .equal = equal,
1062 .quote = quote,
1063 .between_them = between_them,
1064 .count = 0
1065 };
1066 return label_walkthrough_read(labels, label_to_buffer_callback, (void *)&tmp);
1067 }
1068
1069 void rrdlabels_to_buffer_json_members(RRDLABELS *labels, BUFFER *wb)
1070 {
1071 RRDLABEL *lb;
1072 RRDLABEL_SRC ls;
1073 lfe_start_read(labels, lb, ls)
1074 buffer_json_member_add_string(wb, string2str(lb->index.key), string2str(lb->index.value));
1075 lfe_done(labels);
1076 }
1077
1078 size_t rrdlabels_entries(RRDLABELS *labels __maybe_unused)
1079 {
1080 if (unlikely(!labels))
1081 return 0;
1082
1083 size_t count;
1084 spinlock_lock(&labels->spinlock);
1085 count = JudyLCount(labels->JudyL, 0, -1, PJE0);
1086 spinlock_unlock(&labels->spinlock);
1087 return count;
1088 }
1089
1090 uint32_t rrdlabels_version(RRDLABELS *labels __maybe_unused)
1091 {
1092 if (unlikely(!labels))
1093 return 0;
1094
1095 return labels->version;
1096 }
1097
1098 void rrdset_update_rrdlabels(RRDSET *st, RRDLABELS *new_rrdlabels) {
1099 bool labels_changed = false;
1100
1101 if(!st->rrdlabels)
1102 st->rrdlabels = rrdlabels_create();
1103
1104 if (new_rrdlabels)
1105 labels_changed = rrdlabels_migrate_to_these(st->rrdlabels, new_rrdlabels);
1106
1107 rrdset_flag_set(st, RRDSET_FLAG_METADATA_UPDATE);
1108 rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
1109 rrdset_metadata_updated(st);
1110
1111 if(labels_changed) {
1112 rrdset_flag_set(st, RRDSET_FLAG_PENDING_LABEL_RECHECK);
1113 rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION);
1114 }
1115 }
1116
1117 // ----------------------------------------------------------------------------
1118 // rrdlabels unit test
1119
1120 struct rrdlabels_unittest_add_a_pair {
1121 const char *pair;
1122 const char *expected_name;
1123 const char *expected_value;
1124 const char *name;
1125 const char *value;
1126 int errors;
1127 };
1128
1129 RRDLABEL *rrdlabels_find_label_with_key(RRDLABELS *labels, const char *key, RRDLABEL_SRC *source)
1130 {
1131 if (!labels || !key)
1132 return NULL;
1133
1134 STRING *this_key = string_strdupz(key);
1135
1136 RRDLABEL *lb = NULL;
1137 RRDLABEL_SRC ls;
1138
1139 lfe_start_read(labels, lb, ls)
1140 {
1141 if (lb->index.key == this_key) {
1142 if (source)
1143 *source = ls;
1144 break;
1145 }
1146 }
1147 lfe_done(labels);
1148 string_freez(this_key);
1149 return lb;
1150 }
1151
1152 static int rrdlabels_unittest_add_a_pair_callback(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
1153 struct rrdlabels_unittest_add_a_pair *t = (struct rrdlabels_unittest_add_a_pair *)data;
1154
1155 t->name = name;
1156 t->value = value;
1157
1158 if(strcmp(name, t->expected_name) != 0) {
1159 fprintf(stderr, "name is wrong, found \"%s\", expected \"%s\"", name, t->expected_name);
1160 t->errors++;
1161 }
1162
1163 if(value == NULL && t->expected_value == NULL) {
1164 ;
1165 }
1166 else if(value == NULL || t->expected_value == NULL) {
1167 fprintf(stderr, "value is wrong, found \"%s\", expected \"%s\"", value?value:"(null)", t->expected_value?t->expected_value:"(null)");
1168 t->errors++;
1169 }
1170 else if(strcmp(value, t->expected_value) != 0) {
1171 fprintf(stderr, "values don't match, found \"%s\", expected \"%s\"", value, t->expected_value);
1172 t->errors++;
1173 }
1174
1175 return 1;
1176 }
1177
1178 static int rrdlabels_unittest_add_a_pair(const char *pair, const char *name, const char *value) {
1179 RRDLABELS *labels = rrdlabels_create();
1180 int errors;
1181
1182 fprintf(stderr, "rrdlabels_add_pair(labels, %s) ... ", pair);
1183
1184 rrdlabels_add_pair(labels, pair, RRDLABEL_SRC_CONFIG);
1185
1186 struct rrdlabels_unittest_add_a_pair tmp = {
1187 .pair = pair,
1188 .expected_name = name,
1189 .expected_value = value,
1190 .errors = 0
1191 };
1192 int ret = rrdlabels_walkthrough_read(labels, rrdlabels_unittest_add_a_pair_callback, &tmp);
1193 errors = tmp.errors;
1194 if(ret != 1) {
1195 fprintf(stderr, "failed to get \"%s\" label", name);
1196 errors++;
1197 }
1198
1199 if(!errors)
1200 fprintf(stderr, " OK, name='%s' and value='%s'\n", tmp.name, tmp.value?tmp.value:"(null)");
1201 else
1202 fprintf(stderr, " FAILED\n");
1203
1204 rrdlabels_destroy(labels);
1205 return errors;
1206 }
1207
1208 static int rrdlabels_unittest_add_pairs() {
1209 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1210
1211 int errors = 0;
1212
1213 // basic test
1214 errors += rrdlabels_unittest_add_a_pair("tag=value", "tag", "value");
1215 errors += rrdlabels_unittest_add_a_pair("tag:value", "tag", "value");
1216
1217 // test newlines
1218 errors += rrdlabels_unittest_add_a_pair(" tag = \t value \r\n", "tag", "value");
1219
1220 // test spaces in names
1221 errors += rrdlabels_unittest_add_a_pair(" t a g = value", "t_a_g", "value");
1222
1223 // test : in values
1224 errors += rrdlabels_unittest_add_a_pair("tag=:value", "tag", ":value");
1225 errors += rrdlabels_unittest_add_a_pair("tag::value", "tag", ":value");
1226 errors += rrdlabels_unittest_add_a_pair(" tag = :value ", "tag", ":value");
1227 errors += rrdlabels_unittest_add_a_pair(" tag : :value ", "tag", ":value");
1228 errors += rrdlabels_unittest_add_a_pair("tag:5", "tag", "5");
1229 errors += rrdlabels_unittest_add_a_pair("tag:55", "tag", "55");
1230 errors += rrdlabels_unittest_add_a_pair("tag:aa", "tag", "aa");
1231 errors += rrdlabels_unittest_add_a_pair("tag:a", "tag", "a");
1232
1233 // test empty values
1234 errors += rrdlabels_unittest_add_a_pair("tag", "tag", "[none]");
1235 errors += rrdlabels_unittest_add_a_pair("tag:", "tag", "[none]");
1236 errors += rrdlabels_unittest_add_a_pair("tag:\"\"", "tag", "[none]");
1237 errors += rrdlabels_unittest_add_a_pair("tag:''", "tag", "[none]");
1238 errors += rrdlabels_unittest_add_a_pair("tag:\r\n", "tag", "[none]");
1239 errors += rrdlabels_unittest_add_a_pair("tag\r\n", "tag", "[none]");
1240
1241 // test UTF-8 in values
1242 errors += rrdlabels_unittest_add_a_pair("tag: country:Ελλάδα", "tag", "country:Ελλάδα");
1243 errors += rrdlabels_unittest_add_a_pair("\"tag\": \"country:Ελλάδα\"", "tag", "country:Ελλάδα");
1244 errors += rrdlabels_unittest_add_a_pair("\"tag\": country:\"Ελλάδα\"", "tag", "country:Ελλάδα");
1245 errors += rrdlabels_unittest_add_a_pair("\"tag=1\": country:\"Gre\\\"ece\"", "tag_1", "country:Gre_ece");
1246 errors += rrdlabels_unittest_add_a_pair("\"tag=1\" = country:\"Gre\\\"ece\"", "tag_1", "country:Gre_ece");
1247
1248 errors += rrdlabels_unittest_add_a_pair("\t'LABE=L'\t=\t\"World\" peace", "LABE_L", "World peace");
1249 errors += rrdlabels_unittest_add_a_pair("\t'LA\\'B:EL'\t=\tcountry:\"World\":\"Europe\":\"Greece\"", "LA_B_EL", "country:World:Europe:Greece");
1250 errors += rrdlabels_unittest_add_a_pair("\t'LA\\'B:EL'\t=\tcountry\\\"World\"\\\"Europe\"\\\"Greece\"", "LA_B_EL", "country/World/Europe/Greece");
1251
1252 errors += rrdlabels_unittest_add_a_pair("NAME=\"VALUE\"", "NAME", "VALUE");
1253 errors += rrdlabels_unittest_add_a_pair("\"NAME\" : \"VALUE\"", "NAME", "VALUE");
1254 errors += rrdlabels_unittest_add_a_pair("NAME: \"VALUE\"", "NAME", "VALUE");
1255
1256 return errors;
1257 }
1258
1259 static int rrdlabels_unittest_expect_value(RRDLABELS *labels, const char *key, const char *value, RRDLABEL_SRC required_source)
1260 {
1261 RRDLABEL_SRC source;
1262 RRDLABEL *label = rrdlabels_find_label_with_key(labels, key, &source);
1263 return (!label || strcmp(string2str(label->index.value), value) != 0 || (source != required_source));
1264 }
1265
1266 static int rrdlabels_unittest_double_check()
1267 {
1268 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1269
1270 int ret = 0;
1271 RRDLABELS *labels = rrdlabels_create();
1272
1273 rrdlabels_add(labels, "key1", "value1", RRDLABEL_SRC_CONFIG);
1274 ret += rrdlabels_unittest_expect_value(labels, "key1", "value1", RRDLABEL_FLAG_NEW | RRDLABEL_SRC_CONFIG);
1275
1276 rrdlabels_add(labels, "key1", "value2", RRDLABEL_SRC_CONFIG);
1277 ret += !rrdlabels_unittest_expect_value(labels, "key1", "value2", RRDLABEL_FLAG_OLD | RRDLABEL_SRC_CONFIG);
1278
1279 rrdlabels_add(labels, "key2", "value1", RRDLABEL_SRC_ACLK|RRDLABEL_SRC_AUTO);
1280 ret += !rrdlabels_unittest_expect_value(labels, "key1", "value3", RRDLABEL_FLAG_NEW | RRDLABEL_SRC_ACLK);
1281
1282 ret += (rrdlabels_entries(labels) != 2);
1283
1284 rrdlabels_destroy(labels);
1285
1286 if (ret)
1287 fprintf(stderr, "\n%s() tests failed\n", __FUNCTION__);
1288 return ret;
1289 }
1290
1291 static int rrdlabels_walkthrough_index_read(RRDLABELS *labels, int (*callback)(const char *name, const char *value, RRDLABEL_SRC ls, size_t index, void *data), void *data)
1292 {
1293 int ret = 0;
1294
1295 if(unlikely(!labels || !callback)) return 0;
1296
1297 RRDLABEL *lb;
1298 RRDLABEL_SRC ls;
1299 size_t index = 0;
1300 lfe_start_read(labels, lb, ls)
1301 {
1302 ret = callback(string2str(lb->index.key), string2str(lb->index.value), ls, index, data);
1303 if (ret < 0)
1304 break;
1305
1306 index++;
1307 }
1308 lfe_done(labels);
1309
1310 return ret;
1311 }
1312
1313 static int unittest_dump_labels(const char *name, const char *value, RRDLABEL_SRC ls, size_t index, void *data __maybe_unused)
1314 {
1315 if (!index && data) {
1316 fprintf(stderr, "%s\n", (char *) data);
1317 }
1318 fprintf(stderr, "LABEL \"%s\" = %d \"%s\"\n", name, ls & (~RRDLABEL_FLAG_INTERNAL), value);
1319 return 1;
1320 }
1321
1322 void pattern_array_add_lblkey_with_sp(struct pattern_array *pa, const char *key, SIMPLE_PATTERN *sp);
1323 static int rrdlabels_unittest_pattern_check()
1324 {
1325 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1326 int rc = 0;
1327
1328 RRDLABELS *labels = NULL;
1329
1330 labels = rrdlabels_create();
1331
1332 rrdlabels_add(labels, "_module", "disk_detection", RRDLABEL_SRC_CONFIG);
1333 rrdlabels_add(labels, "_plugin", "super_plugin", RRDLABEL_SRC_CONFIG);
1334 rrdlabels_add(labels, "key1", "value1", RRDLABEL_SRC_CONFIG);
1335 rrdlabels_add(labels, "key2", "caterpillar", RRDLABEL_SRC_CONFIG);
1336 rrdlabels_add(labels, "key3", "elephant", RRDLABEL_SRC_CONFIG);
1337 rrdlabels_add(labels, "key4", "value4", RRDLABEL_SRC_CONFIG);
1338
1339 bool match;
1340 struct pattern_array *pa = pattern_array_add_key_value(NULL, "_module", "wrong_module", '=');
1341 match = pattern_array_label_match(pa, labels, '=', NULL);
1342 // This should not match: _module in ("wrong_module")
1343 if (match)
1344 rc++;
1345
1346 pattern_array_add_key_value(pa, "_module", "disk_detection", '=');
1347 match = pattern_array_label_match(pa, labels, '=', NULL);
1348 // This should match: _module in ("wrong_module","disk_detection")
1349 if (!match)
1350 rc++;
1351
1352 pattern_array_add_key_value(pa, "key1", "wrong_key1_value", '=');
1353 match = pattern_array_label_match(pa, labels, '=', NULL);
1354 // This should not match: _module in ("wrong_module","disk_detection") AND key1 in ("wrong_key1_value")
1355 if (match)
1356 rc++;
1357
1358 pattern_array_add_key_value(pa, "key1", "value1", '=');
1359 match = pattern_array_label_match(pa, labels, '=', NULL);
1360 // This should match: _module in ("wrong_module","disk_detection") AND key1 in ("wrong_key1_value", "value1")
1361 if (!match)
1362 rc++;
1363
1364 SIMPLE_PATTERN *sp = simple_pattern_create("key2=cat*,!d*", SIMPLE_PATTERN_DEFAULT_WEB_SEPARATORS, SIMPLE_PATTERN_EXACT, true);
1365 pattern_array_add_lblkey_with_sp(pa, "key2", sp);
1366
1367 sp = simple_pattern_create("key3=*phant", SIMPLE_PATTERN_DEFAULT_WEB_SEPARATORS, SIMPLE_PATTERN_EXACT, true);
1368 pattern_array_add_lblkey_with_sp(pa, "key3", sp);
1369
1370 match = pattern_array_label_match(pa, labels, '=', NULL);
1371 // This should match: _module in ("wrong_module","disk_detection") AND key1 in ("wrong_key1_value", "value1") AND key2 in ("cat* !d*") AND key3 in ("*phant")
1372 if (!match)
1373 rc++;
1374
1375 rrdlabels_add(labels, "key3", "now_fail", RRDLABEL_SRC_CONFIG);
1376 match = pattern_array_label_match(pa, labels, '=', NULL);
1377 // This should not match: _module in ("wrong_module","disk_detection") AND key1 in ("wrong_key1_value", "value1") AND key2 in ("cat* !d*") AND key3 in ("*phant")
1378 if (match)
1379 rc++;
1380
1381 pattern_array_free(pa);
1382 rrdlabels_destroy(labels);
1383
1384 return rc;
1385 }
1386
1387 // --------------------------------------------------------------------------------------------------------------------
1388 // Full text search for labels
1389
1390 #include "rrdlabels-aggregated.h"
1391
1392 struct label_full_text_search_data {
1393 SIMPLE_PATTERN *pattern;
1394 RRDLABELS_AGGREGATED *agg;
1395 size_t searches;
1396 bool found;
1397 };
1398
1399 static int label_full_text_search_callback_string(STRING *name, STRING *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
1400 struct label_full_text_search_data *d = (struct label_full_text_search_data *)data;
1401
1402 // Now we have STRING* directly, no need to create them
1403 bool key_matches = false;
1404 bool value_matches = false;
1405
1406 // First try to match the key
1407 if(simple_pattern_matches_string(d->pattern, name)) {
1408 key_matches = true;
1409 d->searches++;
1410 }
1411
1412 // Then try to match the value
1413 if(simple_pattern_matches_string(d->pattern, value)) {
1414 value_matches = true;
1415 d->searches++;
1416 }
1417
1418 // If either matched, add this label to the aggregated structure
1419 if(key_matches || value_matches) {
1420 if(!d->agg) {
1421 d->agg = rrdlabels_aggregated_create();
1422 }
1423 rrdlabels_aggregated_add_label(d->agg, string2str(name), string2str(value));
1424 d->found = true;
1425 }
1426
1427 return 0; // Continue walking through labels
1428 }
1429
1430 RRDLABELS_AGGREGATED *rrdlabels_full_text_search(RRDLABELS *labels, SIMPLE_PATTERN *pattern, RRDLABELS_AGGREGATED *agg, size_t *searches) {
1431 if(!labels || !pattern) return agg;
1432
1433 struct label_full_text_search_data data = {
1434 .pattern = pattern,
1435 .agg = agg,
1436 .searches = 0,
1437 .found = false
1438 };
1439
1440 rrdlabels_walkthrough_read_string(labels, label_full_text_search_callback_string, &data);
1441
1442 if(searches)
1443 *searches = data.searches;
1444
1445 // If we found matches and didn't have an agg structure, one was created
1446 // If we had an agg structure, we added to it (or not if no matches)
1447 // If no matches and no initial agg, data.agg is still NULL
1448 return data.agg;
1449 }
1450
1451 // --------------------------------------------------------------------------------------------------------------------
1452 // unittest
1453
1454 static int rrdlabels_unittest_migrate_check()
1455 {
1456 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1457
1458 RRDLABELS *labels1 = NULL;
1459 RRDLABELS *labels2 = NULL;
1460
1461 labels1 = rrdlabels_create();
1462 labels2 = rrdlabels_create();
1463
1464 rrdlabels_add(labels1, "key1", "value1", RRDLABEL_SRC_CONFIG);
1465 rrdlabels_add(labels1, "key1", "value2", RRDLABEL_SRC_CONFIG);
1466
1467 rrdlabels_add(labels2, "new_key1", "value2", RRDLABEL_SRC_CONFIG);
1468 rrdlabels_add(labels2, "new_key2", "value2", RRDLABEL_SRC_CONFIG);
1469 rrdlabels_add(labels2, "key1", "value2", RRDLABEL_SRC_CONFIG);
1470
1471 fprintf(stderr, "Labels1 entries found %zu (should be 1)\n", rrdlabels_entries(labels1));
1472 fprintf(stderr, "Labels2 entries found %zu (should be 3)\n", rrdlabels_entries(labels2));
1473
1474 rrdlabels_migrate_to_these(labels1, labels2);
1475
1476 int rc = 0;
1477 rc = rrdlabels_unittest_expect_value(labels1, "key1", "value2", RRDLABEL_FLAG_OLD | RRDLABEL_SRC_CONFIG);
1478 if (rc) {
1479 rrdlabels_destroy(labels1);
1480 rrdlabels_destroy(labels2);
1481 return rc;
1482 }
1483
1484 fprintf(stderr, "labels1 (migrated) entries found %zu (should be 3)\n", rrdlabels_entries(labels1));
1485 size_t entries = rrdlabels_entries(labels1);
1486
1487 rrdlabels_destroy(labels1);
1488 rrdlabels_destroy(labels2);
1489
1490 if (entries != 3)
1491 return 1;
1492
1493 // Copy test
1494 labels1 = rrdlabels_create();
1495 labels2 = rrdlabels_create();
1496
1497 rrdlabels_add(labels1, "key1", "value1", RRDLABEL_SRC_CONFIG);
1498 rrdlabels_add(labels1, "key2", "value2", RRDLABEL_SRC_CONFIG);
1499 rrdlabels_add(labels1, "key3", "value3", RRDLABEL_SRC_CONFIG);
1500 rrdlabels_add(labels1, "key4", "value4", RRDLABEL_SRC_CONFIG); // 4 keys
1501 rrdlabels_walkthrough_index_read(labels1, unittest_dump_labels, "\nlabels1");
1502
1503 rrdlabels_add(labels2, "key0", "value0", RRDLABEL_SRC_CONFIG);
1504 rrdlabels_add(labels2, "key1", "value1", RRDLABEL_SRC_CONFIG);
1505 rrdlabels_add(labels2, "key2", "value2", RRDLABEL_SRC_CONFIG);
1506
1507 rc = rrdlabels_unittest_expect_value(labels1, "key1", "value1", RRDLABEL_FLAG_NEW | RRDLABEL_SRC_CONFIG);
1508 if (rc) {
1509 rrdlabels_destroy(labels1);
1510 rrdlabels_destroy(labels2);
1511 return rc;
1512 }
1513
1514 rrdlabels_walkthrough_index_read(labels2, unittest_dump_labels, "\nlabels2");
1515
1516 rrdlabels_copy(labels1, labels2); // labels1 should have 5 keys
1517 rc = rrdlabels_unittest_expect_value(labels1, "key1", "value1", RRDLABEL_FLAG_OLD | RRDLABEL_SRC_CONFIG);
1518 if (rc) {
1519 rrdlabels_destroy(labels1);
1520 rrdlabels_destroy(labels2);
1521 return rc;
1522 }
1523
1524 rc = rrdlabels_unittest_expect_value(labels1, "key0", "value0", RRDLABEL_FLAG_NEW | RRDLABEL_SRC_CONFIG);
1525 if (rc) {
1526 rrdlabels_destroy(labels1);
1527 rrdlabels_destroy(labels2);
1528 return rc;
1529 }
1530
1531 rrdlabels_walkthrough_index_read(labels1, unittest_dump_labels, "\nlabels1 after copy from labels2");
1532 entries = rrdlabels_entries(labels1);
1533
1534 fprintf(stderr, "labels1 (copied) entries found %zu (should be 5)\n", rrdlabels_entries(labels1));
1535 if (entries != 5) {
1536 rrdlabels_destroy(labels1);
1537 rrdlabels_destroy(labels2);
1538 return 1;
1539 }
1540
1541 rrdlabels_add(labels1, "key0", "value0", RRDLABEL_SRC_CONFIG);
1542 rc = rrdlabels_unittest_expect_value(labels1, "key0", "value0", RRDLABEL_FLAG_OLD | RRDLABEL_SRC_CONFIG);
1543
1544 rrdlabels_destroy(labels1);
1545 rrdlabels_destroy(labels2);
1546
1547 return rc;
1548 }
1549
1550 // Exercises the unmark + re-add + remove_all_unmarked cycle that
1551 // reload_host_labels() relies on, plus the rrdlabels_mark_source_as_old()
1552 // preservation path used when the kubernetes loader fails.
1553 static int rrdlabels_unittest_mark_source_as_old(void) {
1554 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1555 int errors = 0;
1556
1557 // Subtest A: the prune drops entries that no loader re-added.
1558 {
1559 RRDLABELS *l = rrdlabels_create();
1560 rrdlabels_add(l, "cfg_key", "cv", RRDLABEL_SRC_CONFIG);
1561 rrdlabels_add(l, "k8s_key", "kv", RRDLABEL_SRC_AUTO | RRDLABEL_SRC_K8S);
1562 rrdlabels_add(l, "aclk_key", "av", RRDLABEL_SRC_AUTO | RRDLABEL_SRC_ACLK);
1563
1564 rrdlabels_unmark_all(l);
1565 // simulate a successful config + aclk loader; the k8s loader added nothing
1566 rrdlabels_add(l, "cfg_key", "cv", RRDLABEL_SRC_CONFIG);
1567 rrdlabels_add(l, "aclk_key", "av", RRDLABEL_SRC_AUTO | RRDLABEL_SRC_ACLK);
1568 rrdlabels_remove_all_unmarked(l);
1569
1570 if (rrdlabels_exist(l, "k8s_key")) {
1571 fprintf(stderr, " FAIL (A): k8s_key should have been pruned (not re-added, not preserved)\n");
1572 errors++;
1573 }
1574 if (!rrdlabels_exist(l, "cfg_key") || !rrdlabels_exist(l, "aclk_key")) {
1575 fprintf(stderr, " FAIL (A): re-added labels should have survived the prune\n");
1576 errors++;
1577 }
1578 rrdlabels_destroy(l);
1579 }
1580
1581 // Subtest B: mark_source_as_old(K8S) preserves k8s entries through the prune
1582 // (the k8s-loader-failure preservation path in reload_host_labels()).
1583 {
1584 RRDLABELS *l = rrdlabels_create();
1585 rrdlabels_add(l, "cfg_key", "cv", RRDLABEL_SRC_CONFIG);
1586 rrdlabels_add(l, "k8s_key", "kv", RRDLABEL_SRC_AUTO | RRDLABEL_SRC_K8S);
1587 rrdlabels_add(l, "k8s_key2", "kv2", RRDLABEL_SRC_AUTO | RRDLABEL_SRC_K8S);
1588
1589 rrdlabels_unmark_all(l);
1590 // simulate successful config loader; k8s loader failed
1591 rrdlabels_add(l, "cfg_key", "cv", RRDLABEL_SRC_CONFIG);
1592 rrdlabels_mark_source_as_old(l, RRDLABEL_SRC_K8S);
1593 rrdlabels_remove_all_unmarked(l);
1594
1595 if (!rrdlabels_exist(l, "k8s_key") || !rrdlabels_exist(l, "k8s_key2")) {
1596 fprintf(stderr, " FAIL (B): K8S labels should be preserved via mark_source_as_old\n");
1597 errors++;
1598 }
1599 if (!rrdlabels_exist(l, "cfg_key")) {
1600 fprintf(stderr, " FAIL (B): cfg_key should still be present after the prune\n");
1601 errors++;
1602 }
1603 rrdlabels_destroy(l);
1604 }
1605
1606 // Subtest C: mark_source_as_old(K8S) only preserves labels whose source
1607 // mask intersects K8S; entries with other-only sources are still pruned.
1608 {
1609 RRDLABELS *l = rrdlabels_create();
1610 rrdlabels_add(l, "aclk_key", "av", RRDLABEL_SRC_AUTO | RRDLABEL_SRC_ACLK);
1611 rrdlabels_add(l, "cfg_key", "cv", RRDLABEL_SRC_CONFIG);
1612
1613 rrdlabels_unmark_all(l);
1614 rrdlabels_mark_source_as_old(l, RRDLABEL_SRC_K8S);
1615 rrdlabels_remove_all_unmarked(l);
1616
1617 if (rrdlabels_exist(l, "aclk_key") || rrdlabels_exist(l, "cfg_key")) {
1618 fprintf(stderr, " FAIL (C): non-K8S labels must not be preserved by mark_source_as_old(K8S)\n");
1619 errors++;
1620 }
1621 rrdlabels_destroy(l);
1622 }
1623
1624 fprintf(stderr, "%s: %d errors\n", __FUNCTION__, errors);
1625 return errors;
1626 }
1627
1628 #define UT_EXPECT(_cond, _msg) do { \
1629 if (!(_cond)) { \
1630 fprintf(stderr, " FAIL: %s\n", (_msg)); \
1631 errors++; \
1632 } \
1633 } while (0)
1634
1635 // Returns true when labels[key] resolves to exactly `expected`.
1636 static bool rrdlabels_unittest_value_is(RRDLABELS *labels, const char *key, const char *expected) {
1637 char *v = NULL;
1638 rrdlabels_get_value_strdup_or_null(labels, &v, key);
1639 bool ok = (v != NULL && strcmp(v, expected) == 0);
1640 freez(v);
1641 return ok;
1642 }
1643
1644 // Plant `n` pinned DONT_DELETE entries that share `key` but carry distinct
1645 // `values`, writing them straight into labels->JudyL. This bypasses the
1646 // public add/migrate/copy paths so a multi-duplicate starting state -- which
1647 // the fixed code can no longer produce -- can be constructed for tests.
1648 static void rrdlabels_unittest_plant_dups(RRDLABELS *labels, const char *key, const char *const *values, size_t n) {
1649 for (size_t i = 0; i < n; i++) {
1650 RRDLABEL *stale = add_label_name_value(key, values[i]);
1651 Pvoid_t *p = JudyLIns(&labels->JudyL, (Word_t)stale, PJE0);
1652 *((RRDLABEL_SRC *)p) = RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE;
1653 }
1654 }
1655
1656 static int rrdlabels_unittest_change_detection(void) {
1657 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1658 int errors = 0;
1659
1660 // ---- rrdlabels_add_changed: new vs same vs value-change ----
1661 RRDLABELS *l = rrdlabels_create();
1662 UT_EXPECT(rrdlabels_add_changed(l, "k1", "v1", RRDLABEL_SRC_CONFIG) == true,
1663 "add of new label should return true");
1664 UT_EXPECT(rrdlabels_add_changed(l, "k1", "v1", RRDLABEL_SRC_CONFIG) == false,
1665 "re-add of identical key+value should return false");
1666 UT_EXPECT(rrdlabels_add_changed(l, "k1", "v2", RRDLABEL_SRC_CONFIG) == true,
1667 "value change for an existing key should return true");
1668 UT_EXPECT(rrdlabels_add_changed(l, "k2", "v2", RRDLABEL_SRC_CONFIG) == true,
1669 "add of a different new key should return true");
1670 rrdlabels_destroy(l);
1671
1672 // ---- rrdlabels_migrate_to_these: empty/identical/diff/DONT_DELETE ----
1673 RRDLABELS *dst = rrdlabels_create();
1674 RRDLABELS *src = rrdlabels_create();
1675
1676 // empty dst, empty src => no add, no remove => false
1677 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == false,
1678 "migrate of two empty label sets should return false");
1679
1680 // populate src; dst empty => added > 0 => true
1681 rrdlabels_add(src, "k1", "v1", RRDLABEL_SRC_CONFIG);
1682 rrdlabels_add(src, "k2", "v2", RRDLABEL_SRC_CONFIG);
1683 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == true,
1684 "migrate to empty dst should return true when src has labels");
1685
1686 // identical content now (dst was populated from src) => false
1687 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == false,
1688 "migrate with identical key/value sets should return false");
1689
1690 // src adds one => true
1691 rrdlabels_add(src, "k3", "v3", RRDLABEL_SRC_CONFIG);
1692 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == true,
1693 "migrate that adds a label should return true");
1694
1695 // src drops one => true (removed > 0)
1696 {
1697 RRDLABELS *trimmed = rrdlabels_create();
1698 rrdlabels_add(trimmed, "k1", "v1", RRDLABEL_SRC_CONFIG);
1699 rrdlabels_add(trimmed, "k2", "v2", RRDLABEL_SRC_CONFIG);
1700 UT_EXPECT(rrdlabels_migrate_to_these(dst, trimmed) == true,
1701 "migrate that removes a label should return true");
1702 rrdlabels_destroy(trimmed);
1703 }
1704 rrdlabels_destroy(dst);
1705 rrdlabels_destroy(src);
1706
1707 // DONT_DELETE: a label that is not in src remains in dst; nothing added or
1708 // removed, so the function returns false even though dst != src.
1709 dst = rrdlabels_create();
1710 src = rrdlabels_create();
1711 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1712 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == false,
1713 "migrate where the only diff is a DONT_DELETE label should return false");
1714 UT_EXPECT(rrdlabels_entries(dst) == 1,
1715 "DONT_DELETE label should be preserved after migrate");
1716 rrdlabels_destroy(dst);
1717 rrdlabels_destroy(src);
1718
1719 // Value change via migrate (no DONT_DELETE): dst ends with a single entry
1720 // for the key, carrying the new value.
1721 dst = rrdlabels_create();
1722 src = rrdlabels_create();
1723 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG);
1724 rrdlabels_add(src, "k1", "v2", RRDLABEL_SRC_CONFIG);
1725 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == true,
1726 "migrate with a value change should return true");
1727 UT_EXPECT(rrdlabels_entries(dst) == 1,
1728 "migrate with a value change should leave one entry per key");
1729 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "v2"),
1730 "migrate with a value change should leave the new value in dst");
1731 rrdlabels_destroy(dst);
1732 rrdlabels_destroy(src);
1733
1734 // Value change via migrate where dst pinned the old value with DONT_DELETE:
1735 // the stale (key, old-value) must not survive, even though DONT_DELETE
1736 // would otherwise protect it from the remove-unmarked sweep.
1737 dst = rrdlabels_create();
1738 src = rrdlabels_create();
1739 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1740 rrdlabels_add(src, "k1", "v2", RRDLABEL_SRC_CONFIG);
1741 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == true,
1742 "migrate with a value change for a DONT_DELETE key should return true");
1743 UT_EXPECT(rrdlabels_entries(dst) == 1,
1744 "migrate must not leave a duplicate (key,old-value) when DONT_DELETE was set");
1745 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "v2"),
1746 "migrate with a DONT_DELETE value change should leave the new value in dst");
1747 rrdlabels_destroy(dst);
1748 rrdlabels_destroy(src);
1749
1750 // ---- rrdlabels_copy: same-key cleanup path ----
1751 // Value change via copy: dst ends with a single entry for the key,
1752 // carrying the new value from src. Exercises the same-key cleanup loop
1753 // that mirrors the migrate path.
1754 dst = rrdlabels_create();
1755 src = rrdlabels_create();
1756 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG);
1757 rrdlabels_add(src, "k1", "v2", RRDLABEL_SRC_CONFIG);
1758 rrdlabels_copy(dst, src);
1759 UT_EXPECT(rrdlabels_entries(dst) == 1,
1760 "copy with a value change should leave one entry per key");
1761 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "v2"),
1762 "copy with a value change should leave the new value in dst");
1763 rrdlabels_destroy(dst);
1764 rrdlabels_destroy(src);
1765
1766 // Value change via copy where dst pinned the old value with DONT_DELETE.
1767 dst = rrdlabels_create();
1768 src = rrdlabels_create();
1769 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1770 rrdlabels_add(src, "k1", "v2", RRDLABEL_SRC_CONFIG);
1771 rrdlabels_copy(dst, src);
1772 UT_EXPECT(rrdlabels_entries(dst) == 1,
1773 "copy must not leave a duplicate (key,old-value) when DONT_DELETE was set");
1774 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "v2"),
1775 "copy with a DONT_DELETE value change should leave the new value in dst");
1776 rrdlabels_destroy(dst);
1777 rrdlabels_destroy(src);
1778
1779 // ---- Multi-duplicate drain ----
1780 // The fixed code path can no longer produce a (key, *) duplicate state.
1781 // To verify the same-key cleanup LOOP drains arbitrarily many duplicates
1782 // (not just the first), plant multiple stale entries directly into
1783 // dst->JudyL via add_label_name_value() + JudyLIns(). This bypasses the
1784 // public add/migrate/copy paths so the pathological starting state can
1785 // be constructed for the test.
1786
1787 // Migrate (cleanup-only path): dst seeded with 3 pinned DONT_DELETE
1788 // entries sharing "k1"; src carries (k1, vc) whose dedup pointer is
1789 // ALREADY one of the planted entries, so JudyLIns lands on an existing
1790 // slot, takes the else-branch (added stays 0), and the cleanup loop is
1791 // the only mutation. Asserts that:
1792 // - the loop drains the OTHER two stale entries (not just the first);
1793 // - the function returns true even though added==0 and removed==0
1794 // (regression on the `cleaned` term in the return would fail here);
1795 // - dst ends with the single source entry.
1796 static const char *const planted_dups[] = { "va", "vb", "vc" };
1797 dst = rrdlabels_create();
1798 src = rrdlabels_create();
1799 rrdlabels_unittest_plant_dups(dst, "k1", planted_dups, 3);
1800 UT_EXPECT(rrdlabels_entries(dst) == 3,
1801 "test setup: dst should start with 3 manually-planted same-key entries");
1802 rrdlabels_add(src, "k1", "vc", RRDLABEL_SRC_CONFIG);
1803 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == true,
1804 "migrate must return true when cleanup is the only mutation (added=0, removed=0, cleaned>0)");
1805 UT_EXPECT(rrdlabels_entries(dst) == 1,
1806 "migrate must drain all stale same-key duplicates, not just the first");
1807 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "vc"),
1808 "migrate multi-duplicate drain should leave only the src value in dst");
1809 rrdlabels_destroy(dst);
1810 rrdlabels_destroy(src);
1811
1812 // Copy (cleanup-only path): same setup; src's (k1, vc) matches one of
1813 // the planted entries so the copy takes the else-branch and the cleanup
1814 // is the ONLY mutation. Asserts dst entries+value AND that
1815 // dst->version advances despite no insert -- regression on the
1816 // version bump inside the cleanup loop would fail this check.
1817 dst = rrdlabels_create();
1818 src = rrdlabels_create();
1819 rrdlabels_unittest_plant_dups(dst, "k1", planted_dups, 3);
1820 UT_EXPECT(rrdlabels_entries(dst) == 3,
1821 "test setup: dst should start with 3 manually-planted same-key entries (copy)");
1822 rrdlabels_add(src, "k1", "vc", RRDLABEL_SRC_CONFIG);
1823 uint32_t copy_version_before = rrdlabels_version(dst);
1824 rrdlabels_copy(dst, src);
1825 UT_EXPECT(rrdlabels_entries(dst) == 1,
1826 "copy must drain all stale same-key duplicates, not just the first");
1827 UT_EXPECT(rrdlabels_version(dst) > copy_version_before,
1828 "copy must advance dst->version when cleanup is the only mutation");
1829 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "vc"),
1830 "copy multi-duplicate drain should leave only the src value in dst");
1831 rrdlabels_destroy(dst);
1832 rrdlabels_destroy(src);
1833
1834 // ---- Cleanup respects key boundaries ----
1835 // The cleanup loop calls find_label_with_key_unsafe() which filters by
1836 // STRING key pointer. Regressing that key check would make the loop
1837 // delete entries with DIFFERENT keys. Plant pinned DONT_DELETE entries
1838 // under TWO keys; mutate only the first key's value; the second key's
1839 // entry must survive untouched.
1840 dst = rrdlabels_create();
1841 src = rrdlabels_create();
1842 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1843 rrdlabels_add(dst, "k2", "v2", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1844 rrdlabels_add(src, "k1", "v3", RRDLABEL_SRC_CONFIG);
1845 UT_EXPECT(rrdlabels_migrate_to_these(dst, src) == true,
1846 "migrate with mixed-key DONT_DELETE dst must return true on k1 change");
1847 UT_EXPECT(rrdlabels_entries(dst) == 2,
1848 "migrate cleanup must leave the unrelated DONT_DELETE key intact");
1849 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "v3"),
1850 "migrate must update k1 to the src value");
1851 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k2", "v2"),
1852 "migrate must preserve the unrelated DONT_DELETE key's value");
1853 rrdlabels_destroy(dst);
1854 rrdlabels_destroy(src);
1855
1856 // Same scenario for rrdlabels_copy().
1857 dst = rrdlabels_create();
1858 src = rrdlabels_create();
1859 rrdlabels_add(dst, "k1", "v1", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1860 rrdlabels_add(dst, "k2", "v2", RRDLABEL_SRC_CONFIG | RRDLABEL_FLAG_DONT_DELETE);
1861 rrdlabels_add(src, "k1", "v3", RRDLABEL_SRC_CONFIG);
1862 rrdlabels_copy(dst, src);
1863 UT_EXPECT(rrdlabels_entries(dst) == 2,
1864 "copy cleanup must leave the unrelated DONT_DELETE key intact");
1865 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k1", "v3"),
1866 "copy must update k1 to the src value");
1867 UT_EXPECT(rrdlabels_unittest_value_is(dst, "k2", "v2"),
1868 "copy must preserve the unrelated DONT_DELETE key's value");
1869 rrdlabels_destroy(dst);
1870 rrdlabels_destroy(src);
1871
1872 // ---- rrdlabels_remove_all_unmarked_and_changed: CLABEL-commit semantics ----
1873 // (1) unmark + re-add identical set => no change => false
1874 l = rrdlabels_create();
1875 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1876 rrdlabels_add(l, "k2", "v2", RRDLABEL_SRC_CONFIG);
1877 rrdlabels_unmark_all(l);
1878 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1879 rrdlabels_add(l, "k2", "v2", RRDLABEL_SRC_CONFIG);
1880 UT_EXPECT(rrdlabels_remove_all_unmarked_and_changed(l) == false,
1881 "remove_all_unmarked_and_changed: identical re-commit should return false");
1882 rrdlabels_destroy(l);
1883
1884 // (2) unmark + add a new label => added > 0 => true
1885 l = rrdlabels_create();
1886 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1887 rrdlabels_unmark_all(l);
1888 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1889 rrdlabels_add(l, "k2", "v2", RRDLABEL_SRC_CONFIG);
1890 UT_EXPECT(rrdlabels_remove_all_unmarked_and_changed(l) == true,
1891 "remove_all_unmarked_and_changed: adding a label should return true");
1892 rrdlabels_destroy(l);
1893
1894 // (3) unmark + commit a subset => removed > 0 => true
1895 l = rrdlabels_create();
1896 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1897 rrdlabels_add(l, "k2", "v2", RRDLABEL_SRC_CONFIG);
1898 rrdlabels_unmark_all(l);
1899 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1900 UT_EXPECT(rrdlabels_remove_all_unmarked_and_changed(l) == true,
1901 "remove_all_unmarked_and_changed: dropping a label should return true");
1902 rrdlabels_destroy(l);
1903
1904 // (4) unmark + change a value => true (the changed entry is NEW)
1905 l = rrdlabels_create();
1906 rrdlabels_add(l, "k1", "v1", RRDLABEL_SRC_CONFIG);
1907 rrdlabels_unmark_all(l);
1908 rrdlabels_add(l, "k1", "v2", RRDLABEL_SRC_CONFIG);
1909 UT_EXPECT(rrdlabels_remove_all_unmarked_and_changed(l) == true,
1910 "remove_all_unmarked_and_changed: value change should return true");
1911 rrdlabels_destroy(l);
1912
1913 return errors;
1914 }
1915
1916 #undef UT_EXPECT
1917
1918 struct pattern_array *trim_and_add_key_to_values(struct pattern_array *pa, const char *key, STRING *input);
1919 static int rrdlabels_unittest_check_pattern_list(RRDLABELS *labels, const char *pattern, bool expected) {
1920 fprintf(stderr, "rrdlabels_match_simple_pattern(labels, \"%s\") ... ", pattern);
1921
1922 STRING *str = string_strdupz(pattern);
1923 struct pattern_array *pa = trim_and_add_key_to_values(NULL, NULL, str);
1924
1925 bool ret = pattern_array_label_match(pa, labels, '=', NULL);
1926
1927 fprintf(stderr, "%s, got %s expected %s\n", (ret == expected)?"OK":"FAILED", ret?"true":"false", expected?"true":"false");
1928
1929 string_freez(str);
1930 pattern_array_free(pa);
1931
1932 return (ret == expected)?0:1;
1933 }
1934
1935 static int rrdlabels_unittest_host_chart_labels() {
1936 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1937
1938 int errors = 0;
1939
1940 RRDLABELS *labels = rrdlabels_create();
1941 rrdlabels_add(labels, "_hostname", "hostname1", RRDLABEL_SRC_CONFIG);
1942 rrdlabels_add(labels, "_os", "linux", RRDLABEL_SRC_CONFIG);
1943 rrdlabels_add(labels, "_distro", "ubuntu", RRDLABEL_SRC_CONFIG);
1944
1945 // match a single key
1946 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=*", true);
1947 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=!*", false);
1948
1949 // conflicting keys (some positive, some negative)
1950 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=* _os=!*", false);
1951 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=!* _os=*", false);
1952
1953 // the user uses a key that is not there
1954 errors += rrdlabels_unittest_check_pattern_list(labels, "_not_a_key=*", false);
1955 errors += rrdlabels_unittest_check_pattern_list(labels, "_not_a_key=!*", false);
1956 errors += rrdlabels_unittest_check_pattern_list(labels, "_not_a_key=* _hostname=* _os=*", false);
1957 errors += rrdlabels_unittest_check_pattern_list(labels, "_not_a_key=!* _hostname=* _os=*", false);
1958
1959 // positive and negative matches on the same key
1960 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=!*invalid* !*bad* *name*", true);
1961 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=*name* !*invalid* !*bad*", true);
1962
1963 // positive and negative matches on the same key with catch all
1964 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=!*invalid* !*bad* *", true);
1965 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=* !*invalid* !*bad*", true);
1966 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=!*invalid* !*name* *", false);
1967 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=* !*invalid* !*name*", true);
1968 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=*name* !*", true);
1969
1970 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=!*name* _os=l*", false);
1971 errors += rrdlabels_unittest_check_pattern_list(labels, "_os=l* hostname=!*name*", false);
1972 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=*name* _hostname=*", true);
1973 errors += rrdlabels_unittest_check_pattern_list(labels, "_hostname=*name* _os=l*", true);
1974 errors += rrdlabels_unittest_check_pattern_list(labels, "_os=l* _hostname=*name*", true);
1975
1976 rrdlabels_destroy(labels);
1977
1978 return errors;
1979 }
1980
1981 static int rrdlabels_unittest_check_simple_pattern(RRDLABELS *labels, const char *pattern, bool expected) {
1982 fprintf(stderr, "rrdlabels_match_simple_pattern(labels, \"%s\") ... ", pattern);
1983
1984 bool ret = rrdlabels_match_simple_pattern(labels, pattern);
1985 fprintf(stderr, "%s, got %s expected %s\n", (ret == expected)?"OK":"FAILED", ret?"true":"false", expected?"true":"false");
1986
1987 return (ret == expected)?0:1;
1988 }
1989
1990 static int rrdlabels_unittest_simple_pattern() {
1991 fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1992
1993 int errors = 0;
1994
1995 RRDLABELS *labels = rrdlabels_create();
1996 rrdlabels_add(labels, "tag1", "value1", RRDLABEL_SRC_CONFIG);
1997 rrdlabels_add(labels, "tag2", "value2", RRDLABEL_SRC_CONFIG);
1998 rrdlabels_add(labels, "tag3", "value3", RRDLABEL_SRC_CONFIG);
1999
2000 errors += rrdlabels_unittest_check_simple_pattern(labels, "*", true);
2001 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag", false);
2002 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag*", true);
2003 errors += rrdlabels_unittest_check_simple_pattern(labels, "*1", true);
2004 errors += rrdlabels_unittest_check_simple_pattern(labels, "value*", false);
2005 errors += rrdlabels_unittest_check_simple_pattern(labels, "*=value*", true);
2006 errors += rrdlabels_unittest_check_simple_pattern(labels, "*:value*", true);
2007 errors += rrdlabels_unittest_check_simple_pattern(labels, "*2", true);
2008 errors += rrdlabels_unittest_check_simple_pattern(labels, "*2 *3", true);
2009 errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag3 *2", true);
2010 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1 tag2", true);
2011 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1tag2", false);
2012 errors += rrdlabels_unittest_check_simple_pattern(labels, "invalid1 invalid2 tag3", true);
2013 errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag1 tag4", false);
2014 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1=value1", true);
2015 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1=value2", false);
2016 errors += rrdlabels_unittest_check_simple_pattern(labels, "tag*=value*", true);
2017 errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag*=value*", false);
2018 errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag2=something2 tag2=*2", true);
2019
2020 rrdlabels_destroy(labels);
2021
2022 return errors;
2023 }
2024
2025 int rrdlabels_unittest_sanitize_value(const char *src, const char *expected) {
2026 char buf[RRDLABELS_MAX_VALUE_LENGTH + 1];
2027 size_t len = rrdlabels_sanitize_value(buf, src, RRDLABELS_MAX_VALUE_LENGTH);
2028 size_t expected_len = strlen(expected);
2029
2030 int err = 0;
2031 if(strcmp(buf, expected) != 0) err = 1;
2032 if(len != expected_len) err = 1;
2033
2034 fprintf(stderr, "%s(%s): %s, expected '%s', got '%s', expected bytes = %zu, got bytes = %zu\n", __FUNCTION__, src, (err==1)?"FAILED":"OK", expected, buf, expected_len, strlen(buf));
2035 return err;
2036 }
2037
2038 int rrdlabels_unittest_sanitization() {
2039 int errors = 0;
2040
2041 errors += rrdlabels_unittest_sanitize_value("", "[none]");
2042 errors += rrdlabels_unittest_sanitize_value("1", "1");
2043 errors += rrdlabels_unittest_sanitize_value(" hello world ", "hello world");
2044 errors += rrdlabels_unittest_sanitize_value("[none]", "[none]");
2045
2046 // 2-byte UTF-8
2047 errors += rrdlabels_unittest_sanitize_value(" Ελλάδα ", "Ελλάδα");
2048 errors += rrdlabels_unittest_sanitize_value("aŰbŲcŴ", "aŰbŲcŴ");
2049 errors += rrdlabels_unittest_sanitize_value("Ű b Ų c Ŵ", "Ű b Ų c Ŵ");
2050
2051 // 3-byte UTF-8
2052 errors += rrdlabels_unittest_sanitize_value("‱", "‱");
2053 errors += rrdlabels_unittest_sanitize_value("a‱b", "a‱b");
2054 errors += rrdlabels_unittest_sanitize_value("a ‱ b", "a ‱ b");
2055
2056 // 4-byte UTF-8
2057 errors += rrdlabels_unittest_sanitize_value("𩸽", "𩸽");
2058 errors += rrdlabels_unittest_sanitize_value("a𩸽b", "a𩸽b");
2059 errors += rrdlabels_unittest_sanitize_value("a 𩸽 b", "a 𩸽 b");
2060
2061 // mixed multi-byte
2062 errors += rrdlabels_unittest_sanitize_value("Ű‱𩸽‱Ű", "Ű‱𩸽‱Ű");
2063
2064 // invalid UTF8 No 1
2065 const unsigned char invalid1[] = { 0xC3, 0x28, 'A', 'B', 0x0 };
2066 errors += rrdlabels_unittest_sanitize_value((const char *)invalid1, "c3(AB");
2067
2068 // invalid UTF8 No 2
2069 const unsigned char invalid2[] = { 'A', 'B', 0xC3, 0x28, 'C', 'D', 0x0 };
2070 errors += rrdlabels_unittest_sanitize_value((const char *)invalid2, "ABc3(CD");
2071
2072 // invalid UTF8 No 3
2073 const unsigned char invalid3[] = { 'A', 'B', 0xC3, 0x28, 0x0 };
2074 errors += rrdlabels_unittest_sanitize_value((const char *)invalid3, "ABc3(");
2075
2076 // invalid UTF8 No 4
2077 const unsigned char invalid4[] = "clewd修改\xe7\x89";
2078 errors += rrdlabels_unittest_sanitize_value((const char *)invalid4, "clewd修改e789");
2079
2080 // invalid UTF8 No 5
2081 const unsigned char invalid5[] = "app.clewd修改\xe7\x89_fd_open_limits";
2082 errors += rrdlabels_unittest_sanitize_value((const char *)invalid5, "app.clewd修改e789_fd_open_limits");
2083
2084 // invalid UTF8 No 6
2085 const unsigned char invalid6[] = "\260\327\312\300\322\242";
2086 errors += rrdlabels_unittest_sanitize_value((const char *)invalid6, "d7cac0Ң");
2087
2088 return errors;
2089 }
2090
2091 int rrdlabels_unittest(void) {
2092 int errors = 0;
2093
2094 errors += rrdlabels_unittest_sanitization();
2095 errors += rrdlabels_unittest_add_pairs();
2096 errors += rrdlabels_unittest_simple_pattern();
2097 errors += rrdlabels_unittest_host_chart_labels();
2098 errors += rrdlabels_unittest_double_check();
2099 errors += rrdlabels_unittest_migrate_check();
2100 errors += rrdlabels_unittest_mark_source_as_old();
2101 errors += rrdlabels_unittest_change_detection();
2102 errors += rrdlabels_unittest_pattern_check();
2103
2104 fprintf(stderr, "%d errors found\n", errors);
2105
2106 // string_destroy();
2107 return errors;
2108 }