Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* Hash routine. |
3 | | * Copyright (C) 1998 Kunihiro Ishiguro |
4 | | */ |
5 | | |
6 | | #include <zebra.h> |
7 | | #include <math.h> |
8 | | |
9 | | #include "hash.h" |
10 | | #include "memory.h" |
11 | | #include "linklist.h" |
12 | | #include "termtable.h" |
13 | | #include "vty.h" |
14 | | #include "command.h" |
15 | | #include "libfrr.h" |
16 | | #include "frr_pthread.h" |
17 | | #include "libfrr_trace.h" |
18 | | |
19 | 8 | DEFINE_MTYPE_STATIC(LIB, HASH, "Hash"); |
20 | 8 | DEFINE_MTYPE_STATIC(LIB, HASH_BUCKET, "Hash Bucket"); |
21 | 8 | DEFINE_MTYPE_STATIC(LIB, HASH_INDEX, "Hash Index"); |
22 | 8 | |
23 | 8 | static pthread_mutex_t _hashes_mtx = PTHREAD_MUTEX_INITIALIZER; |
24 | 8 | static struct list *_hashes; |
25 | 8 | |
26 | 8 | struct hash *hash_create_size(unsigned int size, |
27 | 8 | unsigned int (*hash_key)(const void *), |
28 | 8 | bool (*hash_cmp)(const void *, const void *), |
29 | 8 | const char *name) |
30 | 57.4k | { |
31 | 57.4k | struct hash *hash; |
32 | | |
33 | 57.4k | assert((size & (size - 1)) == 0); |
34 | 57.4k | hash = XCALLOC(MTYPE_HASH, sizeof(struct hash)); |
35 | 57.4k | hash->index = |
36 | 57.4k | XCALLOC(MTYPE_HASH_INDEX, sizeof(struct hash_bucket *) * size); |
37 | 57.4k | hash->size = size; |
38 | 57.4k | hash->hash_key = hash_key; |
39 | 57.4k | hash->hash_cmp = hash_cmp; |
40 | 57.4k | hash->count = 0; |
41 | 57.4k | hash->name = name ? XSTRDUP(MTYPE_HASH, name) : NULL; |
42 | 57.4k | hash->stats.empty = hash->size; |
43 | | |
44 | 57.4k | frr_with_mutex (&_hashes_mtx) { |
45 | 57.4k | if (!_hashes) |
46 | 4 | _hashes = list_new(); |
47 | | |
48 | 57.4k | listnode_add(_hashes, hash); |
49 | 57.4k | } |
50 | | |
51 | 57.4k | return hash; |
52 | 57.4k | } |
53 | | |
54 | | struct hash *hash_create(unsigned int (*hash_key)(const void *), |
55 | | bool (*hash_cmp)(const void *, const void *), |
56 | | const char *name) |
57 | 49 | { |
58 | 49 | return hash_create_size(HASH_INITIAL_SIZE, hash_key, hash_cmp, name); |
59 | 49 | } |
60 | | |
61 | | void *hash_alloc_intern(void *arg) |
62 | 82.5k | { |
63 | 82.5k | return arg; |
64 | 82.5k | } |
65 | | |
66 | | /* |
67 | | * ssq = ssq + (new^2 - old^2) |
68 | | * = ssq + ((new + old) * (new - old)) |
69 | | */ |
70 | | #define hash_update_ssq(hz, old, new) \ |
71 | 178k | do { \ |
72 | 178k | int _adjust = (new + old) * (new - old); \ |
73 | 178k | if (_adjust < 0) \ |
74 | 178k | atomic_fetch_sub_explicit(&hz->stats.ssq, -_adjust, \ |
75 | 84.8k | memory_order_relaxed); \ |
76 | 178k | else \ |
77 | 178k | atomic_fetch_add_explicit(&hz->stats.ssq, _adjust, \ |
78 | 93.7k | memory_order_relaxed); \ |
79 | 178k | } while (0) |
80 | | |
81 | | /* Expand hash if the chain length exceeds the threshold. */ |
82 | | static void hash_expand(struct hash *hash) |
83 | 32 | { |
84 | 32 | unsigned int i, new_size; |
85 | 32 | struct hash_bucket *hb, *hbnext, **new_index; |
86 | | |
87 | 32 | new_size = hash->size * 2; |
88 | | |
89 | 32 | if (hash->max_size && new_size > hash->max_size) |
90 | 0 | return; |
91 | | |
92 | 32 | new_index = XCALLOC(MTYPE_HASH_INDEX, |
93 | 32 | sizeof(struct hash_bucket *) * new_size); |
94 | | |
95 | 32 | hash->stats.empty = new_size; |
96 | | |
97 | 4.72k | for (i = 0; i < hash->size; i++) |
98 | 9.39k | for (hb = hash->index[i]; hb; hb = hbnext) { |
99 | 4.69k | unsigned int h = hb->key & (new_size - 1); |
100 | | |
101 | 4.69k | hbnext = hb->next; |
102 | 4.69k | hb->next = new_index[h]; |
103 | | |
104 | 4.69k | int oldlen = hb->next ? hb->next->len : 0; |
105 | 4.69k | int newlen = oldlen + 1; |
106 | | |
107 | 4.69k | if (newlen == 1) |
108 | 3.72k | hash->stats.empty--; |
109 | 976 | else |
110 | 976 | hb->next->len = 0; |
111 | | |
112 | 4.69k | hb->len = newlen; |
113 | | |
114 | 4.69k | hash_update_ssq(hash, oldlen, newlen); |
115 | | |
116 | 4.69k | new_index[h] = hb; |
117 | 4.69k | } |
118 | | |
119 | | /* Switch to new table */ |
120 | 32 | XFREE(MTYPE_HASH_INDEX, hash->index); |
121 | 32 | hash->size = new_size; |
122 | 32 | hash->index = new_index; |
123 | 32 | } |
124 | | |
125 | | void *hash_get(struct hash *hash, void *data, void *(*alloc_func)(void *)) |
126 | 541k | { |
127 | 541k | frrtrace(2, frr_libfrr, hash_get, hash, data); |
128 | | |
129 | 541k | unsigned int key; |
130 | 541k | unsigned int index; |
131 | 541k | void *newdata; |
132 | 541k | struct hash_bucket *bucket; |
133 | | |
134 | 541k | if (!alloc_func && !hash->count) |
135 | 74.3k | return NULL; |
136 | | |
137 | 467k | key = (*hash->hash_key)(data); |
138 | 467k | index = key & (hash->size - 1); |
139 | | |
140 | 656k | for (bucket = hash->index[index]; bucket != NULL; |
141 | 528k | bucket = bucket->next) { |
142 | 528k | if (bucket->key == key && (*hash->hash_cmp)(bucket->data, data)) |
143 | 338k | return bucket->data; |
144 | 528k | } |
145 | | |
146 | 128k | if (alloc_func) { |
147 | 89.0k | newdata = (*alloc_func)(data); |
148 | 89.0k | if (newdata == NULL) |
149 | 0 | return NULL; |
150 | | |
151 | 89.0k | if (HASH_THRESHOLD(hash->count + 1, hash->size)) { |
152 | 32 | hash_expand(hash); |
153 | 32 | index = key & (hash->size - 1); |
154 | 32 | } |
155 | | |
156 | 89.0k | bucket = XCALLOC(MTYPE_HASH_BUCKET, sizeof(struct hash_bucket)); |
157 | 89.0k | bucket->data = newdata; |
158 | 89.0k | bucket->key = key; |
159 | 89.0k | bucket->next = hash->index[index]; |
160 | 89.0k | hash->index[index] = bucket; |
161 | 89.0k | hash->count++; |
162 | | |
163 | 89.0k | frrtrace(3, frr_libfrr, hash_insert, hash, data, key); |
164 | | |
165 | 89.0k | int oldlen = bucket->next ? bucket->next->len : 0; |
166 | 89.0k | int newlen = oldlen + 1; |
167 | | |
168 | 89.0k | if (newlen == 1) |
169 | 67.1k | hash->stats.empty--; |
170 | 21.9k | else |
171 | 21.9k | bucket->next->len = 0; |
172 | | |
173 | 89.0k | bucket->len = newlen; |
174 | | |
175 | 89.0k | hash_update_ssq(hash, oldlen, newlen); |
176 | | |
177 | 89.0k | return bucket->data; |
178 | 89.0k | } |
179 | 39.2k | return NULL; |
180 | 128k | } |
181 | | |
182 | | void *hash_lookup(struct hash *hash, void *data) |
183 | 417k | { |
184 | 417k | return hash_get(hash, data, NULL); |
185 | 417k | } |
186 | | |
187 | | unsigned int string_hash_make(const char *str) |
188 | 0 | { |
189 | 0 | unsigned int hash = 0; |
190 | |
|
191 | 0 | while (*str) |
192 | 0 | hash = (hash * 33) ^ (unsigned int)*str++; |
193 | |
|
194 | 0 | return hash; |
195 | 0 | } |
196 | | |
197 | | void *hash_release(struct hash *hash, void *data) |
198 | 87.6k | { |
199 | 87.6k | void *ret = NULL; |
200 | 87.6k | unsigned int key; |
201 | 87.6k | unsigned int index; |
202 | 87.6k | struct hash_bucket *bucket; |
203 | 87.6k | struct hash_bucket *pp; |
204 | | |
205 | 87.6k | key = (*hash->hash_key)(data); |
206 | 87.6k | index = key & (hash->size - 1); |
207 | | |
208 | 90.2k | for (bucket = pp = hash->index[index]; bucket; bucket = bucket->next) { |
209 | 87.4k | if (bucket->key == key |
210 | 84.8k | && (*hash->hash_cmp)(bucket->data, data)) { |
211 | 84.8k | int oldlen = hash->index[index]->len; |
212 | 84.8k | int newlen = oldlen - 1; |
213 | | |
214 | 84.8k | if (bucket == pp) |
215 | 82.4k | hash->index[index] = bucket->next; |
216 | 2.40k | else |
217 | 2.40k | pp->next = bucket->next; |
218 | | |
219 | 84.8k | if (hash->index[index]) |
220 | 20.0k | hash->index[index]->len = newlen; |
221 | 64.7k | else |
222 | 64.7k | hash->stats.empty++; |
223 | | |
224 | 84.8k | hash_update_ssq(hash, oldlen, newlen); |
225 | | |
226 | 84.8k | ret = bucket->data; |
227 | 84.8k | XFREE(MTYPE_HASH_BUCKET, bucket); |
228 | 84.8k | hash->count--; |
229 | 84.8k | break; |
230 | 84.8k | } |
231 | 2.59k | pp = bucket; |
232 | 2.59k | } |
233 | | |
234 | 87.6k | frrtrace(3, frr_libfrr, hash_release, hash, data, ret); |
235 | | |
236 | 87.6k | return ret; |
237 | 87.6k | } |
238 | | |
239 | | void hash_iterate(struct hash *hash, void (*func)(struct hash_bucket *, void *), |
240 | | void *arg) |
241 | 1.29k | { |
242 | 1.29k | unsigned int i; |
243 | 1.29k | struct hash_bucket *hb; |
244 | 1.29k | struct hash_bucket *hbnext; |
245 | | |
246 | 52.6k | for (i = 0; i < hash->size; i++) |
247 | 54.3k | for (hb = hash->index[i]; hb; hb = hbnext) { |
248 | | /* get pointer to next hash bucket here, in case (*func) |
249 | | * decides to delete hb by calling hash_release |
250 | | */ |
251 | 3.04k | hbnext = hb->next; |
252 | 3.04k | (*func)(hb, arg); |
253 | 3.04k | } |
254 | 1.29k | } |
255 | | |
256 | | void hash_walk(struct hash *hash, int (*func)(struct hash_bucket *, void *), |
257 | | void *arg) |
258 | 3.08k | { |
259 | 3.08k | unsigned int i; |
260 | 3.08k | struct hash_bucket *hb; |
261 | 3.08k | struct hash_bucket *hbnext; |
262 | 3.08k | int ret = HASHWALK_CONTINUE; |
263 | | |
264 | 141k | for (i = 0; i < hash->size; i++) { |
265 | 168k | for (hb = hash->index[i]; hb; hb = hbnext) { |
266 | | /* get pointer to next hash bucket here, in case (*func) |
267 | | * decides to delete hb by calling hash_release |
268 | | */ |
269 | 30.0k | hbnext = hb->next; |
270 | 30.0k | ret = (*func)(hb, arg); |
271 | 30.0k | if (ret == HASHWALK_ABORT) |
272 | 191 | return; |
273 | 30.0k | } |
274 | 138k | } |
275 | 3.08k | } |
276 | | |
277 | | void hash_clean(struct hash *hash, void (*free_func)(void *)) |
278 | 26.3k | { |
279 | 26.3k | unsigned int i; |
280 | 26.3k | struct hash_bucket *hb; |
281 | 26.3k | struct hash_bucket *next; |
282 | | |
283 | 868k | for (i = 0; i < hash->size; i++) { |
284 | 842k | for (hb = hash->index[i]; hb; hb = next) { |
285 | 0 | next = hb->next; |
286 | |
|
287 | 0 | if (free_func) |
288 | 0 | (*free_func)(hb->data); |
289 | |
|
290 | 0 | XFREE(MTYPE_HASH_BUCKET, hb); |
291 | 0 | hash->count--; |
292 | 0 | } |
293 | 842k | hash->index[i] = NULL; |
294 | 842k | } |
295 | | |
296 | 26.3k | hash->stats.ssq = 0; |
297 | 26.3k | hash->stats.empty = hash->size; |
298 | 26.3k | } |
299 | | |
300 | | void hash_clean_and_free(struct hash **hash, void (*free_func)(void *)) |
301 | 26.3k | { |
302 | 26.3k | if (!*hash) |
303 | 0 | return; |
304 | | |
305 | 26.3k | hash_clean(*hash, free_func); |
306 | 26.3k | hash_free(*hash); |
307 | 26.3k | *hash = NULL; |
308 | 26.3k | } |
309 | | |
310 | | static void hash_to_list_iter(struct hash_bucket *hb, void *arg) |
311 | 0 | { |
312 | 0 | struct list *list = arg; |
313 | |
|
314 | 0 | listnode_add(list, hb->data); |
315 | 0 | } |
316 | | |
317 | | struct list *hash_to_list(struct hash *hash) |
318 | 0 | { |
319 | 0 | struct list *list = list_new(); |
320 | |
|
321 | 0 | hash_iterate(hash, hash_to_list_iter, list); |
322 | 0 | return list; |
323 | 0 | } |
324 | | |
325 | | void hash_free(struct hash *hash) |
326 | 56.5k | { |
327 | 56.5k | frr_with_mutex (&_hashes_mtx) { |
328 | 56.5k | if (_hashes) { |
329 | 56.5k | listnode_delete(_hashes, hash); |
330 | 56.5k | if (_hashes->count == 0) { |
331 | 0 | list_delete(&_hashes); |
332 | 0 | } |
333 | 56.5k | } |
334 | 56.5k | } |
335 | | |
336 | 56.5k | XFREE(MTYPE_HASH, hash->name); |
337 | | |
338 | 56.5k | XFREE(MTYPE_HASH_INDEX, hash->index); |
339 | 56.5k | XFREE(MTYPE_HASH, hash); |
340 | 56.5k | } |
341 | | |
342 | | |
343 | | /* CLI commands ------------------------------------------------------------ */ |
344 | | |
345 | | DEFUN_NOSH(show_hash_stats, |
346 | | show_hash_stats_cmd, |
347 | | "show debugging hashtable [statistics]", |
348 | | SHOW_STR |
349 | | DEBUG_STR |
350 | | "Statistics about hash tables\n" |
351 | | "Statistics about hash tables\n") |
352 | 0 | { |
353 | 0 | struct hash *h; |
354 | 0 | struct listnode *ln; |
355 | 0 | struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); |
356 | |
|
357 | 0 | ttable_add_row(tt, "Hash table|Buckets|Entries|Empty|LF|SD|FLF|SD"); |
358 | 0 | tt->style.cell.lpad = 2; |
359 | 0 | tt->style.cell.rpad = 1; |
360 | 0 | tt->style.corner = '+'; |
361 | 0 | ttable_restyle(tt); |
362 | 0 | ttable_rowseps(tt, 0, BOTTOM, true, '-'); |
363 | | |
364 | | /* Summary statistics calculated are: |
365 | | * |
366 | | * - Load factor: This is the number of elements in the table divided |
367 | | * by the number of buckets. Since this hash table implementation |
368 | | * uses chaining, this value can be greater than 1. |
369 | | * This number provides information on how 'full' the table is, but |
370 | | * does not provide information on how evenly distributed the |
371 | | * elements are. |
372 | | * Notably, a load factor >= 1 does not imply that every bucket has |
373 | | * an element; with a pathological hash function, all elements could |
374 | | * be in a single bucket. |
375 | | * |
376 | | * - Full load factor: this is the number of elements in the table |
377 | | * divided by the number of buckets that have some elements in them. |
378 | | * |
379 | | * - Std. Dev.: This is the standard deviation calculated from the |
380 | | * relevant load factor. If the load factor is the mean of number of |
381 | | * elements per bucket, the standard deviation measures how much any |
382 | | * particular bucket is likely to deviate from the mean. |
383 | | * As a rule of thumb this number should be less than 2, and ideally |
384 | | * <= 1 for optimal performance. A number larger than 3 generally |
385 | | * indicates a poor hash function. |
386 | | */ |
387 | |
|
388 | 0 | double lf; // load factor |
389 | 0 | double flf; // full load factor |
390 | 0 | double var; // overall variance |
391 | 0 | double fvar; // full variance |
392 | 0 | double stdv; // overall stddev |
393 | 0 | double fstdv; // full stddev |
394 | |
|
395 | 0 | long double x2; // h->count ^ 2 |
396 | 0 | long double ldc; // (long double) h->count |
397 | 0 | long double full; // h->size - h->stats.empty |
398 | 0 | long double ssq; // ssq casted to long double |
399 | |
|
400 | 0 | pthread_mutex_lock(&_hashes_mtx); |
401 | 0 | if (!_hashes) { |
402 | 0 | pthread_mutex_unlock(&_hashes_mtx); |
403 | 0 | ttable_del(tt); |
404 | 0 | vty_out(vty, "No hash tables in use.\n"); |
405 | 0 | return CMD_SUCCESS; |
406 | 0 | } |
407 | | |
408 | 0 | for (ALL_LIST_ELEMENTS_RO(_hashes, ln, h)) { |
409 | 0 | if (!h->name) |
410 | 0 | continue; |
411 | | |
412 | 0 | ssq = (long double)h->stats.ssq; |
413 | 0 | x2 = h->count * h->count; |
414 | 0 | ldc = (long double)h->count; |
415 | 0 | full = h->size - h->stats.empty; |
416 | 0 | lf = h->count / (double)h->size; |
417 | 0 | flf = full ? h->count / (double)(full) : 0; |
418 | 0 | var = ldc ? (1.0 / ldc) * (ssq - x2 / ldc) : 0; |
419 | 0 | fvar = full ? (1.0 / full) * (ssq - x2 / full) : 0; |
420 | 0 | var = (var < .0001) ? 0 : var; |
421 | 0 | fvar = (fvar < .0001) ? 0 : fvar; |
422 | 0 | stdv = sqrt(var); |
423 | 0 | fstdv = sqrt(fvar); |
424 | |
|
425 | 0 | ttable_add_row(tt, "%s|%d|%ld|%.0f%%|%.2lf|%.2lf|%.2lf|%.2lf", |
426 | 0 | h->name, h->size, h->count, |
427 | 0 | (h->stats.empty / (double)h->size) * 100, lf, |
428 | 0 | stdv, flf, fstdv); |
429 | 0 | } |
430 | 0 | pthread_mutex_unlock(&_hashes_mtx); |
431 | | |
432 | | /* display header */ |
433 | 0 | char header[] = "Showing hash table statistics for "; |
434 | 0 | char underln[sizeof(header) + strlen(frr_protonameinst)]; |
435 | 0 | memset(underln, '-', sizeof(underln)); |
436 | 0 | underln[sizeof(underln) - 1] = '\0'; |
437 | 0 | vty_out(vty, "%s%s\n", header, frr_protonameinst); |
438 | 0 | vty_out(vty, "%s\n", underln); |
439 | |
|
440 | 0 | vty_out(vty, "# allocated: %d\n", _hashes->count); |
441 | 0 | vty_out(vty, "# named: %d\n\n", tt->nrows - 1); |
442 | |
|
443 | 0 | if (tt->nrows > 1) { |
444 | 0 | ttable_colseps(tt, 0, RIGHT, true, '|'); |
445 | 0 | char *table = ttable_dump(tt, "\n"); |
446 | 0 | vty_out(vty, "%s\n", table); |
447 | 0 | XFREE(MTYPE_TMP, table); |
448 | 0 | } else |
449 | 0 | vty_out(vty, "No named hash tables to display.\n"); |
450 | |
|
451 | 0 | ttable_del(tt); |
452 | |
|
453 | 0 | return CMD_SUCCESS; |
454 | 0 | } |
455 | | |
456 | | void hash_cmd_init(void) |
457 | 4 | { |
458 | 4 | install_element(ENABLE_NODE, &show_hash_stats_cmd); |
459 | 4 | } |