blob: cfc3440f17e7b16272555df0c310eb6a4743b867 [file] [log] [blame]
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +01001/*
2 * Resizable, Scalable, Concurrent Hash Table
3 *
Thomas Graf1aa661f2015-04-30 22:37:41 +00004 * Copyright (c) 2014-2015 Thomas Graf <tgraf@suug.ch>
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +01005 * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net>
6 *
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +01007 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12/**************************************************************************
13 * Self Test
14 **************************************************************************/
15
16#include <linux/init.h>
17#include <linux/jhash.h>
18#include <linux/kernel.h>
Phil Sutterf4a3e902015-08-15 00:37:15 +020019#include <linux/kthread.h>
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010020#include <linux/module.h>
21#include <linux/rcupdate.h>
22#include <linux/rhashtable.h>
Phil Sutterf4a3e902015-08-15 00:37:15 +020023#include <linux/semaphore.h>
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010024#include <linux/slab.h>
Thomas Graf685a0152015-07-17 10:52:48 +020025#include <linux/sched.h>
Phil Sutterf4a3e902015-08-15 00:37:15 +020026#include <linux/vmalloc.h>
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010027
Thomas Graf1aa661f2015-04-30 22:37:41 +000028#define MAX_ENTRIES 1000000
Thomas Graf67b7cbf2015-04-30 22:37:45 +000029#define TEST_INSERT_FAIL INT_MAX
Thomas Graf1aa661f2015-04-30 22:37:41 +000030
31static int entries = 50000;
32module_param(entries, int, 0);
33MODULE_PARM_DESC(entries, "Number of entries to add (default: 50000)");
34
35static int runs = 4;
36module_param(runs, int, 0);
37MODULE_PARM_DESC(runs, "Number of test runs per variant (default: 4)");
38
39static int max_size = 65536;
40module_param(max_size, int, 0);
41MODULE_PARM_DESC(runs, "Maximum table size (default: 65536)");
42
43static bool shrinking = false;
44module_param(shrinking, bool, 0);
45MODULE_PARM_DESC(shrinking, "Enable automatic shrinking (default: off)");
46
47static int size = 8;
48module_param(size, int, 0);
49MODULE_PARM_DESC(size, "Initial size hint of table (default: 8)");
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010050
Phil Sutterf4a3e902015-08-15 00:37:15 +020051static int tcount = 10;
52module_param(tcount, int, 0);
53MODULE_PARM_DESC(tcount, "Number of threads to spawn (default: 10)");
54
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010055struct test_obj {
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010056 int value;
57 struct rhash_head node;
58};
59
Phil Sutterf4a3e902015-08-15 00:37:15 +020060struct thread_data {
61 int id;
62 struct task_struct *task;
63 struct test_obj *objs;
64};
65
Thomas Graffcc57022015-04-30 22:37:43 +000066static struct test_obj array[MAX_ENTRIES];
67
Thomas Graf1aa661f2015-04-30 22:37:41 +000068static struct rhashtable_params test_rht_params = {
Herbert Xub182aa62015-03-20 21:57:04 +110069 .head_offset = offsetof(struct test_obj, node),
70 .key_offset = offsetof(struct test_obj, value),
71 .key_len = sizeof(int),
72 .hashfn = jhash,
Herbert Xub182aa62015-03-20 21:57:04 +110073 .nulls_base = (3U << RHT_BASE_SHIFT),
74};
75
Phil Sutterf4a3e902015-08-15 00:37:15 +020076static struct semaphore prestart_sem;
77static struct semaphore startup_sem = __SEMAPHORE_INITIALIZER(startup_sem, 0);
78
Phil Sutter9e9089e2015-11-20 18:17:18 +010079static int insert_retry(struct rhashtable *ht, struct rhash_head *obj,
80 const struct rhashtable_params params)
81{
82 int err, retries = -1;
83
84 do {
85 retries++;
86 cond_resched();
87 err = rhashtable_insert_fast(ht, obj, params);
88 } while (err == -EBUSY);
89
90 return err ? : retries;
91}
92
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010093static int __init test_rht_lookup(struct rhashtable *ht)
94{
95 unsigned int i;
96
Thomas Graf1aa661f2015-04-30 22:37:41 +000097 for (i = 0; i < entries * 2; i++) {
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +010098 struct test_obj *obj;
99 bool expected = !(i % 2);
100 u32 key = i;
101
Thomas Graf67b7cbf2015-04-30 22:37:45 +0000102 if (array[i / 2].value == TEST_INSERT_FAIL)
103 expected = false;
104
Herbert Xub182aa62015-03-20 21:57:04 +1100105 obj = rhashtable_lookup_fast(ht, &key, test_rht_params);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100106
107 if (expected && !obj) {
108 pr_warn("Test failed: Could not find key %u\n", key);
109 return -ENOENT;
110 } else if (!expected && obj) {
111 pr_warn("Test failed: Unexpected entry found for key %u\n",
112 key);
113 return -EEXIST;
114 } else if (expected && obj) {
Thomas Grafc2c8a902015-04-30 22:37:42 +0000115 if (obj->value != i) {
116 pr_warn("Test failed: Lookup value mismatch %u!=%u\n",
117 obj->value, i);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100118 return -EINVAL;
119 }
120 }
Thomas Graf685a0152015-07-17 10:52:48 +0200121
122 cond_resched_rcu();
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100123 }
124
125 return 0;
126}
127
Thomas Graf246b23a2015-04-30 22:37:44 +0000128static void test_bucket_stats(struct rhashtable *ht)
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100129{
Thomas Graf246b23a2015-04-30 22:37:44 +0000130 unsigned int err, total = 0, chain_len = 0;
131 struct rhashtable_iter hti;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100132 struct rhash_head *pos;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100133
Thomas Graf246b23a2015-04-30 22:37:44 +0000134 err = rhashtable_walk_init(ht, &hti);
135 if (err) {
136 pr_warn("Test failed: allocation error");
137 return;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100138 }
139
Thomas Graf246b23a2015-04-30 22:37:44 +0000140 err = rhashtable_walk_start(&hti);
141 if (err && err != -EAGAIN) {
142 pr_warn("Test failed: iterator failed: %d\n", err);
143 return;
144 }
145
146 while ((pos = rhashtable_walk_next(&hti))) {
147 if (PTR_ERR(pos) == -EAGAIN) {
148 pr_info("Info: encountered resize\n");
149 chain_len++;
150 continue;
151 } else if (IS_ERR(pos)) {
152 pr_warn("Test failed: rhashtable_walk_next() error: %ld\n",
153 PTR_ERR(pos));
154 break;
155 }
156
157 total++;
158 }
159
160 rhashtable_walk_stop(&hti);
161 rhashtable_walk_exit(&hti);
162
163 pr_info(" Traversal complete: counted=%u, nelems=%u, entries=%d, table-jumps=%u\n",
164 total, atomic_read(&ht->nelems), entries, chain_len);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100165
Thomas Graf1aa661f2015-04-30 22:37:41 +0000166 if (total != atomic_read(&ht->nelems) || total != entries)
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100167 pr_warn("Test failed: Total count mismatch ^^^");
168}
169
Thomas Graf1aa661f2015-04-30 22:37:41 +0000170static s64 __init test_rhashtable(struct rhashtable *ht)
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100171{
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100172 struct test_obj *obj;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100173 int err;
Phil Sutter9e9089e2015-11-20 18:17:18 +0100174 unsigned int i, insert_retries = 0;
Thomas Graf1aa661f2015-04-30 22:37:41 +0000175 s64 start, end;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100176
177 /*
178 * Insertion Test:
Thomas Graf1aa661f2015-04-30 22:37:41 +0000179 * Insert entries into table with all keys even numbers
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100180 */
Thomas Graf1aa661f2015-04-30 22:37:41 +0000181 pr_info(" Adding %d keys\n", entries);
182 start = ktime_get_ns();
183 for (i = 0; i < entries; i++) {
Thomas Graffcc57022015-04-30 22:37:43 +0000184 struct test_obj *obj = &array[i];
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100185
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100186 obj->value = i * 2;
Phil Sutter9e9089e2015-11-20 18:17:18 +0100187 err = insert_retry(ht, &obj->node, test_rht_params);
188 if (err > 0)
189 insert_retries += err;
190 else if (err)
Thomas Graffcc57022015-04-30 22:37:43 +0000191 return err;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100192 }
193
Phil Sutter9e9089e2015-11-20 18:17:18 +0100194 if (insert_retries)
195 pr_info(" %u insertions retried due to memory pressure\n",
196 insert_retries);
Thomas Graf67b7cbf2015-04-30 22:37:45 +0000197
Thomas Graf246b23a2015-04-30 22:37:44 +0000198 test_bucket_stats(ht);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100199 rcu_read_lock();
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100200 test_rht_lookup(ht);
201 rcu_read_unlock();
202
Thomas Graf246b23a2015-04-30 22:37:44 +0000203 test_bucket_stats(ht);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100204
Thomas Graf1aa661f2015-04-30 22:37:41 +0000205 pr_info(" Deleting %d keys\n", entries);
206 for (i = 0; i < entries; i++) {
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100207 u32 key = i * 2;
208
Thomas Graf67b7cbf2015-04-30 22:37:45 +0000209 if (array[i].value != TEST_INSERT_FAIL) {
210 obj = rhashtable_lookup_fast(ht, &key, test_rht_params);
211 BUG_ON(!obj);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100212
Thomas Graf67b7cbf2015-04-30 22:37:45 +0000213 rhashtable_remove_fast(ht, &obj->node, test_rht_params);
214 }
Thomas Graf685a0152015-07-17 10:52:48 +0200215
216 cond_resched();
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100217 }
218
Thomas Graf1aa661f2015-04-30 22:37:41 +0000219 end = ktime_get_ns();
220 pr_info(" Duration of test: %lld ns\n", end - start);
221
222 return end - start;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100223}
224
Daniel Borkmannb7f5e5c2015-02-20 21:14:21 +0100225static struct rhashtable ht;
226
Phil Sutterf4a3e902015-08-15 00:37:15 +0200227static int thread_lookup_test(struct thread_data *tdata)
228{
229 int i, err = 0;
230
231 for (i = 0; i < entries; i++) {
232 struct test_obj *obj;
233 int key = (tdata->id << 16) | i;
234
235 obj = rhashtable_lookup_fast(&ht, &key, test_rht_params);
236 if (obj && (tdata->objs[i].value == TEST_INSERT_FAIL)) {
237 pr_err(" found unexpected object %d\n", key);
238 err++;
239 } else if (!obj && (tdata->objs[i].value != TEST_INSERT_FAIL)) {
240 pr_err(" object %d not found!\n", key);
241 err++;
242 } else if (obj && (obj->value != key)) {
243 pr_err(" wrong object returned (got %d, expected %d)\n",
244 obj->value, key);
245 err++;
246 }
Phil Suttercd5b3182015-11-20 18:17:17 +0100247
248 cond_resched();
Phil Sutterf4a3e902015-08-15 00:37:15 +0200249 }
250 return err;
251}
252
253static int threadfunc(void *data)
254{
Phil Sutter9e9089e2015-11-20 18:17:18 +0100255 int i, step, err = 0, insert_retries = 0;
Phil Sutterf4a3e902015-08-15 00:37:15 +0200256 struct thread_data *tdata = data;
257
258 up(&prestart_sem);
259 if (down_interruptible(&startup_sem))
260 pr_err(" thread[%d]: down_interruptible failed\n", tdata->id);
261
262 for (i = 0; i < entries; i++) {
263 tdata->objs[i].value = (tdata->id << 16) | i;
Phil Sutter9e9089e2015-11-20 18:17:18 +0100264 err = insert_retry(&ht, &tdata->objs[i].node, test_rht_params);
265 if (err > 0) {
266 insert_retries += err;
Phil Sutterf4a3e902015-08-15 00:37:15 +0200267 } else if (err) {
268 pr_err(" thread[%d]: rhashtable_insert_fast failed\n",
269 tdata->id);
270 goto out;
271 }
272 }
Phil Sutter9e9089e2015-11-20 18:17:18 +0100273 if (insert_retries)
274 pr_info(" thread[%d]: %u insertions retried due to memory pressure\n",
275 tdata->id, insert_retries);
Phil Sutterf4a3e902015-08-15 00:37:15 +0200276
277 err = thread_lookup_test(tdata);
278 if (err) {
279 pr_err(" thread[%d]: rhashtable_lookup_test failed\n",
280 tdata->id);
281 goto out;
282 }
283
284 for (step = 10; step > 0; step--) {
285 for (i = 0; i < entries; i += step) {
286 if (tdata->objs[i].value == TEST_INSERT_FAIL)
287 continue;
288 err = rhashtable_remove_fast(&ht, &tdata->objs[i].node,
289 test_rht_params);
290 if (err) {
291 pr_err(" thread[%d]: rhashtable_remove_fast failed\n",
292 tdata->id);
293 goto out;
294 }
295 tdata->objs[i].value = TEST_INSERT_FAIL;
Phil Suttercd5b3182015-11-20 18:17:17 +0100296
297 cond_resched();
Phil Sutterf4a3e902015-08-15 00:37:15 +0200298 }
299 err = thread_lookup_test(tdata);
300 if (err) {
301 pr_err(" thread[%d]: rhashtable_lookup_test (2) failed\n",
302 tdata->id);
303 goto out;
304 }
305 }
306out:
307 while (!kthread_should_stop()) {
308 set_current_state(TASK_INTERRUPTIBLE);
309 schedule();
310 }
311 return err;
312}
313
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100314static int __init test_rht_init(void)
315{
Phil Sutterf4a3e902015-08-15 00:37:15 +0200316 int i, err, started_threads = 0, failed_threads = 0;
Thomas Graf1aa661f2015-04-30 22:37:41 +0000317 u64 total_time = 0;
Phil Sutterf4a3e902015-08-15 00:37:15 +0200318 struct thread_data *tdata;
319 struct test_obj *objs;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100320
Thomas Graf1aa661f2015-04-30 22:37:41 +0000321 entries = min(entries, MAX_ENTRIES);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100322
Thomas Graf1aa661f2015-04-30 22:37:41 +0000323 test_rht_params.automatic_shrinking = shrinking;
324 test_rht_params.max_size = max_size;
325 test_rht_params.nelem_hint = size;
326
327 pr_info("Running rhashtable test nelem=%d, max_size=%d, shrinking=%d\n",
328 size, max_size, shrinking);
329
330 for (i = 0; i < runs; i++) {
331 s64 time;
332
333 pr_info("Test %02d:\n", i);
Thomas Graffcc57022015-04-30 22:37:43 +0000334 memset(&array, 0, sizeof(array));
Thomas Graf1aa661f2015-04-30 22:37:41 +0000335 err = rhashtable_init(&ht, &test_rht_params);
336 if (err < 0) {
337 pr_warn("Test failed: Unable to initialize hashtable: %d\n",
338 err);
339 continue;
340 }
341
342 time = test_rhashtable(&ht);
343 rhashtable_destroy(&ht);
344 if (time < 0) {
345 pr_warn("Test failed: return code %lld\n", time);
346 return -EINVAL;
347 }
348
349 total_time += time;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100350 }
351
Thomas Graf6decd632015-05-05 02:27:02 +0200352 do_div(total_time, runs);
353 pr_info("Average test time: %llu\n", total_time);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100354
Phil Sutterf4a3e902015-08-15 00:37:15 +0200355 if (!tcount)
356 return 0;
357
358 pr_info("Testing concurrent rhashtable access from %d threads\n",
359 tcount);
360 sema_init(&prestart_sem, 1 - tcount);
361 tdata = vzalloc(tcount * sizeof(struct thread_data));
362 if (!tdata)
363 return -ENOMEM;
364 objs = vzalloc(tcount * entries * sizeof(struct test_obj));
365 if (!objs) {
366 vfree(tdata);
367 return -ENOMEM;
368 }
369
370 err = rhashtable_init(&ht, &test_rht_params);
371 if (err < 0) {
372 pr_warn("Test failed: Unable to initialize hashtable: %d\n",
373 err);
374 vfree(tdata);
375 vfree(objs);
376 return -EINVAL;
377 }
378 for (i = 0; i < tcount; i++) {
379 tdata[i].id = i;
380 tdata[i].objs = objs + i * entries;
381 tdata[i].task = kthread_run(threadfunc, &tdata[i],
382 "rhashtable_thrad[%d]", i);
383 if (IS_ERR(tdata[i].task))
384 pr_err(" kthread_run failed for thread %d\n", i);
385 else
386 started_threads++;
387 }
388 if (down_interruptible(&prestart_sem))
389 pr_err(" down interruptible failed\n");
390 for (i = 0; i < tcount; i++)
391 up(&startup_sem);
392 for (i = 0; i < tcount; i++) {
393 if (IS_ERR(tdata[i].task))
394 continue;
395 if ((err = kthread_stop(tdata[i].task))) {
396 pr_warn("Test failed: thread %d returned: %d\n",
397 i, err);
398 failed_threads++;
399 }
400 }
401 pr_info("Started %d threads, %d failed\n",
402 started_threads, failed_threads);
403 rhashtable_destroy(&ht);
404 vfree(tdata);
405 vfree(objs);
Thomas Graf1aa661f2015-04-30 22:37:41 +0000406 return 0;
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100407}
408
Daniel Borkmann6dd0c162015-02-20 00:53:39 +0100409static void __exit test_rht_exit(void)
410{
411}
412
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100413module_init(test_rht_init);
Daniel Borkmann6dd0c162015-02-20 00:53:39 +0100414module_exit(test_rht_exit);
Geert Uytterhoeven9d6dbe12015-01-29 15:40:25 +0100415
416MODULE_LICENSE("GPL v2");