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");