diff --git a/src/conky.c b/src/conky.c index 669612b0..059dbbba 100644 --- a/src/conky.c +++ b/src/conky.c @@ -259,8 +259,8 @@ int no_buffers; static int pad_percents = 0; #ifdef TCP_PORT_MONITOR -static int min_port_monitors = 0; /* config item */ -static int min_port_monitor_connections = 0; /* config item */ +tcp_port_monitor_collection_args_t tcp_port_monitor_collection_args; +tcp_port_monitor_args_t tcp_port_monitor_args; #endif /* Text that is shown */ @@ -1826,18 +1826,8 @@ int a = stippled_borders, b = 1; /* if the port monitor collection hasn't been created, we must create it */ if ( !info.p_tcp_port_monitor_collection ) { - double hash_size, log_base_2; - - /* calculate hash_size from min_port_monitors */ - hash_size = (double)min_port_monitors / (double)TCP_MONITOR_HASH_MAX_LOAD_PCT; - /* correct hash_size to nearest power of two */ - log_base_2 = log(hash_size) / log(2); - if ( log_base_2 - (int)log_base_2 > 0.001 ) - log_base_2 = (double)( (int)log_base_2 + 1 ); - hash_size = pow(2,log_base_2); - /*fprintf(stderr,"collection hash size is %d\n",(int)hash_size);*/ - - info.p_tcp_port_monitor_collection = create_tcp_port_monitor_collection( (int)hash_size ); + info.p_tcp_port_monitor_collection = + create_tcp_port_monitor_collection( &tcp_port_monitor_collection_args ); if ( !info.p_tcp_port_monitor_collection ) { CRIT_ERR("tcp_portmon: unable to create port monitor collection"); @@ -1847,19 +1837,8 @@ int a = stippled_borders, b = 1; /* if a port monitor for this port does not exist, create one and add it to the collection */ if ( find_tcp_port_monitor( info.p_tcp_port_monitor_collection, port_begin, port_end ) == NULL ) { - double hash_size, log_base_2; - - /* calculate hash_size from min_port_monitor_connections */ - hash_size = (double)min_port_monitor_connections / (double)TCP_CONNECTION_HASH_MAX_LOAD_PCT; - /* correct hash_size to nearest power of two */ - log_base_2 = log(hash_size) / log(2); - if ( log_base_2 - (int)log_base_2 > 0.001) - log_base_2 = (double)( (int)log_base_2 + 1 ); - hash_size = pow(2, log_base_2); - /*fprintf(stderr,"monitor hash size is %d\n",(int)hash_size);*/ - tcp_port_monitor_t * p_monitor = - create_tcp_port_monitor( port_begin, port_end, (int)hash_size ); + create_tcp_port_monitor( port_begin, port_end, &tcp_port_monitor_args ); if ( !p_monitor ) { CRIT_ERR("tcp_portmon: unable to create port monitor"); @@ -4507,8 +4486,8 @@ static void set_default_configurations(void) #endif #ifdef TCP_PORT_MONITOR - min_port_monitors = MIN_PORT_MONITORS_DEFAULT; - min_port_monitor_connections = MIN_PORT_MONITOR_CONNECTIONS_DEFAULT; + tcp_port_monitor_collection_args.min_port_monitors = MIN_PORT_MONITORS_DEFAULT; + tcp_port_monitor_args.min_port_monitor_connections = MIN_PORT_MONITOR_CONNECTIONS_DEFAULT; #endif } @@ -4919,20 +4898,20 @@ else if (strcasecmp(name, a) == 0 || strcasecmp(name, b) == 0) CONF("min_port_monitors") { if ( !value || - (sscanf(value, "%d", &min_port_monitors) != 1) || - min_port_monitors <= 0 ) + (sscanf(value, "%d", &tcp_port_monitor_collection_args.min_port_monitors) != 1) || + tcp_port_monitor_collection_args.min_port_monitors <= 0 ) { - min_port_monitors = MIN_PORT_MONITORS_DEFAULT; + tcp_port_monitor_collection_args.min_port_monitors = MIN_PORT_MONITORS_DEFAULT; CONF_ERR; } } CONF("min_port_monitor_connections") { if ( !value || - (sscanf(value, "%d", &min_port_monitor_connections) != 1) - || min_port_monitor_connections <= 0 ) + (sscanf(value, "%d", &tcp_port_monitor_args.min_port_monitor_connections) != 1) + || tcp_port_monitor_args.min_port_monitor_connections <= 0 ) { - min_port_monitor_connections = MIN_PORT_MONITOR_CONNECTIONS_DEFAULT; + tcp_port_monitor_args.min_port_monitor_connections = MIN_PORT_MONITOR_CONNECTIONS_DEFAULT; CONF_ERR; } } diff --git a/src/conky.h b/src/conky.h index 4eada930..b7f78d28 100644 --- a/src/conky.h +++ b/src/conky.h @@ -114,7 +114,6 @@ struct mpd_s { #endif #ifdef TCP_PORT_MONITOR -#include #include "libtcp-portmon.h" #define MIN_PORT_MONITORS_DEFAULT 16 #define MIN_PORT_MONITOR_CONNECTIONS_DEFAULT 256 diff --git a/src/libtcp-portmon.c b/src/libtcp-portmon.c index a3636908..a1261f27 100644 --- a/src/libtcp-portmon.c +++ b/src/libtcp-portmon.c @@ -328,7 +328,7 @@ void maintain_tcp_port_monitor_hash( #ifdef HASH_DEBUG fprintf(stderr,"--- num vacated is %d, vacated factor is %.3f\n", p_monitor->hash.vacated, vacated_load ); #endif - if ( vacated_load <= TCP_CONNECIION_HASH_MAX_VACATED_PCT ) + if ( vacated_load <= TCP_CONNECIION_HASH_MAX_VACATED_RATIO ) { /* hash is fine and needs no rebalancing */ return; @@ -371,7 +371,7 @@ void rebuild_tcp_port_monitor_peek_table( return; /* zero out the peek array */ - memset( p_monitor->p_peek, 0, TCP_CONNECTION_HASH_SIZE * sizeof(tcp_connection_t *) ); + memset( p_monitor->p_peek, 0, p_monitor->hash.size * sizeof(tcp_connection_t *) ); for ( p_node=p_monitor->connection_list.p_head; p_node!=NULL; p_node=p_node->p_next, i++ ) { @@ -427,7 +427,7 @@ void show_connection_to_tcp_port_monitor( * connection limit. Future versions should probably allow the client to set the hash size * and load limits and/or provide for automatic resizing of hashes. */ - if ( (double)p_monitor->hash.size / (double)p_monitor->hash.positions >= TCP_CONNECTION_HASH_MAX_LOAD_PCT ) + if ( (double)p_monitor->hash.size / (double)p_monitor->hash.positions >= TCP_CONNECTION_HASH_MAX_LOAD_RATIO ) { /* hash exceeds our load limit is now "full" */ return; @@ -498,7 +498,38 @@ void for_each_tcp_port_monitor_in_collection( } +/* ---------------------------------------------------------------------------------------- + * Calculate an efficient hash size based on the desired number of elements and load factor. + * ---------------------------------------------------------------------------------------- */ +int calc_efficient_hash_size( + int min_elements, + int max_hash_size, + double max_load_factor + ) +{ + double min_size, hash_size, log_base_2; + + /* the size of the hash will the smallest power of two such that the minimum number + of desired elements does not exceed the maximum load factor. */ + + min_size = (double)min_elements / max_load_factor; /* starting point */ + + /* now adjust size up to nearest power of two */ + log_base_2 = (double) (int) ( log(min_size) / log(2) ) ; /* lop off fractional portion of log */ + hash_size = pow(2,log_base_2) >= min_size ? min_size : pow(2,(double)++log_base_2); + + /* respect the maximum */ + hash_size = hash_size <= max_hash_size ? hash_size : max_hash_size; + + /* + fprintf(stderr,"hash size is %d, based on %d min_elements and %.02f max load, %d maximum\n", + (int)hash_size, min_elements, max_load_factor, max_hash_size); + */ + + return hash_size; +} + /* ---------------------------------------------------------------------- * CLIENT INTERFACE * @@ -514,7 +545,7 @@ void for_each_tcp_port_monitor_in_collection( tcp_port_monitor_t * create_tcp_port_monitor( in_port_t port_range_begin, in_port_t port_range_end, - int hash_size + tcp_port_monitor_args_t * p_creation_args ) { tcp_port_monitor_t * p_monitor; @@ -526,7 +557,11 @@ tcp_port_monitor_t * create_tcp_port_monitor( /* create the monitor's connection hash */ if ( hash_create( &p_monitor->hash, - hash_size > 0 ? hash_size : TCP_CONNECTION_HASH_SIZE, + p_creation_args && p_creation_args->min_port_monitor_connections > 0 ? + calc_efficient_hash_size( p_creation_args->min_port_monitor_connections, + TCP_CONNECTION_HASH_SIZE_MAX, + TCP_CONNECTION_HASH_MAX_LOAD_RATIO ) : + TCP_CONNECTION_HASH_SIZE_DEFAULT, &connection_hash_function_1, &connection_hash_function_2, &connection_match_function, NULL ) != 0 ) { @@ -536,7 +571,7 @@ tcp_port_monitor_t * create_tcp_port_monitor( } /* create the monitor's peek array */ - if ( (p_monitor->p_peek = (tcp_connection_t **) calloc( TCP_CONNECTION_HASH_SIZE, sizeof(tcp_connection_t *))) == NULL ) + if ( (p_monitor->p_peek = (tcp_connection_t **) calloc( p_monitor->hash.size, sizeof(tcp_connection_t *))) == NULL ) { /* we failed to create the peek array, so destroy the monitor completely, again, so we don't leak */ destroy_tcp_port_monitor(p_monitor,NULL); @@ -658,7 +693,7 @@ int peek_tcp_port_monitor( /* Create a monitor collection. Do this one first. */ tcp_port_monitor_collection_t * create_tcp_port_monitor_collection( - int hash_size + tcp_port_monitor_collection_args_t * p_creation_args ) { tcp_port_monitor_collection_t * p_collection; @@ -669,7 +704,11 @@ tcp_port_monitor_collection_t * create_tcp_port_monitor_collection( /* create the collection's monitor hash */ if ( hash_create( &p_collection->hash, - hash_size > 0 ? hash_size : TCP_MONITOR_HASH_SIZE, + p_creation_args && p_creation_args->min_port_monitors > 0 ? + calc_efficient_hash_size( p_creation_args->min_port_monitors, + TCP_MONITOR_HASH_SIZE_MAX, + TCP_MONITOR_HASH_MAX_LOAD_RATIO ) : + TCP_MONITOR_HASH_SIZE_DEFAULT, &monitor_hash_function_1, &monitor_hash_function_2, &monitor_match_function, NULL ) != 0 ) { diff --git a/src/libtcp-portmon.h b/src/libtcp-portmon.h index 006e1763..727a11a2 100644 --- a/src/libtcp-portmon.h +++ b/src/libtcp-portmon.h @@ -23,6 +23,7 @@ #ifndef LIBTCP_PORTMON_H #define LIBTCP_PORTMON_H +#include #include #include #include @@ -37,7 +38,7 @@ * Each port monitor contains a connection hash whose contents changes dynamically as the monitor * is presented with connections on each update cycle. This implementation maintains the health * of this hash by enforcing several rules. First, the hash cannot contain more items than the - * TCP_CONNECTION_HASH_MAX_LOAD_PCT permits. For example, a 256 element hash with a max load of + * TCP_CONNECTION_HASH_MAX_LOAD_RATIO permits. For example, a 256 element hash with a max load of * 0.5 cannot contain more than 128 connections. Additional connections are ignored by the monitor. * The load factor of 0.5 is low enough to keep the hash running at near O(1) performanace at all * times. As elements are removed from the hash, the hash slots are tagged vacated, as required @@ -46,14 +47,15 @@ * The problem with vacated slots (even though they are reused) is that, as they increase in number, * esp. past about 1/4 of all slots, the average number of probes the hash has to perform increases * from O(1) on average to O(n) worst case. To keep the hash healthy, we simply rebuild it when the - * percentage of vacated slots gets too high (above TCP_CONNECTION_HASH_MAX_VACATED_PCT). Rebuilding - * the hash takes O(n) on the number of elements, but it well worth it as it keeps the hash running - * at an average access time of O(1). + * percentage of vacated slots gets too high (above TCP_CONNECTION_HASH_MAX_VACATED_RATIO). + * Rebuilding the hash takes O(n) on the number of elements, but it well worth it as it keeps the + * hash running at an average access time of O(1). * ------------------------------------------------------------------------------------------------*/ -#define TCP_CONNECTION_HASH_SIZE 512 /* connection hash size -- must be a power of two */ -#define TCP_CONNECTION_HASH_MAX_LOAD_PCT 0.5 /* disallow inserts after this % load is exceeded */ -#define TCP_CONNECIION_HASH_MAX_VACATED_PCT 0.25 /* rebalance hash after this % of vacated slots is exceeded */ +#define TCP_CONNECTION_HASH_SIZE_DEFAULT 512 /* connection hash size default -- must be a power of two */ +#define TCP_CONNECTION_HASH_SIZE_MAX 65535 /* connection hash size maximum -- must be a power of two */ +#define TCP_CONNECTION_HASH_MAX_LOAD_RATIO 0.5 /* disallow inserts after this load ratio is exceeded */ +#define TCP_CONNECIION_HASH_MAX_VACATED_RATIO 0.25 /* rebalance hash after this ratio of vacated slots is exceeded */ #define TCP_CONNECIION_STARTING_AGE 1 /* connection deleted if unseen again after this # of refreshes */ /* ---------------------------------------------------------------------------------------- @@ -66,8 +68,9 @@ * lookups at O(1). * ----------------------------------------------------------------------------------------*/ -#define TCP_MONITOR_HASH_SIZE 32 /* monitor hash size -- must be a power of two */ -#define TCP_MONITOR_HASH_MAX_LOAD_PCT 0.5 /* disallow new monitors after this % load is exceeded */ +#define TCP_MONITOR_HASH_SIZE_DEFAULT 32 /* monitor hash size default -- must be a power of two */ +#define TCP_MONITOR_HASH_SIZE_MAX 512 /* monitor hash size maximum -- must be a power of two */ +#define TCP_MONITOR_HASH_MAX_LOAD_RATIO 0.5 /* disallow new monitors after this load ratio is exceeded */ /* ------------------------------------------------------------------- * IMPLEMENTATION INTERFACE @@ -217,6 +220,14 @@ void for_each_tcp_port_monitor_in_collection( void * /* p_function_args (for user arguments) */ ); +/* ---------------------------------------------------------------------------------------- + * Calculate an efficient hash size based on the desired number of elements and load factor. + * ---------------------------------------------------------------------------------------- */ +int calc_efficient_hash_size( + int /* min_elements, the minimum number of elements to store */, + int /* max_hash_size, the maximum permissible hash size */, + double /* max_load_factor, the fractional load we wish not to exceed, e.g. 0.5 */ + ); /* ---------------------------------------------------------------------- * CLIENT INTERFACE @@ -224,6 +235,17 @@ void for_each_tcp_port_monitor_in_collection( * Clients should call only those functions below this line. * ---------------------------------------------------------------------- */ +/* struct to hold monitor creation arguments */ +typedef struct _tcp_port_monitor_args_t { + int min_port_monitor_connections; /* monitor must support tracking at least this many connections */ +} tcp_port_monitor_args_t; + + +/* struct to hold collection creation arguments */ +typedef struct _tcp_port_monitor_collection_args_t { + int min_port_monitors; /* collection must support creation of at least this many monitors */ +} tcp_port_monitor_collection_args_t; + /* ---------------------------------- * Client operations on port monitors * ---------------------------------- */ @@ -233,7 +255,7 @@ void for_each_tcp_port_monitor_in_collection( tcp_port_monitor_t * create_tcp_port_monitor( in_port_t /* port_range_begin */, in_port_t /* port_range_end */, - int /* hash_size */ + tcp_port_monitor_args_t * /* p_creation_args, NULL ok for library defaults */ ); /* Clients use this function to get connection data from the indicated port monitor. @@ -253,7 +275,7 @@ int peek_tcp_port_monitor( /* Create a monitor collection. Do this one first. */ tcp_port_monitor_collection_t * create_tcp_port_monitor_collection( - int /* hash_size */ + tcp_port_monitor_collection_args_t * /* p_creation_args, NULL ok for library defaults */ ); /* Destroy the monitor collection (and everything it contains). Do this one last. */ diff --git a/tests/Makefile b/tests/Makefile index 57f6e39a..0249795f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -16,7 +16,7 @@ test-hash: test-hash.o hash.o $(CC) $(CFLAGS) -o $@ test-hash.o hash.o test-portmon: test-portmon.o libtcp-portmon.o hash.o - $(CC) $(CFLAGS) -o $@ test-portmon.o libtcp-portmon.o hash.o + $(CC) $(CFLAGS) -o $@ test-portmon.o libtcp-portmon.o hash.o -lm test-hash.o: test-hash.c hash.h test-portmon.o: test-portmon.c libtcp-portmon.h hash.h diff --git a/tests/test-portmon.c b/tests/test-portmon.c index 028ef712..8ee03548 100644 --- a/tests/test-portmon.c +++ b/tests/test-portmon.c @@ -25,12 +25,12 @@ int main() (void) signal(SIGINT,set_terminate); - if ( (p_collection = create_tcp_port_monitor_collection( 0 )) == NULL) { + if ( (p_collection = create_tcp_port_monitor_collection( NULL )) == NULL) { fprintf(stderr,"error: create_tcp_port_monitor_collection()\n"); exit(1); } - if ( (p_monitor = create_tcp_port_monitor( 1, 65535, 0 )) == NULL) { + if ( (p_monitor = create_tcp_port_monitor( 1, 65535, NULL )) == NULL) { fprintf(stderr,"error: create_tcp_port_monitor()\n"); exit(1); }