@cryptotaxi247 / netdata-1 / commits / 10dbdc979

facets: minimize hashtable collisions (#16215)

* minimize hashtable collisions * fix fstat caching when enable/disable is not called

Costa Tsaousis committed Oct 16, 2023 at 22:04 UTC 10dbdc979895c4cbef692e446736c3baf1c92d92
2 files changed +97 -36
collectors/systemd-journal.plugin/systemd-journal.c
+27 -7
@@ -83,12 +83,11 @@ int fstat64(int fd, struct stat64 *buf) {
83
84 int ret = real_fstat(fd, buf);
85
86 - if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled) {
86 + if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled && fstat64_cache[fd].session == fstat_caching_thread_session) {
87 fstat64_cache[fd].ret = ret;
88 fstat64_cache[fd].updated = true;
89 fstat64_cache[fd].err_no = errno;
90 fstat64_cache[fd].stat = *buf;
91 - fstat64_cache[fd].session = fstat_caching_thread_session;
91 }
92
93 return ret;
@@ -718,6 +717,7 @@ static ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_one_file(
717 };
718
719 if(sd_journal_open_files(&j, paths, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
720 + netdata_log_error("JOURNAL: cannot open file '%s' for query", filename);
721 fstat_cache_disable_on_thread();
722 return ND_SD_JOURNAL_FAILED_TO_OPEN;
723 }
@@ -787,6 +787,7 @@ static void journal_file_update_msg_ut(const char *filename, struct journal_file
787
788 sd_journal *j = NULL;
789 if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
790 + netdata_log_error("JOURNAL: cannot open file '%s' to update msg_ut", filename);
791 fstat_cache_disable_on_thread();
792
793 if(!jf->logged_failure) {
@@ -1878,16 +1879,35 @@ static void netdata_systemd_journal_transform_boot_id(FACETS *facets __maybe_unu
1879 };
1880
1881 sd_journal *j = NULL;
1881 - if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j)
1882 + if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
1883 + internal_error(true, "JOURNAL: cannot open file '%s' to get boot_id", jf_dfe.name);
1884 continue;
1885 + }
1886
1887 char m[100];
1888 size_t len = snprintfz(m, sizeof(m), "_BOOT_ID=%s", boot_id);
1889 +
1890 + if(sd_journal_add_match(j, m, len) < 0) {
1891 + internal_error(true, "JOURNAL: cannot add match '%s' to file '%s'", m, jf_dfe.name);
1892 + sd_journal_close(j);
1893 + continue;
1894 + }
1895 +
1896 + if(sd_journal_seek_head(j) < 0) {
1897 + internal_error(true, "JOURNAL: cannot seek head to file '%s'", jf_dfe.name);
1898 + sd_journal_close(j);
1899 + continue;
1900 + }
1901 +
1902 + if(sd_journal_next(j) < 0) {
1903 + internal_error(true, "JOURNAL: cannot get next of file '%s'", jf_dfe.name);
1904 + sd_journal_close(j);
1905 + continue;
1906 + }
1907 +
1908 usec_t t_ut = 0;
1887 - if(sd_journal_add_match(j, m, len) < 0 ||
1888 - sd_journal_seek_head(j) < 0 ||
1889 - sd_journal_next(j) < 0 ||
1890 - sd_journal_get_realtime_usec(j, &t_ut) < 0 || !t_ut) {
1909 + if(sd_journal_get_realtime_usec(j, &t_ut) < 0 || !t_ut) {
1910 + internal_error(true, "JOURNAL: cannot get realtime_usec of file '%s'", jf_dfe.name);
1911 sd_journal_close(j);
1912 continue;
1913 }
libnetdata/facets/facets.c
+70 -29
@@ -5,8 +5,8 @@
5 #define FACETS_KEYS_WITH_VALUES_MAX 200 // the max number of keys that can be facets
6 #define FACETS_KEYS_IN_ROW_MAX 500 // the max number of keys in a row
7
8 -#define FACETS_KEYS_HASHTABLE_ENTRIES 127
9 -#define FACETS_VALUES_HASHTABLE_ENTRIES 31
8 +#define FACETS_KEYS_HASHTABLE_ENTRIES 15
9 +#define FACETS_VALUES_HASHTABLE_ENTRIES 15
10
11 // ----------------------------------------------------------------------------
12
@@ -30,10 +30,6 @@ static const uint8_t id_encoding_characters_reverse[256] = {
30 ['6'] = 60, ['7'] = 61, ['8'] = 62, ['9'] = 63
31 };
32
33 -__attribute__((constructor)) void initialize_facets_id_encoding_characters_reverse(void) {
34 -
35 -}
36 -
33 #define FACET_STRING_HASH_SIZE 12
34 #define FACETS_HASH XXH64_hash_t
35 #define FACETS_HASH_FUNCTION(src, len) XXH3_64bits(src, len)
@@ -109,11 +105,17 @@ typedef struct simple_hashtable_slot {
105 } SIMPLE_HASHTABLE_SLOT;
106
107 typedef struct simple_hashtable {
108 + size_t resizes;
109 + size_t searches;
110 + size_t collisions;
111 + size_t used;
112 size_t size;
113 SIMPLE_HASHTABLE_SLOT *hashtable;
114 } SIMPLE_HASHTABLE;
115
116 static void simple_hashtable_init(SIMPLE_HASHTABLE *ht, size_t size) {
117 + ht->resizes = 0;
118 + ht->used = 0;
119 ht->size = size;
120 ht->hashtable = callocz(ht->size, sizeof(*ht->hashtable));
121 }
@@ -122,22 +124,42 @@ static void simple_hashtable_free(SIMPLE_HASHTABLE *ht) {
124 freez(ht->hashtable);
125 ht->hashtable = NULL;
126 ht->size = 0;
127 + ht->used = 0;
128 + ht->resizes = 0;
129 }
130
127 -static inline SIMPLE_HASHTABLE_SLOT *simple_hashtable_get_slot(SIMPLE_HASHTABLE *ht, SIMPLE_HASHTABLE_HASH hash) {
131 +static void simple_hashtable_resize_double(SIMPLE_HASHTABLE *ht);
132 +
133 +static inline SIMPLE_HASHTABLE_SLOT *simple_hashtable_get_slot(SIMPLE_HASHTABLE *ht, SIMPLE_HASHTABLE_HASH hash, bool resize) {
134 // IMPORTANT:
135 // If the hashtable supported deletions, we would need to have a special slot.data value
136 // to mark deleted values and assume they are occupied during lookup, but empty during insert.
137 // But for our case, we don't need it, since we never delete items from the hashtable.
138
139 + ht->searches++;
140 +
141 size_t slot = hash % ht->size;
134 - if(!ht->hashtable[slot].data || ht->hashtable[slot].hash == hash)
142 + if(likely(!ht->hashtable[slot].data || ht->hashtable[slot].hash == hash))
143 return &ht->hashtable[slot];
144
145 + ht->collisions++;
146 +
147 + if(unlikely(resize && ht->size <= (ht->used << 4))) {
148 + simple_hashtable_resize_double(ht);
149 +
150 + slot = hash % ht->size;
151 + if(likely(!ht->hashtable[slot].data || ht->hashtable[slot].hash == hash))
152 + return &ht->hashtable[slot];
153 +
154 + ht->collisions++;
155 + }
156 +
157 slot = ((hash >> SIMPLE_HASHTABLE_HASH_SECOND_HASH_SHIFTS) + 1) % ht->size;
158 // Linear probing until we find it
139 - while (ht->hashtable[slot].data && ht->hashtable[slot].hash != hash)
159 + while (ht->hashtable[slot].data && ht->hashtable[slot].hash != hash) {
160 slot = (slot + 1) % ht->size; // Wrap around if necessary
161 + ht->collisions++;
162 + }
163
164 return &ht->hashtable[slot];
165 }
@@ -146,13 +168,14 @@ static void simple_hashtable_resize_double(SIMPLE_HASHTABLE *ht) {
168 SIMPLE_HASHTABLE_SLOT *old = ht->hashtable;
169 size_t old_size = ht->size;
170
149 - ht->size = (ht->size * 2) + 1;
171 + ht->resizes++;
172 + ht->size = (ht->size << 3) - 1;
173 ht->hashtable = callocz(ht->size, sizeof(*ht->hashtable));
174 for(size_t i = 0 ; i < old_size ; i++) {
175 if(!old[i].data)
176 continue;
177
155 - SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(ht, old[i].hash);
178 + SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(ht, old[i].hash, false);
179 *slot = old[i];
180 }
181
@@ -330,7 +353,6 @@ struct facets {
353 struct {
354 size_t registered;
355 size_t unique;
333 - size_t hashtable_increases;
356 } keys;
357
358 struct {
@@ -341,7 +363,6 @@ struct facets {
363 size_t indexed;
364 size_t inserts;
365 size_t conflicts;
344 - size_t hashtable_increases;
366 } values;
367
368 struct {
@@ -448,12 +469,12 @@ static inline void FACET_VALUE_ADD_CONFLICT(FACET_KEY *k, FACET_VALUE *v, const
469 }
470
471 static inline FACET_VALUE *FACET_VALUE_GET_FROM_INDEX(FACET_KEY *k, FACETS_HASH hash) {
451 - SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&k->values.ht, hash);
472 + SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&k->values.ht, hash, true);
473 return slot->data;
474 }
475
476 static inline FACET_VALUE *FACET_VALUE_ADD_TO_INDEX(FACET_KEY *k, const FACET_VALUE * const tv) {
456 - SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&k->values.ht, tv->hash);
477 + SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&k->values.ht, tv->hash, true);
478
479 if(slot->data) {
480 // already exists
@@ -468,6 +489,7 @@ static inline FACET_VALUE *FACET_VALUE_ADD_TO_INDEX(FACET_KEY *k, const FACET_VA
489 FACET_VALUE *v = mallocz(sizeof(*v));
490 slot->hash = tv->hash;
491 slot->data = v;
492 + k->values.ht.used++;
493
494 memcpy(v, tv, sizeof(*v));
495
@@ -489,11 +511,6 @@ static inline FACET_VALUE *FACET_VALUE_ADD_TO_INDEX(FACET_KEY *k, const FACET_VA
511
512 k->facets->operations.values.inserts++;
513
492 - if(unlikely(k->values.used > k->values.ht.size / 2)) {
493 - simple_hashtable_resize_double(&k->values.ht);
494 - k->facets->operations.values.hashtable_increases++;
495 - }
496 -
514 return v;
515 }
516
@@ -589,7 +606,7 @@ static inline void FACETS_KEYS_INDEX_DESTROY(FACETS *facets) {
606 }
607
608 static inline FACET_KEY *FACETS_KEY_GET_FROM_INDEX(FACETS *facets, FACETS_HASH hash) {
592 - SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&facets->keys.ht, hash);
609 + SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&facets->keys.ht, hash, true);
610 return slot->data;
611 }
612
@@ -669,7 +686,7 @@ static inline FACET_KEY *FACETS_KEY_CREATE(FACETS *facets, FACETS_HASH hash, con
686 static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH hash, const char *name, size_t name_length, FACET_KEY_OPTIONS options) {
687 facets->operations.keys.registered++;
688
672 - SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&facets->keys.ht, hash);
689 + SIMPLE_HASHTABLE_SLOT *slot = simple_hashtable_get_slot(&facets->keys.ht, hash, true);
690
691 if(unlikely(!slot->data)) {
692 // we have to add it
@@ -677,11 +694,7 @@ static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH has
694
695 slot->hash = hash;
696 slot->data = k;
680 -
681 - if(facets->keys.count > facets->keys.ht.size / 2) {
682 - simple_hashtable_resize_double(&facets->keys.ht);
683 - facets->operations.keys.hashtable_increases++;
684 - }
697 + facets->keys.ht.used++;
698
699 return k;
700 }
@@ -2520,13 +2533,36 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2533 buffer_json_object_close(wb); // rows
2534 buffer_json_member_add_object(wb, "keys");
2535 {
2536 + size_t resizes = 0, searches = 0, collisions = 0, used = 0, size = 0, count = 0;
2537 + count++;
2538 + used += facets->keys.ht.used;
2539 + size += facets->keys.ht.size;
2540 + resizes += facets->keys.ht.resizes;
2541 + searches += facets->keys.ht.searches;
2542 + collisions += facets->keys.ht.collisions;
2543 +
2544 buffer_json_member_add_uint64(wb, "registered", facets->operations.keys.registered);
2545 buffer_json_member_add_uint64(wb, "unique", facets->operations.keys.unique);
2525 - buffer_json_member_add_uint64(wb, "hashtable_increases", facets->operations.keys.hashtable_increases);
2546 + buffer_json_member_add_uint64(wb, "hashtables", count);
2547 + buffer_json_member_add_uint64(wb, "hashtable_used", used);
2548 + buffer_json_member_add_uint64(wb, "hashtable_size", size);
2549 + buffer_json_member_add_uint64(wb, "hashtable_searches", searches);
2550 + buffer_json_member_add_uint64(wb, "hashtable_collisions", collisions);
2551 + buffer_json_member_add_uint64(wb, "hashtable_resizes", resizes);
2552 }
2553 buffer_json_object_close(wb); // keys
2554 buffer_json_member_add_object(wb, "values");
2555 {
2556 + size_t resizes = 0, searches = 0, collisions = 0, used = 0, size = 0, count = 0;
2557 + for(FACET_KEY *k = facets->keys.ll; k ; k = k->next) {
2558 + count++;
2559 + used += k->values.ht.used;
2560 + size += k->values.ht.size;
2561 + resizes += k->values.ht.resizes;
2562 + searches += k->values.ht.searches;
2563 + collisions += k->values.ht.collisions;
2564 + }
2565 +
2566 buffer_json_member_add_uint64(wb, "registered", facets->operations.values.registered);
2567 buffer_json_member_add_uint64(wb, "transformed", facets->operations.values.transformed);
2568 buffer_json_member_add_uint64(wb, "dynamic", facets->operations.values.dynamic);
@@ -2534,7 +2570,12 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2570 buffer_json_member_add_uint64(wb, "indexed", facets->operations.values.indexed);
2571 buffer_json_member_add_uint64(wb, "inserts", facets->operations.values.inserts);
2572 buffer_json_member_add_uint64(wb, "conflicts", facets->operations.values.conflicts);
2537 - buffer_json_member_add_uint64(wb, "hashtable_increases", facets->operations.values.hashtable_increases);
2573 + buffer_json_member_add_uint64(wb, "hashtables", count);
2574 + buffer_json_member_add_uint64(wb, "hashtable_used", used);
2575 + buffer_json_member_add_uint64(wb, "hashtable_size", size);
2576 + buffer_json_member_add_uint64(wb, "hashtable_searches", searches);
2577 + buffer_json_member_add_uint64(wb, "hashtable_collisions", collisions);
2578 + buffer_json_member_add_uint64(wb, "hashtable_resizes", resizes);
2579 }
2580 buffer_json_object_close(wb); // values
2581 buffer_json_member_add_object(wb, "fts");