1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "uuidmap.h"
4
+
5
+#define UUIDMAP_REUSE_GAP 1000
6
+
7
+struct uuidmap_entry {
8
+ nd_uuid_t uuid;
9
+ REFCOUNT refcount;
10
+};
11
+
12
+struct uuidmap_partition {
13
+ Pvoid_t uuid_to_id; // JudyL: UUID string -> ID
14
+ Pvoid_t id_to_uuid; // JudyL: ID -> UUID binary
15
+ Pvoid_t freed_ids; // JudyL: the freed IDs
16
+ UUIDMAP_ID next_id; // Only use lower bits
17
+ UUIDMAP_ID next_free_id; // fifo id when reusing freed ids
18
+ RW_SPINLOCK spinlock;
19
+
20
+ int64_t memory;
21
+ int32_t entries;
22
+};
23
+
24
+static struct {
25
+ struct uuidmap_partition p[UUIDMAP_PARTITIONS];
26
+ ARAL *ar;
27
+} uuid_map = { 0 };
28
+
29
+static struct aral_statistics uuidmap_stats = { 0 };
30
+struct aral_statistics *uuidmap_aral_statistics(void) { return &uuidmap_stats; }
31
+
32
+size_t uuidmap_memory(void) {
33
+ size_t memory = 0;
34
+
35
+ for(size_t i = 0; i < _countof(uuid_map.p) ;i++) {
36
+ rw_spinlock_read_lock(&uuid_map.p[i].spinlock);
37
+ memory += uuid_map.p[i].memory;
38
+ rw_spinlock_read_unlock(&uuid_map.p[i].spinlock);
39
+ }
40
+
41
+ return memory;
42
+}
43
+
44
+size_t uuidmap_free_bytes(void) {
45
+ return aral_free_bytes_from_stats(&uuidmap_stats);
46
+}
47
+
48
+static void uuidmap_init_aral(void) {
49
+ static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
50
+
51
+ if(!uuid_map.ar) {
52
+ spinlock_lock(&spinlock);
53
+ if(!uuid_map.ar) {
54
+ uuid_map.ar = aral_create(
55
+ "uuidmap",
56
+ sizeof(struct uuidmap_entry),
57
+ 0,
58
+ 0,
59
+ &uuidmap_stats,
60
+ NULL, NULL, false, false, true);
61
+ }
62
+ spinlock_unlock(&spinlock);
63
+ }
64
+}
65
+
66
+static UUIDMAP_ID get_next_id_unsafe(struct uuidmap_partition *partition) {
67
+ UUIDMAP_ID id = 0;
68
+
69
+ // Try to get a freed ID first if we have enough gap
70
+ Pvoid_t *PValue;
71
+ Word_t Index = 0;
72
+
73
+ PValue = JudyLFirst(partition->freed_ids, &Index, PJE0);
74
+ if (PValue && PValue != PJERR && *PValue) {
75
+ // Check if the stored ID is old enough to be reused
76
+ UUIDMAP_ID stored_id = *(UUIDMAP_ID *)PValue;
77
+ UUIDMAP_ID current_next_id = uuidmap_make_id(partition - uuid_map.p, partition->next_id);
78
+
79
+ if ((current_next_id - stored_id) >= UUIDMAP_REUSE_GAP) {
80
+ id = stored_id;
81
+ // Remove this entry from freed_ids since we're reusing it
82
+ int rc = JudyLDel(&partition->freed_ids, Index, PJE0);
83
+ if (unlikely(!rc))
84
+ fatal("UUIDMAP: cannot delete ID from freed_ids JudyL");
85
+ }
86
+ }
87
+
88
+ if (id == 0) {
89
+ // No reusable IDs available, get next sequential ID
90
+ id = uuidmap_make_id(partition - uuid_map.p, ++partition->next_id);
91
+ }
92
+
93
+ return id;
94
+}
95
+
96
+static inline UUIDMAP_ID uuidmap_acquire_by_uuid(const nd_uuid_t uuid) {
97
+ UUIDMAP_ID id = 0;
98
+
99
+ uint8_t partition = uuid_to_uuidmap_partition(uuid);
100
+
101
+ // try to find it in the JudyHS - we may have it already
102
+ rw_spinlock_read_lock(&uuid_map.p[partition].spinlock);
103
+ Pvoid_t *PValue = JudyHSGet(uuid_map.p[partition].uuid_to_id, (void *)uuid, sizeof(nd_uuid_t));
104
+ if(PValue == PJERR)
105
+ fatal("UUIDMAP: corrupted JudyHS array");
106
+
107
+ if(PValue && *PValue) {
108
+ // it is found
109
+
110
+ id = *(UUIDMAP_ID *)PValue;
111
+
112
+ PValue = JudyLGet(uuid_map.p[partition].id_to_uuid, id, PJE0);
113
+ if (!PValue || PValue == PJERR)
114
+ fatal("UUIDMAP: corrupted JudyL array");
115
+
116
+ struct uuidmap_entry *ue = *PValue;
117
+ if(!refcount_acquire(&ue->refcount))
118
+ id = 0;
119
+ }
120
+
121
+ rw_spinlock_read_unlock(&uuid_map.p[partition].spinlock);
122
+ return id;
123
+}
124
+
125
+UUIDMAP_ID uuidmap_create(const nd_uuid_t uuid) {
126
+ UUIDMAP_ID id = uuidmap_acquire_by_uuid(uuid);
127
+ if(id != 0) return id;
128
+
129
+ uuidmap_init_aral();
130
+
131
+ // we didn't find it - let's add it
132
+
133
+ uint8_t partition = uuid_to_uuidmap_partition(uuid);
134
+
135
+ JudyAllocThreadPulseReset();
136
+
137
+ Pvoid_t *PValue;
138
+ while(true) {
139
+ rw_spinlock_write_lock(&uuid_map.p[partition].spinlock);
140
+
141
+ PValue = JudyHSIns(&uuid_map.p[partition].uuid_to_id, (void *)uuid, sizeof(nd_uuid_t), PJE0);
142
+ if (!PValue || PValue == PJERR)
143
+ fatal("UUIDMAP: corrupted JudyHS array");
144
+
145
+ // If value exists, return it
146
+ if (*PValue != 0) {
147
+ id = (UUIDMAP_ID)(uintptr_t)*PValue;
148
+
149
+ PValue = JudyLGet(uuid_map.p[partition].id_to_uuid, id, PJE0);
150
+ if (!PValue || PValue == PJERR)
151
+ fatal("UUIDMAP: corrupted JudyL array");
152
+
153
+ struct uuidmap_entry *ue = *PValue;
154
+ if (!refcount_acquire(&ue->refcount)) {
155
+ rw_spinlock_write_unlock(&uuid_map.p[partition].spinlock);
156
+ continue;
157
+ }
158
+
159
+ uuid_map.p[partition].memory += JudyAllocThreadPulseGetAndReset();
160
+ rw_spinlock_write_unlock(&uuid_map.p[partition].spinlock);
161
+ return id;
162
+ }
163
+ else
164
+ break;
165
+ }
166
+
167
+ id = get_next_id_unsafe(&uuid_map.p[partition]);
168
+ *(UUIDMAP_ID *)PValue = id;
169
+
170
+ // Store ID -> UUID mapping
171
+ PValue = JudyLIns(&uuid_map.p[partition].id_to_uuid, id, PJE0);
172
+ if (!PValue || PValue == PJERR)
173
+ fatal("UUIDMAP: corrupted JudyL array");
174
+
175
+ struct uuidmap_entry *ue = aral_mallocz(uuid_map.ar);
176
+ nd_uuid_copy(ue->uuid, uuid);
177
+ ue->refcount = 1;
178
+ *PValue = ue;
179
+
180
+ uuid_map.p[partition].entries++;
181
+ uuid_map.p[partition].memory += sizeof(*ue);
182
+
183
+ uuid_map.p[partition].memory += JudyAllocThreadPulseGetAndReset();
184
+ rw_spinlock_write_unlock(&uuid_map.p[partition].spinlock);
185
+ return id;
186
+}
187
+
188
+static struct uuidmap_entry *get_entry_by_id(UUIDMAP_ID id) {
189
+ if(id == 0) return NULL;
190
+
191
+ uint8_t partition = uuidmap_id_to_partition(id);
192
+
193
+ rw_spinlock_read_lock(&uuid_map.p[partition].spinlock);
194
+
195
+ Pvoid_t *PValue = JudyLGet(uuid_map.p[partition].id_to_uuid, id, PJE0);
196
+ if (PValue == PJERR)
197
+ fatal("UUIDMAP: corrupted JudyL array");
198
+
199
+ struct uuidmap_entry *ue = PValue ? *PValue : NULL;
200
+
201
+ rw_spinlock_read_unlock(&uuid_map.p[partition].spinlock);
202
+
203
+ return ue;
204
+}
205
+
206
+void uuidmap_free(UUIDMAP_ID id) {
207
+ struct uuidmap_entry *ue = get_entry_by_id(id);
208
+
209
+ if(ue && refcount_release_and_acquire_for_deletion(&ue->refcount)) {
210
+ JudyAllocThreadPulseReset();
211
+ uint8_t partition = uuidmap_id_to_partition(id);
212
+ rw_spinlock_write_lock(&uuid_map.p[partition].spinlock);
213
+
214
+ int rc;
215
+ rc = JudyHSDel(&uuid_map.p[partition].uuid_to_id, (void *)ue->uuid, sizeof(nd_uuid_t), PJE0);
216
+ if(unlikely(!rc))
217
+ fatal("UUIDMAP: cannot delete UUID from JudyHS");
218
+
219
+ rc = JudyLDel(&uuid_map.p[partition].id_to_uuid, id, PJE0);
220
+ if(unlikely(!rc))
221
+ fatal("UUIDMAP: cannot delete ID from JudyL");
222
+
223
+ // Add the freed ID to the freed_ids JudyL using next_free_id as index
224
+ Pvoid_t *PValue = JudyLIns(&uuid_map.p[partition].freed_ids, ++uuid_map.p[partition].next_free_id, PJE0);
225
+ if (!PValue || PValue == PJERR)
226
+ fatal("UUIDMAP: corrupted freed_ids JudyL array");
227
+
228
+ *(UUIDMAP_ID *)PValue = id; // Store the actual METRIC_ID as the value
229
+
230
+ uuid_map.p[partition].memory -= sizeof(*ue);
231
+ uuid_map.p[partition].entries--;
232
+
233
+ uuid_map.p[partition].memory += JudyAllocThreadPulseGetAndReset();
234
+ rw_spinlock_write_unlock(&uuid_map.p[partition].spinlock);
235
+
236
+ aral_freez(uuid_map.ar, ue);
237
+ }
238
+}
239
+
240
+nd_uuid_t *uuidmap_uuid_ptr(UUIDMAP_ID id) {
241
+ struct uuidmap_entry *ue = get_entry_by_id(id);
242
+ return ue ? &ue->uuid : NULL;
243
+}
244
+
245
+nd_uuid_t *uuidmap_uuid_ptr_and_dup(UUIDMAP_ID id) {
246
+ struct uuidmap_entry *ue = get_entry_by_id(id);
247
+
248
+ if(ue && refcount_acquire(&ue->refcount))
249
+ return &ue->uuid;
250
+
251
+ return NULL;
252
+}
253
+
254
+bool uuidmap_uuid(UUIDMAP_ID id, nd_uuid_t out_uuid) {
255
+ nd_uuid_t *uuid = uuidmap_uuid_ptr(id);
256
+
257
+ if(!uuid) {
258
+ nd_uuid_clear(out_uuid);
259
+ return false;
260
+ }
261
+
262
+ uuid_copy(out_uuid, *uuid);
263
+ return true;
264
+}
265
+
266
+ND_UUID uuidmap_get(UUIDMAP_ID id) {
267
+ ND_UUID uuid;
268
+ uuidmap_uuid(id, uuid.uuid);
269
+ return uuid;
270
+}
271
+
272
+UUIDMAP_ID uuidmap_dup(UUIDMAP_ID id) {
273
+ struct uuidmap_entry *ue = get_entry_by_id(id);
274
+
275
+ if(!ue || !refcount_acquire(&ue->refcount))
276
+ fatal("UUIDMAP: id %u does not exist, or cannot be acquired, in %s", id, __FUNCTION__ );
277
+
278
+ return id;
279
+}
280
+
281
+// --------------------------------------------------------------------------------------------------------------------
282
+
283
+static volatile bool stop_flag = false;
284
+
285
+typedef struct thread_stats {
286
+ size_t creates;
287
+ size_t finds;
288
+ size_t dups;
289
+ size_t frees;
290
+ size_t cycles;
291
+} THREAD_STATS;
292
+
293
+static void *concurrent_test_thread(void *arg) {
294
+ THREAD_STATS *stats = arg;
295
+ nd_uuid_t test_uuid = {
296
+ 0x12, 0x34, 0x56, 0x78,
297
+ 0x9a, 0xbc, 0xde, 0xf0,
298
+ 0x12, 0x34, 0x56, 0x78,
299
+ 0x9a, 0xbc, 0xde, 0xf0
300
+ };
301
+
302
+ while(!__atomic_load_n(&stop_flag, __ATOMIC_RELAXED)) {
303
+ // 1. Create UUID (refcount 1)
304
+ UUIDMAP_ID id = uuidmap_create(test_uuid);
305
+ if(!id) continue;
306
+ stats->creates++;
307
+
308
+ // 2. Find its pointer
309
+ nd_uuid_t *uuid_ptr = uuidmap_uuid_ptr(id);
310
+ if(!uuid_ptr) {
311
+ fprintf(stderr, "ERROR: Cannot find UUID we just created\n");
312
+ break;
313
+ }
314
+ stats->finds++;
315
+
316
+ // 3. Dup it (refcount 2)
317
+ UUIDMAP_ID id2 = uuidmap_dup(id);
318
+ if(!id2) {
319
+ fprintf(stderr, "ERROR: Cannot dup UUID\n");
320
+ break;
321
+ }
322
+ stats->dups++;
323
+
324
+ // 4. Free it once (refcount 1)
325
+ uuidmap_free(id);
326
+ stats->frees++;
327
+
328
+ // 5. Find its pointer again
329
+ uuid_ptr = uuidmap_uuid_ptr(id2);
330
+ if(!uuid_ptr) {
331
+ fprintf(stderr, "ERROR: Cannot find UUID after first free\n");
332
+ break;
333
+ }
334
+ stats->finds++;
335
+
336
+ // 6. Free it twice (should delete)
337
+ uuidmap_free(id2);
338
+ stats->frees++;
339
+
340
+ stats->cycles++;
341
+ }
342
+
343
+ return NULL;
344
+}
345
+
346
+static int uuidmap_concurrent_unittest(void) {
347
+ const int num_threads = 4;
348
+ const int num_seconds = 5;
349
+ fprintf(stderr, "\nTesting concurrent UUID Map access with %d threads for %d seconds...\n", num_threads, num_seconds);
350
+ int errors = 0;
351
+
352
+ THREAD_STATS stats[num_threads];
353
+ memset(stats, 0, sizeof(stats));
354
+
355
+ ND_THREAD *threads[num_threads];
356
+
357
+ // Start threads
358
+ __atomic_store_n(&stop_flag, false, __ATOMIC_RELAXED);
359
+
360
+ for(int i = 0; i < num_threads; i++) {
361
+ char thread_name[32];
362
+ snprintf(thread_name, sizeof(thread_name), "UUID-TEST-%d", i);
363
+ threads[i] = nd_thread_create(
364
+ thread_name,
365
+ NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
366
+ concurrent_test_thread,
367
+ &stats[i]);
368
+ }
369
+
370
+ // Let it run for 5 seconds
371
+ sleep_usec(num_seconds * USEC_PER_SEC);
372
+
373
+ // Stop threads
374
+ __atomic_store_n(&stop_flag, true, __ATOMIC_RELEASE);
375
+
376
+ // Wait for threads
377
+ for(int i = 0; i < num_threads; i++)
378
+ nd_thread_join(threads[i]);
379
+
380
+ // Print statistics
381
+ size_t total_cycles = 0;
382
+ for(int i = 0; i < num_threads; i++) {
383
+ fprintf(stderr, "Thread %d stats:\n"
384
+ " Cycles completed : %zu\n"
385
+ " Creates : %zu\n"
386
+ " Finds : %zu\n"
387
+ " Dups : %zu\n"
388
+ " Frees : %zu\n",
389
+ i,
390
+ stats[i].cycles,
391
+ stats[i].creates,
392
+ stats[i].finds,
393
+ stats[i].dups,
394
+ stats[i].frees);
395
+
396
+ total_cycles += stats[i].cycles;
397
+ }
398
+
399
+ fprintf(stderr, "\nTotal cycles completed: %zu (%.2f cycles/sec)\n",
400
+ total_cycles,
401
+ (double)total_cycles / 5.0);
402
+
403
+ return errors;
404
+}
405
+
406
+int uuidmap_unittest(void) {
407
+ fprintf(stderr, "\nTesting UUID Map...\n");
408
+
409
+ const size_t ENTRIES = 100000;
410
+ int errors = uuidmap_concurrent_unittest();
411
+
412
+ struct test_entry {
413
+ nd_uuid_t uuid;
414
+ UUIDMAP_ID id;
415
+ };
416
+
417
+ struct test_entry *entries = mallocz(sizeof(struct test_entry) * ENTRIES);
418
+
419
+ fprintf(stderr, "Generating and testing %zu entries...\n", ENTRIES);
420
+
421
+ usec_t start_time = now_monotonic_usec();
422
+ size_t step = ENTRIES / 100;
423
+ size_t next_step = step;
424
+
425
+ for(size_t i = 0; i < ENTRIES; i++) {
426
+ if (i >= next_step) {
427
+ fprintf(stderr, ".");
428
+ next_step += step;
429
+ }
430
+
431
+ uuid_generate_random(entries[i].uuid);
432
+ char uuid_str[UUID_STR_LEN];
433
+ uuid_unparse_lower(entries[i].uuid, uuid_str);
434
+
435
+ // Test 1: Should not exist yet
436
+ UUIDMAP_ID id = uuidmap_acquire_by_uuid(entries[i].uuid);
437
+ if(id != 0) {
438
+ fprintf(stderr, "\nERROR [%zu]: UUID found before adding it"
439
+ "\n UUID: %s"
440
+ "\n Got ID: %u (expected: 0)\n",
441
+ i, uuid_str, id);
442
+ errors++;
443
+ }
444
+
445
+ // Test 2: Create it
446
+ id = uuidmap_create(entries[i].uuid);
447
+ if(id == 0) {
448
+ fprintf(stderr, "\nERROR [%zu]: Failed to create UUID mapping"
449
+ "\n UUID: %s\n",
450
+ i, uuid_str);
451
+ errors++;
452
+ continue;
453
+ }
454
+
455
+ // Test 3: Create again, should return same id
456
+ UUIDMAP_ID id2 = uuidmap_create(entries[i].uuid);
457
+ if(id2 != id) {
458
+ fprintf(stderr, "\nERROR [%zu]: Second create returned different ID"
459
+ "\n UUID: %s"
460
+ "\n First ID: %u"
461
+ "\n Second ID: %u\n",
462
+ i, uuid_str, id, id2);
463
+ errors++;
464
+ }
465
+
466
+ // Test 4: Get UUID and verify
467
+ nd_uuid_t test_uuid;
468
+ if(!uuidmap_uuid(id, test_uuid)) {
469
+ fprintf(stderr, "\nERROR [%zu]: Failed to get UUID for valid ID"
470
+ "\n UUID: %s"
471
+ "\n ID: %u\n",
472
+ i, uuid_str, id);
473
+ errors++;
474
+ }
475
+ else {
476
+ char test_uuid_str[UUID_STR_LEN];
477
+ uuid_unparse_lower(test_uuid, test_uuid_str);
478
+ if(uuid_compare(test_uuid, entries[i].uuid) != 0) {
479
+ fprintf(stderr, "\nERROR [%zu]: Retrieved UUID doesn't match original"
480
+ "\n Original UUID: %s"
481
+ "\n Retrieved UUID: %s"
482
+ "\n ID: %u\n",
483
+ i, uuid_str, test_uuid_str, id);
484
+ errors++;
485
+ }
486
+ }
487
+
488
+ // Test 5: Free once (decrease refcount)
489
+ uuidmap_free(id);
490
+
491
+ // Test 6: Should still exist
492
+ if(!uuidmap_uuid(id, test_uuid)) {
493
+ fprintf(stderr, "\nERROR [%zu]: UUID disappeared after first free"
494
+ "\n UUID: %s"
495
+ "\n ID: %u\n",
496
+ i, uuid_str, id);
497
+ errors++;
498
+ }
499
+ else {
500
+ char test_uuid_str[UUID_STR_LEN];
501
+ uuid_unparse_lower(test_uuid, test_uuid_str);
502
+ if(uuid_compare(test_uuid, entries[i].uuid) != 0) {
503
+ fprintf(stderr, "\nERROR [%zu]: Retrieved UUID doesn't match after first free"
504
+ "\n Original UUID: %s"
505
+ "\n Retrieved UUID: %s"
506
+ "\n ID: %u\n",
507
+ i, uuid_str, test_uuid_str, id);
508
+ errors++;
509
+ }
510
+ }
511
+
512
+ // Test 7: Free again (should delete)
513
+ uuidmap_free(id);
514
+
515
+ // Test 8: Should be gone
516
+ if(uuidmap_uuid_ptr(id) != NULL) {
517
+ char curr_uuid_str[UUID_STR_LEN];
518
+ nd_uuid_t *curr_uuid = uuidmap_uuid_ptr(id);
519
+ if(curr_uuid)
520
+ uuid_unparse_lower(*curr_uuid, curr_uuid_str);
521
+
522
+ fprintf(stderr, "\nERROR [%zu]: UUID still exists after second free"
523
+ "\n Original UUID: %s"
524
+ "\n Current UUID: %s"
525
+ "\n ID: %u\n",
526
+ i, uuid_str, curr_uuid_str, id);
527
+
528
+ errors++;
529
+ }
530
+
531
+ // Test 9: Create again for phase 2
532
+ id = uuidmap_create(entries[i].uuid);
533
+ if(id == 0) {
534
+ fprintf(stderr, "\nERROR [%zu]: Failed to recreate UUID mapping"
535
+ "\n UUID: %s\n",
536
+ i, uuid_str);
537
+ errors++;
538
+ continue;
539
+ }
540
+
541
+ entries[i].id = id;
542
+ }
543
+
544
+ usec_t end_time = now_monotonic_usec();
545
+ fprintf(stderr, "\nPhase 1 completed in %.2f seconds with %d errors\n",
546
+ (double)(end_time - start_time) / (double)USEC_PER_SEC, errors);
547
+
548
+ // BENCHMARK while we have all entries loaded
549
+ if(errors == 0) {
550
+ fprintf(stderr, "\nBenchmarking UUID retrievals...\n");
551
+
552
+ // First benchmark: uuidmap_uuid_ptr()
553
+ size_t successful = 0;
554
+ usec_t start_ut = now_monotonic_usec();
555
+
556
+ for(size_t i = 0; i < ENTRIES; i++) {
557
+ nd_uuid_t *uuid_ptr = uuidmap_uuid_ptr(entries[i].id);
558
+ if(uuid_ptr && uuid_compare(*uuid_ptr, entries[i].uuid) == 0)
559
+ successful++;
560
+ }
561
+
562
+ usec_t end_ut = now_monotonic_usec();
563
+ double secs = (double)(end_ut - start_ut) / USEC_PER_SEC;
564
+ double ops = (double)successful / secs;
565
+
566
+ fprintf(stderr, "uuidmap_uuid_ptr() : %.2f ops/sec (%.2f usec/op)\n",
567
+ ops, (double)(end_ut - start_ut) / successful);
568
+
569
+ // Second benchmark: uuidmap_get_by_uuid()
570
+ successful = 0;
571
+ start_ut = now_monotonic_usec();
572
+
573
+ for(size_t i = 0; i < ENTRIES; i++) {
574
+ UUIDMAP_ID id = uuidmap_acquire_by_uuid(entries[i].uuid);
575
+ if(id != 0) {
576
+ successful++;
577
+ uuidmap_free(id); // Must free since get_by_uuid increases refcount
578
+ }
579
+ }
580
+
581
+ end_ut = now_monotonic_usec();
582
+ secs = (double)(end_ut - start_ut) / USEC_PER_SEC;
583
+ ops = (double)successful / secs;
584
+
585
+ fprintf(stderr, "uuidmap_acquire_by_uuid(): %.2f ops/sec (%.2f usec/op)\n",
586
+ ops, (double)(end_ut - start_ut) / successful);
587
+ }
588
+
589
+ // Phase 2: Delete everything
590
+ fprintf(stderr, "\nDeleting all entries...\n");
591
+ start_time = now_monotonic_usec();
592
+ next_step = step;
593
+
594
+ for(size_t i = 0; i < ENTRIES; i++) {
595
+ if (i >= next_step) {
596
+ fprintf(stderr, ".");
597
+ next_step += step;
598
+ }
599
+
600
+ UUIDMAP_ID id = entries[i].id;
601
+ char uuid_str[UUID_STR_LEN];
602
+ uuid_unparse_lower(entries[i].uuid, uuid_str);
603
+
604
+ // Test 1: Should exist
605
+ nd_uuid_t *uuid_ptr = uuidmap_uuid_ptr(id);
606
+ if(!uuid_ptr) {
607
+ fprintf(stderr, "\nERROR [%zu]: UUID not found before deletion"
608
+ "\n UUID: %s"
609
+ "\n ID: %u\n",
610
+ i, uuid_str, id);
611
+ errors++;
612
+ continue;
613
+ }
614
+
615
+ char current_uuid_str[UUID_STR_LEN];
616
+ uuid_unparse_lower(*uuid_ptr, current_uuid_str);
617
+ if(uuid_compare(*uuid_ptr, entries[i].uuid) != 0) {
618
+ fprintf(stderr, "\nERROR [%zu]: Retrieved UUID doesn't match before deletion"
619
+ "\n Original UUID: %s"
620
+ "\n Current UUID: %s"
621
+ "\n ID: %u\n",
622
+ i, uuid_str, current_uuid_str, id);
623
+ errors++;
624
+ }
625
+
626
+ // Test 2: Create again
627
+ UUIDMAP_ID id2 = uuidmap_create(entries[i].uuid);
628
+ if(id2 != id) {
629
+ fprintf(stderr, "\nERROR [%zu]: Recreation returned different ID"
630
+ "\n UUID: %s"
631
+ "\n Original ID: %u"
632
+ "\n New ID: %u\n",
633
+ i, uuid_str, id, id2);
634
+ errors++;
635
+ }
636
+
637
+ // Test 3 & 4: Free three times (one extra from benchmark)
638
+ uuidmap_free(id);
639
+ uuidmap_free(id);
640
+ uuidmap_free(id);
641
+
642
+ // Test 5: Should be gone
643
+ uuid_ptr = uuidmap_uuid_ptr(id);
644
+ if(uuid_ptr != NULL) {
645
+ char remaining_uuid_str[UUID_STR_LEN];
646
+ uuid_unparse_lower(*uuid_ptr, remaining_uuid_str);
647
+ fprintf(stderr, "\nERROR [%zu]: UUID still exists after final deletion"
648
+ "\n Original UUID: %s"
649
+ "\n Remaining UUID: %s"
650
+ "\n ID: %u\n",
651
+ i, uuid_str, remaining_uuid_str, id);
652
+ errors++;
653
+ }
654
+ }
655
+
656
+ end_time = now_monotonic_usec();
657
+ fprintf(stderr, "\nPhase 2 completed in %.2f seconds with %d errors\n",
658
+ (double)(end_time - start_time) / (double)USEC_PER_SEC, errors);
659
+
660
+ freez(entries);
661
+
662
+ fprintf(stderr, "\nUUID Map test completed with %d total errors\n", errors);
663
+ return errors;
664
+}