1
+#include "../libnetdata.h"
2
+#include "aral.h"
3
+
4
+#ifdef NETDATA_TRACE_ALLOCATIONS
5
+#define TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS , const char *file, const char *function, size_t line
6
+#define TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS , file, function, line
7
+#else
8
+#define TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS
9
+#define TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS
10
+#endif
11
+
12
+#define ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST 5
13
+
14
+// max file size
15
+#define ARAL_MAX_PAGE_SIZE_MMAP (1*1024*1024*1024)
16
+
17
+// max malloc size
18
+// optimal at current versions of libc is up to 256k
19
+// ideal to have the same overhead as libc is 4k
20
+#define ARAL_MAX_PAGE_SIZE_MALLOC (65*1024)
21
+
22
+typedef struct aral_free {
23
+ size_t size;
24
+ struct aral_free *next;
25
+} ARAL_FREE;
26
+
27
+typedef struct aral_page {
28
+ size_t size; // the allocation size of the page
29
+ const char *filename;
30
+ uint8_t *data;
31
+
32
+ uint32_t free_elements_to_move_first;
33
+ uint32_t max_elements; // the number of elements that can fit on this page
34
+
35
+ struct {
36
+ uint32_t used_elements; // the number of used elements on this page
37
+ uint32_t free_elements; // the number of free elements on this page
38
+ } aral_lock;
39
+
40
+ struct {
41
+ SPINLOCK spinlock;
42
+ ARAL_FREE *list;
43
+ } free;
44
+
45
+ struct aral_page *prev; // the prev page on the list
46
+ struct aral_page *next; // the next page on the list
47
+} ARAL_PAGE;
48
+
49
+struct aral {
50
+ struct {
51
+ char name[ARAL_MAX_NAME + 1];
52
+
53
+ bool lockless;
54
+ bool defragment;
55
+
56
+ size_t element_size; // calculated to take into account ARAL overheads
57
+ size_t max_allocation_size; // calculated in bytes
58
+ size_t page_ptr_offset; // calculated
59
+ size_t natural_page_size; // calculated
60
+
61
+ size_t requested_element_size;
62
+ size_t initial_page_elements;
63
+ size_t max_page_elements;
64
+
65
+ struct {
66
+ bool enabled;
67
+ const char *filename;
68
+ char **cache_dir;
69
+ } mmap;
70
+ } config;
71
+
72
+ struct {
73
+ SPINLOCK spinlock;
74
+ size_t file_number; // for mmap
75
+ struct aral_page *pages; // linked list of pages
76
+
77
+ size_t user_malloc_operations;
78
+ size_t user_free_operations;
79
+ size_t defragment_operations;
80
+ size_t defragment_linked_list_traversals;
81
+ } aral_lock;
82
+
83
+ struct {
84
+ SPINLOCK spinlock;
85
+ size_t allocation_size; // current allocation size
86
+ } adders;
87
+
88
+ struct {
89
+ } atomic;
90
+};
91
+
92
+struct {
93
+ struct {
94
+ struct {
95
+ size_t allocations;
96
+ size_t allocated;
97
+ } structures;
98
+
99
+ struct {
100
+ size_t allocations;
101
+ size_t allocated;
102
+ size_t used;
103
+ } malloc;
104
+
105
+ struct {
106
+ size_t allocations;
107
+ size_t allocated;
108
+ size_t used;
109
+ } mmap;
110
+ } atomic;
111
+} aral_globals = {};
112
+
113
+void aral_get_size_statistics(size_t *structures, size_t *malloc_allocated, size_t *malloc_used, size_t *mmap_allocated, size_t *mmap_used) {
114
+ *structures = __atomic_load_n(&aral_globals.atomic.structures.allocated, __ATOMIC_RELAXED);
115
+ *malloc_allocated = __atomic_load_n(&aral_globals.atomic.malloc.allocated, __ATOMIC_RELAXED);
116
+ *malloc_used = __atomic_load_n(&aral_globals.atomic.malloc.used, __ATOMIC_RELAXED);
117
+ *mmap_allocated = __atomic_load_n(&aral_globals.atomic.mmap.allocated, __ATOMIC_RELAXED);
118
+ *mmap_used = __atomic_load_n(&aral_globals.atomic.mmap.used, __ATOMIC_RELAXED);
119
+}
120
+
121
+#define ARAL_NATURAL_ALIGNMENT (sizeof(uintptr_t) * 2)
122
+static inline size_t natural_alignment(size_t size, size_t alignment) {
123
+ if(unlikely(size % alignment))
124
+ size = size + alignment - (size % alignment);
125
+
126
+ return size;
127
+}
128
+
129
+static size_t aral_align_alloc_size(ARAL *ar, uint64_t size) {
130
+ if(size % ar->config.natural_page_size)
131
+ size += ar->config.natural_page_size - (size % ar->config.natural_page_size) ;
132
+
133
+ if(size % ar->config.element_size)
134
+ size -= size % ar->config.element_size;
135
+
136
+ return size;
137
+}
138
+
139
+static inline void aral_lock(ARAL *ar) {
140
+ if(likely(!ar->config.lockless))
141
+ netdata_spinlock_lock(&ar->aral_lock.spinlock);
142
+}
143
+
144
+static inline void aral_unlock(ARAL *ar) {
145
+ if(likely(!ar->config.lockless))
146
+ netdata_spinlock_unlock(&ar->aral_lock.spinlock);
147
+}
148
+
149
+static void aral_delete_leftover_files(const char *name, const char *path, const char *required_prefix) {
150
+ DIR *dir = opendir(path);
151
+ if(!dir) return;
152
+
153
+ char full_path[FILENAME_MAX + 1];
154
+ size_t len = strlen(required_prefix);
155
+
156
+ struct dirent *de = NULL;
157
+ while((de = readdir(dir))) {
158
+ if(de->d_type == DT_DIR)
159
+ continue;
160
+
161
+ if(strncmp(de->d_name, required_prefix, len) != 0)
162
+ continue;
163
+
164
+ snprintfz(full_path, FILENAME_MAX, "%s/%s", path, de->d_name);
165
+ info("ARAL: '%s' removing left-over file '%s'", name, full_path);
166
+ if(unlikely(unlink(full_path) == -1))
167
+ error("ARAL: '%s' cannot delete file '%s'", name, full_path);
168
+ }
169
+
170
+ closedir(dir);
171
+}
172
+
173
+// ----------------------------------------------------------------------------
174
+// check a free slot
175
+
176
+#ifdef NETDATA_INTERNAL_CHECKS
177
+static inline void aral_free_validate_internal_check(ARAL *ar, ARAL_FREE *fr) {
178
+ if(unlikely(fr->size < ar->config.element_size))
179
+ fatal("ARAL: '%s' free item of size %zu, less than the expected element size %zu",
180
+ ar->config.name, fr->size, ar->config.element_size);
181
+
182
+ if(unlikely(fr->size % ar->config.element_size))
183
+ fatal("ARAL: '%s' free item of size %zu is not multiple to element size %zu",
184
+ ar->config.name, fr->size, ar->config.element_size);
185
+}
186
+#else
187
+#define aral_free_validate_internal_check(ar, fr) debug_dummy()
188
+#endif
189
+
190
+// ----------------------------------------------------------------------------
191
+// find the page a pointer belongs to
192
+
193
+#ifdef NETDATA_INTERNAL_CHECKS
194
+static inline ARAL_PAGE *find_page_with_allocation_internal_check(ARAL *ar, void *ptr) {
195
+ aral_lock(ar);
196
+
197
+ uintptr_t seeking = (uintptr_t)ptr;
198
+ ARAL_PAGE *page;
199
+
200
+ for(page = ar->aral_lock.pages; page ; page = page->next) {
201
+ if(unlikely(seeking >= (uintptr_t)page->data && seeking < (uintptr_t)page->data + page->size))
202
+ break;
203
+ }
204
+
205
+ aral_unlock(ar);
206
+
207
+ return page;
208
+}
209
+#endif
210
+
211
+// ----------------------------------------------------------------------------
212
+// find a page with a free slot (there shouldn't be any)
213
+
214
+#ifdef NETDATA_ARAL_INTERNAL_CHECKS
215
+static inline ARAL_PAGE *find_page_with_free_slots_internal_check___with_aral_lock(ARAL *ar) {
216
+ ARAL_PAGE *page;
217
+
218
+ for(page = ar->aral_lock.pages; page ; page = page->next) {
219
+ if(page->aral_lock.free_elements)
220
+ break;
221
+
222
+ internal_fatal(page->size - page->aral_lock.used_elements * ar->config.element_size >= ar->config.element_size,
223
+ "ARAL: '%s' a page is marked full, but it is not!", ar->config.name);
224
+
225
+ internal_fatal(page->size < page->aral_lock.used_elements * ar->config.element_size,
226
+ "ARAL: '%s' a page has been overflown!", ar->config.name);
227
+ }
228
+
229
+ return page;
230
+}
231
+#endif
232
+
233
+static ARAL_PAGE *aral_create_page___no_lock_needed(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
234
+ ARAL_PAGE *page = callocz(1, sizeof(ARAL_PAGE));
235
+ netdata_spinlock_init(&page->free.spinlock);
236
+ page->size = ar->adders.allocation_size;
237
+
238
+ if(page->size > ar->config.max_allocation_size)
239
+ page->size = ar->config.max_allocation_size;
240
+ else
241
+ ar->adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->adders.allocation_size * 4 / 3);
242
+
243
+ page->max_elements = page->aral_lock.free_elements = page->size / ar->config.element_size;
244
+ page->free_elements_to_move_first = page->max_elements / 4;
245
+ if(unlikely(page->free_elements_to_move_first < 1))
246
+ page->free_elements_to_move_first = 1;
247
+
248
+ __atomic_add_fetch(&aral_globals.atomic.structures.allocations, 1, __ATOMIC_RELAXED);
249
+ __atomic_add_fetch(&aral_globals.atomic.structures.allocated, sizeof(ARAL_PAGE), __ATOMIC_RELAXED);
250
+
251
+ if(unlikely(ar->config.mmap.enabled)) {
252
+ ar->aral_lock.file_number++;
253
+ char filename[FILENAME_MAX + 1];
254
+ snprintfz(filename, FILENAME_MAX, "%s/array_alloc.mmap/%s.%zu", *ar->config.mmap.cache_dir, ar->config.mmap.filename, ar->aral_lock.file_number);
255
+ page->filename = strdupz(filename);
256
+ page->data = netdata_mmap(page->filename, page->size, MAP_SHARED, 0, false, NULL);
257
+ if (unlikely(!page->data))
258
+ fatal("ARAL: '%s' cannot allocate aral buffer of size %zu on filename '%s'",
259
+ ar->config.name, page->size, page->filename);
260
+ __atomic_add_fetch(&aral_globals.atomic.mmap.allocations, 1, __ATOMIC_RELAXED);
261
+ __atomic_add_fetch(&aral_globals.atomic.mmap.allocated, page->size, __ATOMIC_RELAXED);
262
+ }
263
+ else {
264
+#ifdef NETDATA_TRACE_ALLOCATIONS
265
+ page->data = mallocz_int(page->size TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
266
+#else
267
+ page->data = mallocz(page->size);
268
+#endif
269
+ __atomic_add_fetch(&aral_globals.atomic.malloc.allocations, 1, __ATOMIC_RELAXED);
270
+ __atomic_add_fetch(&aral_globals.atomic.malloc.allocated, page->size, __ATOMIC_RELAXED);
271
+ }
272
+
273
+ // link the free space to its page
274
+ ARAL_FREE *fr = (ARAL_FREE *)page->data;
275
+ fr->size = page->size;
276
+ fr->next = NULL;
277
+ page->free.list = fr;
278
+
279
+ aral_free_validate_internal_check(ar, fr);
280
+
281
+ return page;
282
+}
283
+
284
+void aral_del_page___no_lock_needed(ARAL *ar, ARAL_PAGE *page TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
285
+
286
+ // free it
287
+ if (ar->config.mmap.enabled) {
288
+ netdata_munmap(page->data, page->size);
289
+
290
+ if (unlikely(unlink(page->filename) == 1))
291
+ error("Cannot delete file '%s'", page->filename);
292
+
293
+ freez((void *)page->filename);
294
+
295
+ __atomic_sub_fetch(&aral_globals.atomic.mmap.allocations, 1, __ATOMIC_RELAXED);
296
+ __atomic_sub_fetch(&aral_globals.atomic.mmap.allocated, page->size, __ATOMIC_RELAXED);
297
+ }
298
+ else {
299
+#ifdef NETDATA_TRACE_ALLOCATIONS
300
+ freez_int(page->data TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
301
+#else
302
+ freez(page->data);
303
+#endif
304
+ __atomic_sub_fetch(&aral_globals.atomic.malloc.allocations, 1, __ATOMIC_RELAXED);
305
+ __atomic_sub_fetch(&aral_globals.atomic.malloc.allocated, page->size, __ATOMIC_RELAXED);
306
+ }
307
+
308
+ freez(page);
309
+
310
+ __atomic_sub_fetch(&aral_globals.atomic.structures.allocations, 1, __ATOMIC_RELAXED);
311
+ __atomic_sub_fetch(&aral_globals.atomic.structures.allocated, sizeof(ARAL_PAGE), __ATOMIC_RELAXED);
312
+}
313
+
314
+static inline void aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) {
315
+ ARAL_PAGE *first = ar->aral_lock.pages;
316
+
317
+ if (page->aral_lock.free_elements <= page->free_elements_to_move_first ||
318
+ !first ||
319
+ !first->aral_lock.free_elements ||
320
+ page->aral_lock.free_elements <= first->aral_lock.free_elements + ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST) {
321
+ // first position
322
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
323
+ }
324
+ else {
325
+ ARAL_PAGE *second = first->next;
326
+
327
+ if (!second ||
328
+ !second->aral_lock.free_elements ||
329
+ page->aral_lock.free_elements <= second->aral_lock.free_elements)
330
+ // second position
331
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, first, page, prev, next);
332
+ else
333
+ // third position
334
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, second, page, prev, next);
335
+ }
336
+}
337
+
338
+static inline ARAL_PAGE *aral_acquire_a_free_slot(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
339
+ aral_lock(ar);
340
+
341
+ ARAL_PAGE *page = ar->aral_lock.pages;
342
+
343
+ while(!page || !page->aral_lock.free_elements) {
344
+#ifdef NETDATA_ARAL_INTERNAL_CHECKS
345
+ internal_fatal(find_page_with_free_slots_internal_check___with_aral_lock(ar), "ARAL: '%s' found page with free slot!", ar->config.name);
346
+#endif
347
+ aral_unlock(ar);
348
+
349
+ if(netdata_spinlock_trylock(&ar->adders.spinlock)) {
350
+ page = aral_create_page___no_lock_needed(ar TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
351
+
352
+ aral_lock(ar);
353
+ aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ar, page);
354
+ netdata_spinlock_unlock(&ar->adders.spinlock);
355
+ break;
356
+ }
357
+ else {
358
+ aral_lock(ar);
359
+ page = ar->aral_lock.pages;
360
+ }
361
+ }
362
+
363
+ // we have a page
364
+ // and aral locked
365
+
366
+ {
367
+ ARAL_PAGE *first = ar->aral_lock.pages;
368
+ ARAL_PAGE *second = first->next;
369
+
370
+ if (!second ||
371
+ !second->aral_lock.free_elements ||
372
+ first->aral_lock.free_elements <= second->aral_lock.free_elements + ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST)
373
+ page = first;
374
+ else {
375
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, second, prev, next);
376
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, second, prev, next);
377
+ page = second;
378
+ }
379
+ }
380
+
381
+ internal_fatal(!page || !page->aral_lock.free_elements,
382
+ "ARAL: '%s' selected page does not have a free slot in it",
383
+ ar->config.name);
384
+
385
+ internal_fatal(page->max_elements != page->aral_lock.used_elements + page->aral_lock.free_elements,
386
+ "ARAL: '%s' page element counters do not match, "
387
+ "page says it can handle %zu elements, "
388
+ "but there are %zu used and %zu free items, "
389
+ "total %zu items",
390
+ ar->config.name,
391
+ (size_t)page->max_elements,
392
+ (size_t)page->aral_lock.used_elements, (size_t)page->aral_lock.free_elements,
393
+ (size_t)page->aral_lock.used_elements + (size_t)page->aral_lock.free_elements
394
+ );
395
+
396
+ ar->aral_lock.user_malloc_operations++;
397
+
398
+ // acquire a slot for the caller
399
+ page->aral_lock.used_elements++;
400
+ if(--page->aral_lock.free_elements == 0) {
401
+ // we are done with this page
402
+ // move the full page last
403
+ // so that pages with free items remain first in the list
404
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
405
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
406
+ }
407
+
408
+ aral_unlock(ar);
409
+
410
+ return page;
411
+}
412
+
413
+void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
414
+
415
+ ARAL_PAGE *page = aral_acquire_a_free_slot(ar TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
416
+
417
+ netdata_spinlock_lock(&page->free.spinlock);
418
+
419
+ internal_fatal(!page->free.list,
420
+ "ARAL: '%s' free item to use, cannot be NULL.", ar->config.name);
421
+
422
+ internal_fatal(page->free.list->size < ar->config.element_size,
423
+ "ARAL: '%s' free item size %zu, cannot be smaller than %zu",
424
+ ar->config.name, page->free.list->size, ar->config.element_size);
425
+
426
+ ARAL_FREE *found_fr = page->free.list;
427
+
428
+ // check if the remaining size (after we use this slot) is not enough for another element
429
+ if(unlikely(found_fr->size - ar->config.element_size < ar->config.element_size)) {
430
+ // we can use the entire free space entry
431
+
432
+ page->free.list = found_fr->next;
433
+ }
434
+ else {
435
+ // we can split the free space entry
436
+
437
+ uint8_t *data = (uint8_t *)found_fr;
438
+ ARAL_FREE *fr = (ARAL_FREE *)&data[ar->config.element_size];
439
+ fr->size = found_fr->size - ar->config.element_size;
440
+
441
+ // link the free slot first in the page
442
+ fr->next = found_fr->next;
443
+ page->free.list = fr;
444
+
445
+ aral_free_validate_internal_check(ar, fr);
446
+ }
447
+
448
+ netdata_spinlock_unlock(&page->free.spinlock);
449
+
450
+ // put the page pointer after the element
451
+ uint8_t *data = (uint8_t *)found_fr;
452
+ ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->config.page_ptr_offset];
453
+ *page_ptr = page;
454
+
455
+ if(unlikely(ar->config.mmap.enabled))
456
+ __atomic_add_fetch(&aral_globals.atomic.mmap.used, ar->config.element_size, __ATOMIC_RELAXED);
457
+ else
458
+ __atomic_add_fetch(&aral_globals.atomic.malloc.used, ar->config.element_size, __ATOMIC_RELAXED);
459
+
460
+ return (void *)found_fr;
461
+}
462
+
463
+static inline ARAL_PAGE *aral_ptr_to_page___must_NOT_have_aral_lock(ARAL *ar, void *ptr) {
464
+ // given a data pointer we returned before,
465
+ // find the ARAL_PAGE it belongs to
466
+
467
+ uint8_t *data = (uint8_t *)ptr;
468
+ ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->config.page_ptr_offset];
469
+ ARAL_PAGE *page = *page_ptr;
470
+
471
+#ifdef NETDATA_INTERNAL_CHECKS
472
+ // make it NULL so that we will fail on double free
473
+ // do not enable this on production, because the MMAP file
474
+ // will need to be saved again!
475
+ *page_ptr = NULL;
476
+#endif
477
+
478
+#ifdef NETDATA_ARAL_INTERNAL_CHECKS
479
+ {
480
+ // find the page ptr belongs
481
+ ARAL_PAGE *page2 = find_page_with_allocation_internal_check(ar, ptr);
482
+
483
+ internal_fatal(page != page2,
484
+ "ARAL: '%s' page pointers do not match!",
485
+ ar->name);
486
+
487
+ internal_fatal(!page2,
488
+ "ARAL: '%s' free of pointer %p is not in ARAL address space.",
489
+ ar->name, ptr);
490
+ }
491
+#endif
492
+
493
+ internal_fatal(!page,
494
+ "ARAL: '%s' possible corruption or double free of pointer %p",
495
+ ar->config.name, ptr);
496
+
497
+ return page;
498
+}
499
+
500
+static void aral_defrag_sorted_page_position___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) {
501
+ ARAL_PAGE *tmp;
502
+
503
+ int action = 0; (void)action;
504
+ size_t move_later = 0, move_earlier = 0;
505
+
506
+ for(tmp = page->next ;
507
+ tmp && tmp->aral_lock.free_elements && tmp->aral_lock.free_elements < page->aral_lock.free_elements ;
508
+ tmp = tmp->next)
509
+ move_later++;
510
+
511
+ if(!tmp && page->next) {
512
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
513
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
514
+ action = 1;
515
+ }
516
+ else if(tmp != page->next) {
517
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
518
+ DOUBLE_LINKED_LIST_INSERT_ITEM_BEFORE_UNSAFE(ar->aral_lock.pages, tmp, page, prev, next);
519
+ action = 2;
520
+ }
521
+ else {
522
+ for(tmp = (page == ar->aral_lock.pages) ? NULL : page->prev ;
523
+ tmp && (!tmp->aral_lock.free_elements || tmp->aral_lock.free_elements > page->aral_lock.free_elements);
524
+ tmp = (tmp == ar->aral_lock.pages) ? NULL : tmp->prev)
525
+ move_earlier++;
526
+
527
+ if(!tmp) {
528
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
529
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
530
+ action = 3;
531
+ }
532
+ else if(tmp != page->prev){
533
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
534
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, tmp, page, prev, next);
535
+ action = 4;
536
+ }
537
+ }
538
+
539
+ ar->aral_lock.defragment_operations++;
540
+ ar->aral_lock.defragment_linked_list_traversals += move_earlier + move_later;
541
+
542
+ internal_fatal(page->next && page->next->aral_lock.free_elements && page->next->aral_lock.free_elements < page->aral_lock.free_elements,
543
+ "ARAL: '%s' item should be later in the list", ar->config.name);
544
+
545
+ internal_fatal(page != ar->aral_lock.pages && (!page->prev->aral_lock.free_elements || page->prev->aral_lock.free_elements > page->aral_lock.free_elements),
546
+ "ARAL: '%s' item should be earlier in the list", ar->config.name);
547
+}
548
+
549
+static inline void aral_move_page_with_free_list___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) {
550
+ if(unlikely(page == ar->aral_lock.pages))
551
+ // we are the first already
552
+ return;
553
+
554
+ if(likely(!ar->config.defragment)) {
555
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
556
+ aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ar, page);
557
+ }
558
+ else
559
+ aral_defrag_sorted_page_position___aral_lock_needed(ar, page);
560
+}
561
+
562
+void aral_freez_internal(ARAL *ar, void *ptr TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
563
+ if(unlikely(!ptr)) return;
564
+
565
+ // get the page pointer
566
+ ARAL_PAGE *page = aral_ptr_to_page___must_NOT_have_aral_lock(ar, ptr);
567
+
568
+ if(unlikely(ar->config.mmap.enabled))
569
+ __atomic_sub_fetch(&aral_globals.atomic.mmap.used, ar->config.element_size, __ATOMIC_RELAXED);
570
+ else
571
+ __atomic_sub_fetch(&aral_globals.atomic.malloc.used, ar->config.element_size, __ATOMIC_RELAXED);
572
+
573
+ // make this element available
574
+ ARAL_FREE *fr = (ARAL_FREE *)ptr;
575
+ fr->size = ar->config.element_size;
576
+
577
+ netdata_spinlock_lock(&page->free.spinlock);
578
+ fr->next = page->free.list;
579
+ page->free.list = fr;
580
+ netdata_spinlock_unlock(&page->free.spinlock);
581
+
582
+ aral_lock(ar);
583
+
584
+ internal_fatal(!page->aral_lock.used_elements,
585
+ "ARAL: '%s' pointer %p is inside a page without any active allocations.",
586
+ ar->config.name, ptr);
587
+
588
+ internal_fatal(page->max_elements != page->aral_lock.used_elements + page->aral_lock.free_elements,
589
+ "ARAL: '%s' page element counters do not match, "
590
+ "page says it can handle %zu elements, "
591
+ "but there are %zu used and %zu free items, "
592
+ "total %zu items",
593
+ ar->config.name,
594
+ (size_t)page->max_elements,
595
+ (size_t)page->aral_lock.used_elements, (size_t)page->aral_lock.free_elements,
596
+ (size_t)page->aral_lock.used_elements + (size_t)page->aral_lock.free_elements
597
+ );
598
+
599
+ page->aral_lock.used_elements--;
600
+ page->aral_lock.free_elements++;
601
+
602
+ ar->aral_lock.user_free_operations++;
603
+
604
+ // if the page is empty, release it
605
+ if(unlikely(!page->aral_lock.used_elements)) {
606
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
607
+ aral_unlock(ar);
608
+ aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
609
+ }
610
+ else {
611
+ aral_move_page_with_free_list___aral_lock_needed(ar, page);
612
+ aral_unlock(ar);
613
+ }
614
+}
615
+
616
+void aral_destroy_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
617
+ aral_lock(ar);
618
+
619
+ ARAL_PAGE *page;
620
+ while((page = ar->aral_lock.pages)) {
621
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, prev, next);
622
+ aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
623
+ }
624
+
625
+ aral_unlock(ar);
626
+ freez(ar);
627
+}
628
+
629
+ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_elements, size_t max_page_elements, const char *filename, char **cache_dir, bool mmap, bool lockless) {
630
+ ARAL *ar = callocz(1, sizeof(ARAL));
631
+ ar->config.requested_element_size = element_size;
632
+ ar->config.initial_page_elements = initial_page_elements;
633
+ ar->config.max_page_elements = max_page_elements;
634
+ ar->config.mmap.filename = filename;
635
+ ar->config.mmap.cache_dir = cache_dir;
636
+ ar->config.mmap.enabled = mmap;
637
+ ar->config.lockless = lockless;
638
+ ar->config.defragment = false;
639
+ strncpyz(ar->config.name, name, ARAL_MAX_NAME);
640
+ netdata_spinlock_init(&ar->aral_lock.spinlock);
641
+
642
+ long int page_size = sysconf(_SC_PAGE_SIZE);
643
+ if (unlikely(page_size == -1))
644
+ ar->config.natural_page_size = 4096;
645
+ else
646
+ ar->config.natural_page_size = page_size;
647
+
648
+ // we need to add a page pointer after the element
649
+ // so, first align the element size to the pointer size
650
+ ar->config.element_size = natural_alignment(ar->config.requested_element_size, sizeof(uintptr_t));
651
+
652
+ // then add the size of a pointer to it
653
+ ar->config.element_size += sizeof(uintptr_t);
654
+
655
+ // make sure it is at least what we need for an ARAL_FREE slot
656
+ if (ar->config.element_size < sizeof(ARAL_FREE))
657
+ ar->config.element_size = sizeof(ARAL_FREE);
658
+
659
+ // and finally align it to the natural alignment
660
+ ar->config.element_size = natural_alignment(ar->config.element_size, ARAL_NATURAL_ALIGNMENT);
661
+
662
+ // we write the page pointer just after each element
663
+ ar->config.page_ptr_offset = ar->config.element_size - sizeof(uintptr_t);
664
+
665
+ if(ar->config.requested_element_size + sizeof(uintptr_t) > ar->config.element_size)
666
+ fatal("ARAL: '%s' failed to calculate properly page_ptr_offset: "
667
+ "element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, "
668
+ "final element size %zu, page_ptr_offset %zu",
669
+ ar->config.name, ar->config.requested_element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT,
670
+ ar->config.element_size, ar->config.page_ptr_offset);
671
+
672
+ //info("ARAL: element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, final element size %zu, page_ptr_offset %zu",
673
+ // ar->element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT, ar->internal.element_size, ar->internal.page_ptr_offset);
674
+
675
+
676
+ if (ar->config.initial_page_elements < 2)
677
+ ar->config.initial_page_elements = 2;
678
+
679
+ if(ar->config.mmap.enabled && (!ar->config.mmap.cache_dir || !*ar->config.mmap.cache_dir)) {
680
+ error("ARAL: '%s' mmap cache directory is not configured properly, disabling mmap.", ar->config.name);
681
+ ar->config.mmap.enabled = false;
682
+ internal_fatal(true, "ARAL: '%s' mmap cache directory is not configured properly", ar->config.name);
683
+ }
684
+
685
+ uint64_t max_alloc_size;
686
+ if(!ar->config.max_page_elements)
687
+ max_alloc_size = ar->config.mmap.enabled ? ARAL_MAX_PAGE_SIZE_MMAP : ARAL_MAX_PAGE_SIZE_MALLOC;
688
+ else
689
+ max_alloc_size = ar->config.max_page_elements * ar->config.element_size;
690
+
691
+ ar->config.max_allocation_size = aral_align_alloc_size(ar, max_alloc_size);
692
+ ar->adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->config.element_size * ar->config.initial_page_elements);
693
+ ar->aral_lock.pages = NULL;
694
+ ar->aral_lock.file_number = 0;
695
+
696
+ if(ar->config.mmap.enabled) {
697
+ char directory_name[FILENAME_MAX + 1];
698
+ snprintfz(directory_name, FILENAME_MAX, "%s/array_alloc.mmap", *ar->config.mmap.cache_dir);
699
+ int r = mkdir(directory_name, 0775);
700
+ if (r != 0 && errno != EEXIST)
701
+ fatal("Cannot create directory '%s'", directory_name);
702
+
703
+ char file[FILENAME_MAX + 1];
704
+ snprintfz(file, FILENAME_MAX, "%s.", ar->config.mmap.filename);
705
+ aral_delete_leftover_files(ar->config.name, directory_name, file);
706
+ }
707
+
708
+ internal_error(true,
709
+ "ARAL: '%s' "
710
+ "element size %zu (requested %zu bytes), "
711
+ "min elements per page %zu (requested %zu), "
712
+ "max elements per page %zu (requested %zu), "
713
+ "max page size %zu bytes, "
714
+ , ar->config.name
715
+ , ar->config.element_size, ar->config.requested_element_size
716
+ , ar->adders.allocation_size / ar->config.element_size, ar->config.initial_page_elements
717
+ , ar->config.max_allocation_size / ar->config.element_size, ar->config.max_page_elements
718
+ , ar->config.max_allocation_size
719
+ );
720
+
721
+ __atomic_add_fetch(&aral_globals.atomic.structures.allocations, 1, __ATOMIC_RELAXED);
722
+ __atomic_add_fetch(&aral_globals.atomic.structures.allocated, sizeof(ARAL), __ATOMIC_RELAXED);
723
+ return ar;
724
+}
725
+
726
+// ----------------------------------------------------------------------------
727
+// unittest
728
+
729
+struct aral_unittest_config {
730
+ bool single_threaded;
731
+ bool stop;
732
+ ARAL *ar;
733
+ size_t elements;
734
+ size_t threads;
735
+ int errors;
736
+};
737
+
738
+static void *aral_test_thread(void *ptr) {
739
+ struct aral_unittest_config *auc = ptr;
740
+ ARAL *ar = auc->ar;
741
+ size_t elements = auc->elements;
742
+
743
+ void **pointers = callocz(elements, sizeof(void *));
744
+
745
+ do {
746
+ for (size_t i = 0; i < elements; i++) {
747
+ pointers[i] = aral_mallocz(ar);
748
+ }
749
+
750
+ for (size_t div = 5; div >= 2; div--) {
751
+ for (size_t i = 0; i < elements / div; i++) {
752
+ aral_freez(ar, pointers[i]);
753
+ pointers[i] = NULL;
754
+ }
755
+
756
+ for (size_t i = 0; i < elements / div; i++) {
757
+ pointers[i] = aral_mallocz(ar);
758
+ }
759
+ }
760
+
761
+ for (size_t step = 50; step >= 10; step -= 10) {
762
+ for (size_t i = 0; i < elements; i += step) {
763
+ aral_freez(ar, pointers[i]);
764
+ pointers[i] = NULL;
765
+ }
766
+
767
+ for (size_t i = 0; i < elements; i += step) {
768
+ pointers[i] = aral_mallocz(ar);
769
+ }
770
+ }
771
+
772
+ for (size_t i = 0; i < elements; i++) {
773
+ aral_freez(ar, pointers[i]);
774
+ pointers[i] = NULL;
775
+ }
776
+
777
+ if (auc->single_threaded && ar->aral_lock.pages) {
778
+ fprintf(stderr, "\n\nARAL leftovers detected (1)\n\n");
779
+ __atomic_add_fetch(&auc->errors, 1, __ATOMIC_RELAXED);
780
+ }
781
+
782
+ if(!auc->single_threaded && __atomic_load_n(&auc->stop, __ATOMIC_RELAXED))
783
+ break;
784
+
785
+ for (size_t i = 0; i < elements; i++) {
786
+ pointers[i] = aral_mallocz(ar);
787
+ }
788
+
789
+ size_t increment = elements / ar->config.max_page_elements;
790
+ for (size_t all = increment; all <= elements / 2; all += increment) {
791
+
792
+ size_t to_free = all % ar->config.max_page_elements;
793
+ size_t step = elements / to_free;
794
+ if(!step) step = 1;
795
+
796
+ // fprintf(stderr, "all %zu, to free %zu, step %zu\n", all, to_free, step);
797
+
798
+ size_t free_list[to_free];
799
+ for (size_t i = 0; i < to_free; i++) {
800
+ size_t pos = step * i;
801
+ aral_freez(ar, pointers[pos]);
802
+ pointers[pos] = NULL;
803
+ free_list[i] = pos;
804
+ }
805
+
806
+ for (size_t i = 0; i < to_free; i++) {
807
+ size_t pos = free_list[i];
808
+ pointers[pos] = aral_mallocz(ar);
809
+ }
810
+ }
811
+
812
+ for (size_t i = 0; i < elements; i++) {
813
+ aral_freez(ar, pointers[i]);
814
+ pointers[i] = NULL;
815
+ }
816
+
817
+ if (auc->single_threaded && ar->aral_lock.pages) {
818
+ fprintf(stderr, "\n\nARAL leftovers detected (2)\n\n");
819
+ __atomic_add_fetch(&auc->errors, 1, __ATOMIC_RELAXED);
820
+ }
821
+
822
+ } while(!auc->single_threaded && !__atomic_load_n(&auc->stop, __ATOMIC_RELAXED));
823
+
824
+ freez(pointers);
825
+
826
+ return ptr;
827
+}
828
+
829
+int aral_stress_test(size_t threads, size_t elements, size_t seconds) {
830
+ fprintf(stderr, "Running stress test of %zu threads, with %zu elements each, for %zu seconds...\n",
831
+ threads, elements, seconds);
832
+
833
+ memset(&aral_globals, 0, sizeof(aral_globals));
834
+
835
+ struct aral_unittest_config auc = {
836
+ .single_threaded = false,
837
+ .threads = threads,
838
+ .ar = aral_create("aral-test", 20, 10, 1024, "test-aral", NULL, false, false),
839
+ .elements = elements,
840
+ .errors = 0,
841
+ };
842
+
843
+ usec_t started_ut = now_monotonic_usec();
844
+ netdata_thread_t thread_ptrs[threads];
845
+
846
+ for(size_t i = 0; i < threads ; i++) {
847
+ char tag[NETDATA_THREAD_NAME_MAX + 1];
848
+ snprintfz(tag, NETDATA_THREAD_NAME_MAX, "TH[%zu]", i);
849
+ netdata_thread_create(&thread_ptrs[i], tag,
850
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
851
+ aral_test_thread, &auc);
852
+ }
853
+
854
+ size_t malloc_done = 0;
855
+ size_t free_done = 0;
856
+ size_t countdown = seconds;
857
+ while(countdown-- > 0) {
858
+ sleep_usec(1 * USEC_PER_SEC);
859
+ aral_lock(auc.ar);
860
+ size_t m = auc.ar->aral_lock.user_malloc_operations;
861
+ size_t f = auc.ar->aral_lock.user_free_operations;
862
+ aral_unlock(auc.ar);
863
+ fprintf(stderr, "ARAL executes %0.2f M malloc and %0.2f M free operations/s\n",
864
+ (double)(m - malloc_done) / 1000000.0, (double)(f - free_done) / 1000000.0);
865
+ malloc_done = m;
866
+ free_done = f;
867
+ }
868
+
869
+ __atomic_store_n(&auc.stop, true, __ATOMIC_RELAXED);
870
+
871
+// fprintf(stderr, "Cancelling the threads...\n");
872
+// for(size_t i = 0; i < threads ; i++) {
873
+// netdata_thread_cancel(thread_ptrs[i]);
874
+// }
875
+
876
+ fprintf(stderr, "Waiting the threads to finish...\n");
877
+ for(size_t i = 0; i < threads ; i++) {
878
+ netdata_thread_join(thread_ptrs[i], NULL);
879
+ }
880
+
881
+ usec_t ended_ut = now_monotonic_usec();
882
+
883
+ if (auc.ar->aral_lock.pages) {
884
+ fprintf(stderr, "\n\nARAL leftovers detected (3)\n\n");
885
+ __atomic_add_fetch(&auc.errors, 1, __ATOMIC_RELAXED);
886
+ }
887
+
888
+ info("ARAL: did %zu malloc, %zu free, "
889
+ "using %zu threads, in %llu usecs",
890
+ auc.ar->aral_lock.user_malloc_operations,
891
+ auc.ar->aral_lock.user_free_operations,
892
+ threads,
893
+ ended_ut - started_ut);
894
+
895
+ aral_destroy(auc.ar);
896
+
897
+ return auc.errors;
898
+}
899
+
900
+int aral_unittest(size_t elements) {
901
+ char *cache_dir = "/tmp/";
902
+
903
+ struct aral_unittest_config auc = {
904
+ .single_threaded = true,
905
+ .threads = 1,
906
+ .ar = aral_create("aral-test", 20, 10, 1024, "test-aral", &cache_dir, false, false),
907
+ .elements = elements,
908
+ .errors = 0,
909
+ };
910
+
911
+ aral_test_thread(&auc);
912
+
913
+ aral_destroy(auc.ar);
914
+
915
+ int errors = aral_stress_test(2, elements, 5);
916
+
917
+ return auc.errors + errors;
918
+}