2015-01-29 17:40:25 +03:00
/*
* Resizable , Scalable , Concurrent Hash Table
*
2015-05-01 01:37:41 +03:00
* Copyright ( c ) 2014 - 2015 Thomas Graf < tgraf @ suug . ch >
2015-01-29 17:40:25 +03:00
* Copyright ( c ) 2008 - 2014 Patrick McHardy < kaber @ trash . net >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
/**************************************************************************
* Self Test
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <linux/init.h>
# include <linux/jhash.h>
# include <linux/kernel.h>
2015-08-15 01:37:15 +03:00
# include <linux/kthread.h>
2015-01-29 17:40:25 +03:00
# include <linux/module.h>
# include <linux/rcupdate.h>
# include <linux/rhashtable.h>
2015-08-15 01:37:15 +03:00
# include <linux/semaphore.h>
2015-01-29 17:40:25 +03:00
# include <linux/slab.h>
2015-07-17 11:52:48 +03:00
# include <linux/sched.h>
2015-08-15 01:37:15 +03:00
# include <linux/vmalloc.h>
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
# define MAX_ENTRIES 1000000
2015-05-01 01:37:45 +03:00
# define TEST_INSERT_FAIL INT_MAX
2015-05-01 01:37:41 +03:00
static int entries = 50000 ;
module_param ( entries , int , 0 ) ;
MODULE_PARM_DESC ( entries , " Number of entries to add (default: 50000) " ) ;
static int runs = 4 ;
module_param ( runs , int , 0 ) ;
MODULE_PARM_DESC ( runs , " Number of test runs per variant (default: 4) " ) ;
2015-11-20 20:17:19 +03:00
static int max_size = 0 ;
2015-05-01 01:37:41 +03:00
module_param ( max_size , int , 0 ) ;
2016-08-04 13:37:17 +03:00
MODULE_PARM_DESC ( max_size , " Maximum table size (default: calculated) " ) ;
2015-05-01 01:37:41 +03:00
static bool shrinking = false ;
module_param ( shrinking , bool , 0 ) ;
MODULE_PARM_DESC ( shrinking , " Enable automatic shrinking (default: off) " ) ;
static int size = 8 ;
module_param ( size , int , 0 ) ;
MODULE_PARM_DESC ( size , " Initial size hint of table (default: 8) " ) ;
2015-01-29 17:40:25 +03:00
2015-08-15 01:37:15 +03:00
static int tcount = 10 ;
module_param ( tcount , int , 0 ) ;
MODULE_PARM_DESC ( tcount , " Number of threads to spawn (default: 10) " ) ;
2015-11-20 20:17:20 +03:00
static bool enomem_retry = false ;
module_param ( enomem_retry , bool , 0 ) ;
MODULE_PARM_DESC ( enomem_retry , " Retry insert even if -ENOMEM was returned (default: off) " ) ;
2015-01-29 17:40:25 +03:00
struct test_obj {
int value ;
struct rhash_head node ;
} ;
2015-08-15 01:37:15 +03:00
struct thread_data {
int id ;
struct task_struct * task ;
struct test_obj * objs ;
} ;
2015-05-01 01:37:43 +03:00
static struct test_obj array [ MAX_ENTRIES ] ;
2015-05-01 01:37:41 +03:00
static struct rhashtable_params test_rht_params = {
2015-03-20 13:57:04 +03:00
. head_offset = offsetof ( struct test_obj , node ) ,
. key_offset = offsetof ( struct test_obj , value ) ,
. key_len = sizeof ( int ) ,
. hashfn = jhash ,
. nulls_base = ( 3U < < RHT_BASE_SHIFT ) ,
} ;
2015-08-15 01:37:15 +03:00
static struct semaphore prestart_sem ;
static struct semaphore startup_sem = __SEMAPHORE_INITIALIZER ( startup_sem , 0 ) ;
2015-11-20 20:17:18 +03:00
static int insert_retry ( struct rhashtable * ht , struct rhash_head * obj ,
const struct rhashtable_params params )
{
2015-11-20 20:17:20 +03:00
int err , retries = - 1 , enomem_retries = 0 ;
2015-11-20 20:17:18 +03:00
do {
retries + + ;
cond_resched ( ) ;
err = rhashtable_insert_fast ( ht , obj , params ) ;
2015-11-20 20:17:20 +03:00
if ( err = = - ENOMEM & & enomem_retry ) {
enomem_retries + + ;
err = - EBUSY ;
}
2015-11-20 20:17:18 +03:00
} while ( err = = - EBUSY ) ;
2015-11-20 20:17:20 +03:00
if ( enomem_retries )
pr_info ( " %u insertions retried after -ENOMEM \n " ,
enomem_retries ) ;
2015-11-20 20:17:18 +03:00
return err ? : retries ;
}
2015-01-29 17:40:25 +03:00
static int __init test_rht_lookup ( struct rhashtable * ht )
{
unsigned int i ;
2015-05-01 01:37:41 +03:00
for ( i = 0 ; i < entries * 2 ; i + + ) {
2015-01-29 17:40:25 +03:00
struct test_obj * obj ;
bool expected = ! ( i % 2 ) ;
u32 key = i ;
2015-05-01 01:37:45 +03:00
if ( array [ i / 2 ] . value = = TEST_INSERT_FAIL )
expected = false ;
2015-03-20 13:57:04 +03:00
obj = rhashtable_lookup_fast ( ht , & key , test_rht_params ) ;
2015-01-29 17:40:25 +03:00
if ( expected & & ! obj ) {
pr_warn ( " Test failed: Could not find key %u \n " , key ) ;
return - ENOENT ;
} else if ( ! expected & & obj ) {
pr_warn ( " Test failed: Unexpected entry found for key %u \n " ,
key ) ;
return - EEXIST ;
} else if ( expected & & obj ) {
2015-05-01 01:37:42 +03:00
if ( obj - > value ! = i ) {
pr_warn ( " Test failed: Lookup value mismatch %u!=%u \n " ,
obj - > value , i ) ;
2015-01-29 17:40:25 +03:00
return - EINVAL ;
}
}
2015-07-17 11:52:48 +03:00
cond_resched_rcu ( ) ;
2015-01-29 17:40:25 +03:00
}
return 0 ;
}
2015-05-01 01:37:44 +03:00
static void test_bucket_stats ( struct rhashtable * ht )
2015-01-29 17:40:25 +03:00
{
2015-05-01 01:37:44 +03:00
unsigned int err , total = 0 , chain_len = 0 ;
struct rhashtable_iter hti ;
2015-01-29 17:40:25 +03:00
struct rhash_head * pos ;
2016-03-02 18:09:19 +03:00
err = rhashtable_walk_init ( ht , & hti , GFP_KERNEL ) ;
2015-05-01 01:37:44 +03:00
if ( err ) {
pr_warn ( " Test failed: allocation error " ) ;
return ;
}
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:44 +03:00
err = rhashtable_walk_start ( & hti ) ;
if ( err & & err ! = - EAGAIN ) {
pr_warn ( " Test failed: iterator failed: %d \n " , err ) ;
return ;
}
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:44 +03:00
while ( ( pos = rhashtable_walk_next ( & hti ) ) ) {
if ( PTR_ERR ( pos ) = = - EAGAIN ) {
pr_info ( " Info: encountered resize \n " ) ;
chain_len + + ;
continue ;
} else if ( IS_ERR ( pos ) ) {
pr_warn ( " Test failed: rhashtable_walk_next() error: %ld \n " ,
PTR_ERR ( pos ) ) ;
break ;
2015-01-29 17:40:25 +03:00
}
2015-05-01 01:37:44 +03:00
total + + ;
2015-01-29 17:40:25 +03:00
}
2015-05-01 01:37:44 +03:00
rhashtable_walk_stop ( & hti ) ;
rhashtable_walk_exit ( & hti ) ;
pr_info ( " Traversal complete: counted=%u, nelems=%u, entries=%d, table-jumps=%u \n " ,
total , atomic_read ( & ht - > nelems ) , entries , chain_len ) ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
if ( total ! = atomic_read ( & ht - > nelems ) | | total ! = entries )
2015-01-29 17:40:25 +03:00
pr_warn ( " Test failed: Total count mismatch ^^^ " ) ;
}
2015-05-01 01:37:41 +03:00
static s64 __init test_rhashtable ( struct rhashtable * ht )
2015-01-29 17:40:25 +03:00
{
struct test_obj * obj ;
int err ;
2015-11-20 20:17:18 +03:00
unsigned int i , insert_retries = 0 ;
2015-05-01 01:37:41 +03:00
s64 start , end ;
2015-01-29 17:40:25 +03:00
/*
* Insertion Test :
2015-05-01 01:37:41 +03:00
* Insert entries into table with all keys even numbers
2015-01-29 17:40:25 +03:00
*/
2015-05-01 01:37:41 +03:00
pr_info ( " Adding %d keys \n " , entries ) ;
start = ktime_get_ns ( ) ;
for ( i = 0 ; i < entries ; i + + ) {
2015-05-01 01:37:43 +03:00
struct test_obj * obj = & array [ i ] ;
2015-01-29 17:40:25 +03:00
obj - > value = i * 2 ;
2015-11-20 20:17:18 +03:00
err = insert_retry ( ht , & obj - > node , test_rht_params ) ;
if ( err > 0 )
insert_retries + = err ;
else if ( err )
2015-05-01 01:37:43 +03:00
return err ;
2015-01-29 17:40:25 +03:00
}
2015-11-20 20:17:18 +03:00
if ( insert_retries )
pr_info ( " %u insertions retried due to memory pressure \n " ,
insert_retries ) ;
2015-05-01 01:37:45 +03:00
2015-05-01 01:37:44 +03:00
test_bucket_stats ( ht ) ;
2015-01-29 17:40:25 +03:00
rcu_read_lock ( ) ;
test_rht_lookup ( ht ) ;
rcu_read_unlock ( ) ;
2015-05-01 01:37:44 +03:00
test_bucket_stats ( ht ) ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
pr_info ( " Deleting %d keys \n " , entries ) ;
for ( i = 0 ; i < entries ; i + + ) {
2015-01-29 17:40:25 +03:00
u32 key = i * 2 ;
2015-05-01 01:37:45 +03:00
if ( array [ i ] . value ! = TEST_INSERT_FAIL ) {
obj = rhashtable_lookup_fast ( ht , & key , test_rht_params ) ;
BUG_ON ( ! obj ) ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:45 +03:00
rhashtable_remove_fast ( ht , & obj - > node , test_rht_params ) ;
}
2015-07-17 11:52:48 +03:00
cond_resched ( ) ;
2015-01-29 17:40:25 +03:00
}
2015-05-01 01:37:41 +03:00
end = ktime_get_ns ( ) ;
pr_info ( " Duration of test: %lld ns \n " , end - start ) ;
return end - start ;
2015-01-29 17:40:25 +03:00
}
rhashtable: don't allocate ht structure on stack in test_rht_init
With object runtime debugging enabled, the rhashtable test suite
will rightfully throw a warning "ODEBUG: object is on stack, but
not annotated" from rhashtable_init().
This is because run_work is (correctly) being initialized via
INIT_WORK(), and not annotated by INIT_WORK_ONSTACK(). Meaning,
rhashtable_init() is okay as is, we just need to move ht e.g.,
into global scope.
It never triggered anything, since test_rhashtable is rather a
controlled environment and effectively runs to completion, so
that stack memory is not vanishing underneath us, we shouldn't
confuse any testers with it though.
Fixes: 7e1e77636e36 ("lib: Resizable, Scalable, Concurrent Hash Table")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Thomas Graf <tgraf@suug.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-02-20 23:14:21 +03:00
static struct rhashtable ht ;
2015-08-15 01:37:15 +03:00
static int thread_lookup_test ( struct thread_data * tdata )
{
int i , err = 0 ;
for ( i = 0 ; i < entries ; i + + ) {
struct test_obj * obj ;
int key = ( tdata - > id < < 16 ) | i ;
obj = rhashtable_lookup_fast ( & ht , & key , test_rht_params ) ;
if ( obj & & ( tdata - > objs [ i ] . value = = TEST_INSERT_FAIL ) ) {
pr_err ( " found unexpected object %d \n " , key ) ;
err + + ;
} else if ( ! obj & & ( tdata - > objs [ i ] . value ! = TEST_INSERT_FAIL ) ) {
pr_err ( " object %d not found! \n " , key ) ;
err + + ;
} else if ( obj & & ( obj - > value ! = key ) ) {
pr_err ( " wrong object returned (got %d, expected %d) \n " ,
obj - > value , key ) ;
err + + ;
}
2015-11-20 20:17:17 +03:00
cond_resched ( ) ;
2015-08-15 01:37:15 +03:00
}
return err ;
}
static int threadfunc ( void * data )
{
2015-11-20 20:17:18 +03:00
int i , step , err = 0 , insert_retries = 0 ;
2015-08-15 01:37:15 +03:00
struct thread_data * tdata = data ;
up ( & prestart_sem ) ;
if ( down_interruptible ( & startup_sem ) )
pr_err ( " thread[%d]: down_interruptible failed \n " , tdata - > id ) ;
for ( i = 0 ; i < entries ; i + + ) {
tdata - > objs [ i ] . value = ( tdata - > id < < 16 ) | i ;
2015-11-20 20:17:18 +03:00
err = insert_retry ( & ht , & tdata - > objs [ i ] . node , test_rht_params ) ;
if ( err > 0 ) {
insert_retries + = err ;
2015-08-15 01:37:15 +03:00
} else if ( err ) {
pr_err ( " thread[%d]: rhashtable_insert_fast failed \n " ,
tdata - > id ) ;
goto out ;
}
}
2015-11-20 20:17:18 +03:00
if ( insert_retries )
pr_info ( " thread[%d]: %u insertions retried due to memory pressure \n " ,
tdata - > id , insert_retries ) ;
2015-08-15 01:37:15 +03:00
err = thread_lookup_test ( tdata ) ;
if ( err ) {
pr_err ( " thread[%d]: rhashtable_lookup_test failed \n " ,
tdata - > id ) ;
goto out ;
}
for ( step = 10 ; step > 0 ; step - - ) {
for ( i = 0 ; i < entries ; i + = step ) {
if ( tdata - > objs [ i ] . value = = TEST_INSERT_FAIL )
continue ;
err = rhashtable_remove_fast ( & ht , & tdata - > objs [ i ] . node ,
test_rht_params ) ;
if ( err ) {
pr_err ( " thread[%d]: rhashtable_remove_fast failed \n " ,
tdata - > id ) ;
goto out ;
}
tdata - > objs [ i ] . value = TEST_INSERT_FAIL ;
2015-11-20 20:17:17 +03:00
cond_resched ( ) ;
2015-08-15 01:37:15 +03:00
}
err = thread_lookup_test ( tdata ) ;
if ( err ) {
pr_err ( " thread[%d]: rhashtable_lookup_test (2) failed \n " ,
tdata - > id ) ;
goto out ;
}
}
out :
while ( ! kthread_should_stop ( ) ) {
set_current_state ( TASK_INTERRUPTIBLE ) ;
schedule ( ) ;
}
return err ;
}
2015-01-29 17:40:25 +03:00
static int __init test_rht_init ( void )
{
2015-08-15 01:37:15 +03:00
int i , err , started_threads = 0 , failed_threads = 0 ;
2015-05-01 01:37:41 +03:00
u64 total_time = 0 ;
2015-08-15 01:37:15 +03:00
struct thread_data * tdata ;
struct test_obj * objs ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
entries = min ( entries , MAX_ENTRIES ) ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
test_rht_params . automatic_shrinking = shrinking ;
2015-11-20 20:17:19 +03:00
test_rht_params . max_size = max_size ? : roundup_pow_of_two ( entries ) ;
2015-05-01 01:37:41 +03:00
test_rht_params . nelem_hint = size ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
pr_info ( " Running rhashtable test nelem=%d, max_size=%d, shrinking=%d \n " ,
size , max_size , shrinking ) ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
for ( i = 0 ; i < runs ; i + + ) {
s64 time ;
2015-01-29 17:40:25 +03:00
2015-05-01 01:37:41 +03:00
pr_info ( " Test %02d: \n " , i ) ;
2015-05-01 01:37:43 +03:00
memset ( & array , 0 , sizeof ( array ) ) ;
2015-05-01 01:37:41 +03:00
err = rhashtable_init ( & ht , & test_rht_params ) ;
if ( err < 0 ) {
pr_warn ( " Test failed: Unable to initialize hashtable: %d \n " ,
err ) ;
continue ;
}
time = test_rhashtable ( & ht ) ;
rhashtable_destroy ( & ht ) ;
if ( time < 0 ) {
pr_warn ( " Test failed: return code %lld \n " , time ) ;
return - EINVAL ;
}
total_time + = time ;
}
2015-05-05 03:27:02 +03:00
do_div ( total_time , runs ) ;
pr_info ( " Average test time: %llu \n " , total_time ) ;
2015-05-01 01:37:41 +03:00
2015-08-15 01:37:15 +03:00
if ( ! tcount )
return 0 ;
pr_info ( " Testing concurrent rhashtable access from %d threads \n " ,
tcount ) ;
sema_init ( & prestart_sem , 1 - tcount ) ;
tdata = vzalloc ( tcount * sizeof ( struct thread_data ) ) ;
if ( ! tdata )
return - ENOMEM ;
objs = vzalloc ( tcount * entries * sizeof ( struct test_obj ) ) ;
if ( ! objs ) {
vfree ( tdata ) ;
return - ENOMEM ;
}
2015-11-20 20:17:19 +03:00
test_rht_params . max_size = max_size ? :
roundup_pow_of_two ( tcount * entries ) ;
2015-08-15 01:37:15 +03:00
err = rhashtable_init ( & ht , & test_rht_params ) ;
if ( err < 0 ) {
pr_warn ( " Test failed: Unable to initialize hashtable: %d \n " ,
err ) ;
vfree ( tdata ) ;
vfree ( objs ) ;
return - EINVAL ;
}
for ( i = 0 ; i < tcount ; i + + ) {
tdata [ i ] . id = i ;
tdata [ i ] . objs = objs + i * entries ;
tdata [ i ] . task = kthread_run ( threadfunc , & tdata [ i ] ,
" rhashtable_thrad[%d] " , i ) ;
if ( IS_ERR ( tdata [ i ] . task ) )
pr_err ( " kthread_run failed for thread %d \n " , i ) ;
else
started_threads + + ;
}
if ( down_interruptible ( & prestart_sem ) )
pr_err ( " down interruptible failed \n " ) ;
for ( i = 0 ; i < tcount ; i + + )
up ( & startup_sem ) ;
for ( i = 0 ; i < tcount ; i + + ) {
if ( IS_ERR ( tdata [ i ] . task ) )
continue ;
if ( ( err = kthread_stop ( tdata [ i ] . task ) ) ) {
pr_warn ( " Test failed: thread %d returned: %d \n " ,
i , err ) ;
failed_threads + + ;
}
}
pr_info ( " Started %d threads, %d failed \n " ,
started_threads , failed_threads ) ;
rhashtable_destroy ( & ht ) ;
vfree ( tdata ) ;
vfree ( objs ) ;
2015-05-01 01:37:41 +03:00
return 0 ;
2015-01-29 17:40:25 +03:00
}
2015-02-20 02:53:39 +03:00
static void __exit test_rht_exit ( void )
{
}
2015-01-29 17:40:25 +03:00
module_init ( test_rht_init ) ;
2015-02-20 02:53:39 +03:00
module_exit ( test_rht_exit ) ;
2015-01-29 17:40:25 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;