test_rhashtable.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. /*
  2. * Resizable, Scalable, Concurrent Hash Table
  3. *
  4. * Copyright (c) 2014-2015 Thomas Graf <tgraf@suug.ch>
  5. * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net>
  6. *
  7. * 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. * Self Test
  13. **************************************************************************/
  14. #include <linux/init.h>
  15. #include <linux/jhash.h>
  16. #include <linux/kernel.h>
  17. #include <linux/kthread.h>
  18. #include <linux/module.h>
  19. #include <linux/rcupdate.h>
  20. #include <linux/rhashtable.h>
  21. #include <linux/semaphore.h>
  22. #include <linux/slab.h>
  23. #include <linux/sched.h>
  24. #include <linux/random.h>
  25. #include <linux/vmalloc.h>
  26. #define MAX_ENTRIES 1000000
  27. #define TEST_INSERT_FAIL INT_MAX
  28. static int parm_entries = 50000;
  29. module_param(parm_entries, int, 0);
  30. MODULE_PARM_DESC(parm_entries, "Number of entries to add (default: 50000)");
  31. static int runs = 4;
  32. module_param(runs, int, 0);
  33. MODULE_PARM_DESC(runs, "Number of test runs per variant (default: 4)");
  34. static int max_size = 0;
  35. module_param(max_size, int, 0);
  36. MODULE_PARM_DESC(max_size, "Maximum table size (default: calculated)");
  37. static bool shrinking = false;
  38. module_param(shrinking, bool, 0);
  39. MODULE_PARM_DESC(shrinking, "Enable automatic shrinking (default: off)");
  40. static int size = 8;
  41. module_param(size, int, 0);
  42. MODULE_PARM_DESC(size, "Initial size hint of table (default: 8)");
  43. static int tcount = 10;
  44. module_param(tcount, int, 0);
  45. MODULE_PARM_DESC(tcount, "Number of threads to spawn (default: 10)");
  46. static bool enomem_retry = false;
  47. module_param(enomem_retry, bool, 0);
  48. MODULE_PARM_DESC(enomem_retry, "Retry insert even if -ENOMEM was returned (default: off)");
  49. struct test_obj_val {
  50. int id;
  51. int tid;
  52. };
  53. struct test_obj {
  54. struct test_obj_val value;
  55. struct rhash_head node;
  56. };
  57. struct test_obj_rhl {
  58. struct test_obj_val value;
  59. struct rhlist_head list_node;
  60. };
  61. struct thread_data {
  62. unsigned int entries;
  63. int id;
  64. struct task_struct *task;
  65. struct test_obj *objs;
  66. };
  67. static struct rhashtable_params test_rht_params = {
  68. .head_offset = offsetof(struct test_obj, node),
  69. .key_offset = offsetof(struct test_obj, value),
  70. .key_len = sizeof(struct test_obj_val),
  71. .hashfn = jhash,
  72. .nulls_base = (3U << RHT_BASE_SHIFT),
  73. };
  74. static struct semaphore prestart_sem;
  75. static struct semaphore startup_sem = __SEMAPHORE_INITIALIZER(startup_sem, 0);
  76. static int insert_retry(struct rhashtable *ht, struct test_obj *obj,
  77. const struct rhashtable_params params)
  78. {
  79. int err, retries = -1, enomem_retries = 0;
  80. do {
  81. retries++;
  82. cond_resched();
  83. err = rhashtable_insert_fast(ht, &obj->node, params);
  84. if (err == -ENOMEM && enomem_retry) {
  85. enomem_retries++;
  86. err = -EBUSY;
  87. }
  88. } while (err == -EBUSY);
  89. if (enomem_retries)
  90. pr_info(" %u insertions retried after -ENOMEM\n",
  91. enomem_retries);
  92. return err ? : retries;
  93. }
  94. static int __init test_rht_lookup(struct rhashtable *ht, struct test_obj *array,
  95. unsigned int entries)
  96. {
  97. unsigned int i;
  98. for (i = 0; i < entries; i++) {
  99. struct test_obj *obj;
  100. bool expected = !(i % 2);
  101. struct test_obj_val key = {
  102. .id = i,
  103. };
  104. if (array[i / 2].value.id == TEST_INSERT_FAIL)
  105. expected = false;
  106. obj = rhashtable_lookup_fast(ht, &key, test_rht_params);
  107. if (expected && !obj) {
  108. pr_warn("Test failed: Could not find key %u\n", key.id);
  109. return -ENOENT;
  110. } else if (!expected && obj) {
  111. pr_warn("Test failed: Unexpected entry found for key %u\n",
  112. key.id);
  113. return -EEXIST;
  114. } else if (expected && obj) {
  115. if (obj->value.id != i) {
  116. pr_warn("Test failed: Lookup value mismatch %u!=%u\n",
  117. obj->value.id, i);
  118. return -EINVAL;
  119. }
  120. }
  121. cond_resched_rcu();
  122. }
  123. return 0;
  124. }
  125. static void test_bucket_stats(struct rhashtable *ht, unsigned int entries)
  126. {
  127. unsigned int err, total = 0, chain_len = 0;
  128. struct rhashtable_iter hti;
  129. struct rhash_head *pos;
  130. err = rhashtable_walk_init(ht, &hti, GFP_KERNEL);
  131. if (err) {
  132. pr_warn("Test failed: allocation error");
  133. return;
  134. }
  135. rhashtable_walk_start(&hti);
  136. while ((pos = rhashtable_walk_next(&hti))) {
  137. if (PTR_ERR(pos) == -EAGAIN) {
  138. pr_info("Info: encountered resize\n");
  139. chain_len++;
  140. continue;
  141. } else if (IS_ERR(pos)) {
  142. pr_warn("Test failed: rhashtable_walk_next() error: %ld\n",
  143. PTR_ERR(pos));
  144. break;
  145. }
  146. total++;
  147. }
  148. rhashtable_walk_stop(&hti);
  149. rhashtable_walk_exit(&hti);
  150. pr_info(" Traversal complete: counted=%u, nelems=%u, entries=%d, table-jumps=%u\n",
  151. total, atomic_read(&ht->nelems), entries, chain_len);
  152. if (total != atomic_read(&ht->nelems) || total != entries)
  153. pr_warn("Test failed: Total count mismatch ^^^");
  154. }
  155. static s64 __init test_rhashtable(struct rhashtable *ht, struct test_obj *array,
  156. unsigned int entries)
  157. {
  158. struct test_obj *obj;
  159. int err;
  160. unsigned int i, insert_retries = 0;
  161. s64 start, end;
  162. /*
  163. * Insertion Test:
  164. * Insert entries into table with all keys even numbers
  165. */
  166. pr_info(" Adding %d keys\n", entries);
  167. start = ktime_get_ns();
  168. for (i = 0; i < entries; i++) {
  169. struct test_obj *obj = &array[i];
  170. obj->value.id = i * 2;
  171. err = insert_retry(ht, obj, test_rht_params);
  172. if (err > 0)
  173. insert_retries += err;
  174. else if (err)
  175. return err;
  176. }
  177. if (insert_retries)
  178. pr_info(" %u insertions retried due to memory pressure\n",
  179. insert_retries);
  180. test_bucket_stats(ht, entries);
  181. rcu_read_lock();
  182. test_rht_lookup(ht, array, entries);
  183. rcu_read_unlock();
  184. test_bucket_stats(ht, entries);
  185. pr_info(" Deleting %d keys\n", entries);
  186. for (i = 0; i < entries; i++) {
  187. struct test_obj_val key = {
  188. .id = i * 2,
  189. };
  190. if (array[i].value.id != TEST_INSERT_FAIL) {
  191. obj = rhashtable_lookup_fast(ht, &key, test_rht_params);
  192. BUG_ON(!obj);
  193. rhashtable_remove_fast(ht, &obj->node, test_rht_params);
  194. }
  195. cond_resched();
  196. }
  197. end = ktime_get_ns();
  198. pr_info(" Duration of test: %lld ns\n", end - start);
  199. return end - start;
  200. }
  201. static struct rhashtable ht;
  202. static struct rhltable rhlt;
  203. static int __init test_rhltable(unsigned int entries)
  204. {
  205. struct test_obj_rhl *rhl_test_objects;
  206. unsigned long *obj_in_table;
  207. unsigned int i, j, k;
  208. int ret, err;
  209. if (entries == 0)
  210. entries = 1;
  211. rhl_test_objects = vzalloc(sizeof(*rhl_test_objects) * entries);
  212. if (!rhl_test_objects)
  213. return -ENOMEM;
  214. ret = -ENOMEM;
  215. obj_in_table = vzalloc(BITS_TO_LONGS(entries) * sizeof(unsigned long));
  216. if (!obj_in_table)
  217. goto out_free;
  218. /* nulls_base not supported in rhlist interface */
  219. test_rht_params.nulls_base = 0;
  220. err = rhltable_init(&rhlt, &test_rht_params);
  221. if (WARN_ON(err))
  222. goto out_free;
  223. k = prandom_u32();
  224. ret = 0;
  225. for (i = 0; i < entries; i++) {
  226. rhl_test_objects[i].value.id = k;
  227. err = rhltable_insert(&rhlt, &rhl_test_objects[i].list_node,
  228. test_rht_params);
  229. if (WARN(err, "error %d on element %d\n", err, i))
  230. break;
  231. if (err == 0)
  232. set_bit(i, obj_in_table);
  233. }
  234. if (err)
  235. ret = err;
  236. pr_info("test %d add/delete pairs into rhlist\n", entries);
  237. for (i = 0; i < entries; i++) {
  238. struct rhlist_head *h, *pos;
  239. struct test_obj_rhl *obj;
  240. struct test_obj_val key = {
  241. .id = k,
  242. };
  243. bool found;
  244. rcu_read_lock();
  245. h = rhltable_lookup(&rhlt, &key, test_rht_params);
  246. if (WARN(!h, "key not found during iteration %d of %d", i, entries)) {
  247. rcu_read_unlock();
  248. break;
  249. }
  250. if (i) {
  251. j = i - 1;
  252. rhl_for_each_entry_rcu(obj, pos, h, list_node) {
  253. if (WARN(pos == &rhl_test_objects[j].list_node, "old element found, should be gone"))
  254. break;
  255. }
  256. }
  257. cond_resched_rcu();
  258. found = false;
  259. rhl_for_each_entry_rcu(obj, pos, h, list_node) {
  260. if (pos == &rhl_test_objects[i].list_node) {
  261. found = true;
  262. break;
  263. }
  264. }
  265. rcu_read_unlock();
  266. if (WARN(!found, "element %d not found", i))
  267. break;
  268. err = rhltable_remove(&rhlt, &rhl_test_objects[i].list_node, test_rht_params);
  269. WARN(err, "rhltable_remove: err %d for iteration %d\n", err, i);
  270. if (err == 0)
  271. clear_bit(i, obj_in_table);
  272. }
  273. if (ret == 0 && err)
  274. ret = err;
  275. for (i = 0; i < entries; i++) {
  276. WARN(test_bit(i, obj_in_table), "elem %d allegedly still present", i);
  277. err = rhltable_insert(&rhlt, &rhl_test_objects[i].list_node,
  278. test_rht_params);
  279. if (WARN(err, "error %d on element %d\n", err, i))
  280. break;
  281. if (err == 0)
  282. set_bit(i, obj_in_table);
  283. }
  284. pr_info("test %d random rhlist add/delete operations\n", entries);
  285. for (j = 0; j < entries; j++) {
  286. u32 i = prandom_u32_max(entries);
  287. u32 prand = prandom_u32();
  288. cond_resched();
  289. if (prand == 0)
  290. prand = prandom_u32();
  291. if (prand & 1) {
  292. prand >>= 1;
  293. continue;
  294. }
  295. err = rhltable_remove(&rhlt, &rhl_test_objects[i].list_node, test_rht_params);
  296. if (test_bit(i, obj_in_table)) {
  297. clear_bit(i, obj_in_table);
  298. if (WARN(err, "cannot remove element at slot %d", i))
  299. continue;
  300. } else {
  301. if (WARN(err != -ENOENT, "removed non-existant element %d, error %d not %d",
  302. i, err, -ENOENT))
  303. continue;
  304. }
  305. if (prand & 1) {
  306. prand >>= 1;
  307. continue;
  308. }
  309. err = rhltable_insert(&rhlt, &rhl_test_objects[i].list_node, test_rht_params);
  310. if (err == 0) {
  311. if (WARN(test_and_set_bit(i, obj_in_table), "succeeded to insert same object %d", i))
  312. continue;
  313. } else {
  314. if (WARN(!test_bit(i, obj_in_table), "failed to insert object %d", i))
  315. continue;
  316. }
  317. if (prand & 1) {
  318. prand >>= 1;
  319. continue;
  320. }
  321. i = prandom_u32_max(entries);
  322. if (test_bit(i, obj_in_table)) {
  323. err = rhltable_remove(&rhlt, &rhl_test_objects[i].list_node, test_rht_params);
  324. WARN(err, "cannot remove element at slot %d", i);
  325. if (err == 0)
  326. clear_bit(i, obj_in_table);
  327. } else {
  328. err = rhltable_insert(&rhlt, &rhl_test_objects[i].list_node, test_rht_params);
  329. WARN(err, "failed to insert object %d", i);
  330. if (err == 0)
  331. set_bit(i, obj_in_table);
  332. }
  333. }
  334. for (i = 0; i < entries; i++) {
  335. cond_resched();
  336. err = rhltable_remove(&rhlt, &rhl_test_objects[i].list_node, test_rht_params);
  337. if (test_bit(i, obj_in_table)) {
  338. if (WARN(err, "cannot remove element at slot %d", i))
  339. continue;
  340. } else {
  341. if (WARN(err != -ENOENT, "removed non-existant element, error %d not %d",
  342. err, -ENOENT))
  343. continue;
  344. }
  345. }
  346. rhltable_destroy(&rhlt);
  347. out_free:
  348. vfree(rhl_test_objects);
  349. vfree(obj_in_table);
  350. return ret;
  351. }
  352. static int __init test_rhashtable_max(struct test_obj *array,
  353. unsigned int entries)
  354. {
  355. unsigned int i, insert_retries = 0;
  356. int err;
  357. test_rht_params.max_size = roundup_pow_of_two(entries / 8);
  358. err = rhashtable_init(&ht, &test_rht_params);
  359. if (err)
  360. return err;
  361. for (i = 0; i < ht.max_elems; i++) {
  362. struct test_obj *obj = &array[i];
  363. obj->value.id = i * 2;
  364. err = insert_retry(&ht, obj, test_rht_params);
  365. if (err > 0)
  366. insert_retries += err;
  367. else if (err)
  368. return err;
  369. }
  370. err = insert_retry(&ht, &array[ht.max_elems], test_rht_params);
  371. if (err == -E2BIG) {
  372. err = 0;
  373. } else {
  374. pr_info("insert element %u should have failed with %d, got %d\n",
  375. ht.max_elems, -E2BIG, err);
  376. if (err == 0)
  377. err = -1;
  378. }
  379. rhashtable_destroy(&ht);
  380. return err;
  381. }
  382. static int thread_lookup_test(struct thread_data *tdata)
  383. {
  384. unsigned int entries = tdata->entries;
  385. int i, err = 0;
  386. for (i = 0; i < entries; i++) {
  387. struct test_obj *obj;
  388. struct test_obj_val key = {
  389. .id = i,
  390. .tid = tdata->id,
  391. };
  392. obj = rhashtable_lookup_fast(&ht, &key, test_rht_params);
  393. if (obj && (tdata->objs[i].value.id == TEST_INSERT_FAIL)) {
  394. pr_err(" found unexpected object %d-%d\n", key.tid, key.id);
  395. err++;
  396. } else if (!obj && (tdata->objs[i].value.id != TEST_INSERT_FAIL)) {
  397. pr_err(" object %d-%d not found!\n", key.tid, key.id);
  398. err++;
  399. } else if (obj && memcmp(&obj->value, &key, sizeof(key))) {
  400. pr_err(" wrong object returned (got %d-%d, expected %d-%d)\n",
  401. obj->value.tid, obj->value.id, key.tid, key.id);
  402. err++;
  403. }
  404. cond_resched();
  405. }
  406. return err;
  407. }
  408. static int threadfunc(void *data)
  409. {
  410. int i, step, err = 0, insert_retries = 0;
  411. struct thread_data *tdata = data;
  412. up(&prestart_sem);
  413. if (down_interruptible(&startup_sem))
  414. pr_err(" thread[%d]: down_interruptible failed\n", tdata->id);
  415. for (i = 0; i < tdata->entries; i++) {
  416. tdata->objs[i].value.id = i;
  417. tdata->objs[i].value.tid = tdata->id;
  418. err = insert_retry(&ht, &tdata->objs[i], test_rht_params);
  419. if (err > 0) {
  420. insert_retries += err;
  421. } else if (err) {
  422. pr_err(" thread[%d]: rhashtable_insert_fast failed\n",
  423. tdata->id);
  424. goto out;
  425. }
  426. }
  427. if (insert_retries)
  428. pr_info(" thread[%d]: %u insertions retried due to memory pressure\n",
  429. tdata->id, insert_retries);
  430. err = thread_lookup_test(tdata);
  431. if (err) {
  432. pr_err(" thread[%d]: rhashtable_lookup_test failed\n",
  433. tdata->id);
  434. goto out;
  435. }
  436. for (step = 10; step > 0; step--) {
  437. for (i = 0; i < tdata->entries; i += step) {
  438. if (tdata->objs[i].value.id == TEST_INSERT_FAIL)
  439. continue;
  440. err = rhashtable_remove_fast(&ht, &tdata->objs[i].node,
  441. test_rht_params);
  442. if (err) {
  443. pr_err(" thread[%d]: rhashtable_remove_fast failed\n",
  444. tdata->id);
  445. goto out;
  446. }
  447. tdata->objs[i].value.id = TEST_INSERT_FAIL;
  448. cond_resched();
  449. }
  450. err = thread_lookup_test(tdata);
  451. if (err) {
  452. pr_err(" thread[%d]: rhashtable_lookup_test (2) failed\n",
  453. tdata->id);
  454. goto out;
  455. }
  456. }
  457. out:
  458. while (!kthread_should_stop()) {
  459. set_current_state(TASK_INTERRUPTIBLE);
  460. schedule();
  461. }
  462. return err;
  463. }
  464. static int __init test_rht_init(void)
  465. {
  466. unsigned int entries;
  467. int i, err, started_threads = 0, failed_threads = 0;
  468. u64 total_time = 0;
  469. struct thread_data *tdata;
  470. struct test_obj *objs;
  471. if (parm_entries < 0)
  472. parm_entries = 1;
  473. entries = min(parm_entries, MAX_ENTRIES);
  474. test_rht_params.automatic_shrinking = shrinking;
  475. test_rht_params.max_size = max_size ? : roundup_pow_of_two(entries);
  476. test_rht_params.nelem_hint = size;
  477. objs = vzalloc((test_rht_params.max_size + 1) * sizeof(struct test_obj));
  478. if (!objs)
  479. return -ENOMEM;
  480. pr_info("Running rhashtable test nelem=%d, max_size=%d, shrinking=%d\n",
  481. size, max_size, shrinking);
  482. for (i = 0; i < runs; i++) {
  483. s64 time;
  484. pr_info("Test %02d:\n", i);
  485. memset(objs, 0, test_rht_params.max_size * sizeof(struct test_obj));
  486. err = rhashtable_init(&ht, &test_rht_params);
  487. if (err < 0) {
  488. pr_warn("Test failed: Unable to initialize hashtable: %d\n",
  489. err);
  490. continue;
  491. }
  492. time = test_rhashtable(&ht, objs, entries);
  493. rhashtable_destroy(&ht);
  494. if (time < 0) {
  495. vfree(objs);
  496. pr_warn("Test failed: return code %lld\n", time);
  497. return -EINVAL;
  498. }
  499. total_time += time;
  500. }
  501. pr_info("test if its possible to exceed max_size %d: %s\n",
  502. test_rht_params.max_size, test_rhashtable_max(objs, entries) == 0 ?
  503. "no, ok" : "YES, failed");
  504. vfree(objs);
  505. do_div(total_time, runs);
  506. pr_info("Average test time: %llu\n", total_time);
  507. if (!tcount)
  508. return 0;
  509. pr_info("Testing concurrent rhashtable access from %d threads\n",
  510. tcount);
  511. sema_init(&prestart_sem, 1 - tcount);
  512. tdata = vzalloc(tcount * sizeof(struct thread_data));
  513. if (!tdata)
  514. return -ENOMEM;
  515. objs = vzalloc(tcount * entries * sizeof(struct test_obj));
  516. if (!objs) {
  517. vfree(tdata);
  518. return -ENOMEM;
  519. }
  520. test_rht_params.max_size = max_size ? :
  521. roundup_pow_of_two(tcount * entries);
  522. err = rhashtable_init(&ht, &test_rht_params);
  523. if (err < 0) {
  524. pr_warn("Test failed: Unable to initialize hashtable: %d\n",
  525. err);
  526. vfree(tdata);
  527. vfree(objs);
  528. return -EINVAL;
  529. }
  530. for (i = 0; i < tcount; i++) {
  531. tdata[i].id = i;
  532. tdata[i].entries = entries;
  533. tdata[i].objs = objs + i * entries;
  534. tdata[i].task = kthread_run(threadfunc, &tdata[i],
  535. "rhashtable_thrad[%d]", i);
  536. if (IS_ERR(tdata[i].task))
  537. pr_err(" kthread_run failed for thread %d\n", i);
  538. else
  539. started_threads++;
  540. }
  541. if (down_interruptible(&prestart_sem))
  542. pr_err(" down interruptible failed\n");
  543. for (i = 0; i < tcount; i++)
  544. up(&startup_sem);
  545. for (i = 0; i < tcount; i++) {
  546. if (IS_ERR(tdata[i].task))
  547. continue;
  548. if ((err = kthread_stop(tdata[i].task))) {
  549. pr_warn("Test failed: thread %d returned: %d\n",
  550. i, err);
  551. failed_threads++;
  552. }
  553. }
  554. rhashtable_destroy(&ht);
  555. vfree(tdata);
  556. vfree(objs);
  557. /*
  558. * rhltable_remove is very expensive, default values can cause test
  559. * to run for 2 minutes or more, use a smaller number instead.
  560. */
  561. err = test_rhltable(entries / 16);
  562. pr_info("Started %d threads, %d failed, rhltable test returns %d\n",
  563. started_threads, failed_threads, err);
  564. return 0;
  565. }
  566. static void __exit test_rht_exit(void)
  567. {
  568. }
  569. module_init(test_rht_init);
  570. module_exit(test_rht_exit);
  571. MODULE_LICENSE("GPL v2");