| 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 | } |