/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2026, Sangoma Technologies Inc * * Joshua Colp * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief Performance Experimentation Tests * * \author Joshua Colp * */ /*** MODULEINFO TEST_FRAMEWORK core ***/ #include "asterisk.h" #include "asterisk/module.h" #include "asterisk/cli.h" #include "asterisk/time.h" #include "asterisk/astobj2.h" #include "asterisk/vector.h" #include "asterisk/utils.h" #include "asterisk/strings.h" #include "asterisk/hashtab.h" #include "asterisk/linkedlists.h" #include /*! \brief The default number of keys to generate for performance tests */ #define DEFAULT_KEY_COUNT 250 /*! \brief The default duration of the performance test in seconds */ #define DEFAULT_DURATION_SEC 10 /*! \brief The length of each test key */ #define TEST_KEY_LENGTH 32 /*! \brief The number of attempts for random strings before we give up */ #define RANDOM_STRING_ATTEMPTS 100 /*! * The following tests cover the most common usage of containers in Asterisk - a key lookup * based on a string which then resolves to an underlying object. The CLI commands assume that * the underlying container implementations are working properly and return the requested * object. These tests also only test single threaded performance. They do not test concurrent * access or thread safety. */ /*! * \brief Frees the memory used by a vector of random strings * \param vec The vector of random strings to free */ static void free_random_strings(struct ast_vector_string *vec) { AST_VECTOR_RESET(vec, ast_free); AST_VECTOR_FREE(vec); ast_free(vec); } /*! * \brief Generates a vector of random strings to use as keys * \param count The number of strings to generate * \return A vector of random strings, or NULL on failure */ static struct ast_vector_string *generate_random_strings(int count) { struct ast_vector_string *vec; struct ao2_container *seen; int i; vec = ast_malloc(sizeof(*vec)); if (!vec) { return NULL; } /* Preallocate the vector to avoid reallocations during insertion */ if (AST_VECTOR_INIT(vec, count)) { ast_free(vec); return NULL; } /* * To eliminate collissions keep track of seen strings and retry if a collision occurs, * since this is just for keeping track of seen we don't care about optimizing the bucket * size of the container. */ seen = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, count); if (!seen) { free_random_strings(vec); return NULL; } for (i = 0; i < count; i++) { char buf[TEST_KEY_LENGTH]; unsigned int attempts = 0; char *dup; /* Generate a random string and retry if it collides with a previously seen string */ while (1) { char *existing; ast_generate_random_string(buf, sizeof(buf)); existing = ao2_find(seen, buf, OBJ_SEARCH_KEY); /* We don't care about the existing string, just that it's not NULL */ ao2_cleanup(existing); if (!existing) { break; } /* To prevent infinite loops in case of collisions, limit the number of attempts */ attempts++; if (attempts > RANDOM_STRING_ATTEMPTS) { ao2_ref(seen, -1); free_random_strings(vec); return NULL; } } /* The vector takes pointers to strings, so we need to duplicate the buffer */ dup = ast_strdup(buf); if (!dup) { ao2_ref(seen, -1); free_random_strings(vec); return NULL; } ast_str_container_add(seen, dup); /* This can't fail as the AST_VECTOR_INIT ensured enough space */ AST_VECTOR_APPEND(vec, dup); } ao2_ref(seen, -1); return vec; } /*! * \brief Compares two strings for sorting * \param a The first string to compare * \param b The second string to compare * \return The result of the comparison */ static int vector_string_cmp(const void *_a, const void *_b) { const char *a = *(const char **)_a; const char *b = *(const char **)_b; return strcmp(a, b); } /*! * \brief Finds the next prime number greater than or equal to n * \param n The number to find the next prime for * \return The next prime number */ static int next_prime(int n) { /* * The lowest prime number is 2. It's also the only even prime number, that fact * is of absolutely no relevance to this module but I put it here just for fun */ if (n <= 2) { return 2; } /* If it's divisible by 2, increment to the next odd number so we can find the next prime easier */ if (n % 2 == 0) { n++; } /* Loop until we find a prime number */ while (!ast_is_prime(n)) { n += 2; } return n; } /*! * \brief Handles the CLI command for testing ao2_container hash string lookups * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_ao2_string_hash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_container *container; struct ast_vector_string *strings; struct timeval start; unsigned int count = 0; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int buckets = 0; int i; switch (cmd) { case CLI_INIT: e->command = "performance test ao2_string_hash"; e->usage = "Usage: performance test ao2_string_hash [] [] []\n" " Test the performance of ao2_container hash string key lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } if (a->argc > 5) { if (sscanf(a->argv[5], "%30d", &buckets) <= 0 || buckets <= 0) { ast_cli(a->fd, "Invalid value for buckets: '%s'\n", a->argv[5]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; if (!buckets) { buckets = next_prime(key_count); } strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } container = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, buckets); if (!container) { ast_cli(a->fd, "Failed to allocate ao2 container\n"); free_random_strings(strings); return CLI_FAILURE; } for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { if (ast_str_container_add(container, AST_VECTOR_GET(strings, i))) { ast_cli(a->fd, "Failed to add string to container\n"); ao2_ref(container, -1); free_random_strings(strings); return CLI_FAILURE; } } ast_cli(a->fd, "Beginning ao2_string_hash performance test (%d keys, %d seconds, buckets %d)\n", key_count, duration_sec, buckets); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); ao2_cleanup(ao2_find(container, AST_VECTOR_GET(strings, idx), OBJ_SEARCH_KEY)); count++; if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "ao2_string_hash: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); ao2_ref(container, -1); free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief Compares two strings for sorting in the RBTree * \param lhs The first string to compare * \param rhs The second string to compare * \param flags The search flags * \return The result of the comparison */ static int rbtree_str_sort(const void *lhs, const void *rhs, int flags) { if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_PARTIAL_KEY) { return strncmp(lhs, rhs, strlen(rhs)); } else { return strcmp(lhs, rhs); } } /*! * \brief Compares two strings for finding in the RBTree * \param lhs The first string to compare * \param rhs The second string to compare * \param flags The search flags * \return The result of the comparison */ static int rbtree_str_cmp(void *lhs, void *rhs, int flags) { int cmp = 0; if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_PARTIAL_KEY) { cmp = strncmp(lhs, rhs, strlen(rhs)); } else { cmp = strcmp(lhs, rhs); } return cmp ? 0 : CMP_MATCH; } /*! * \brief Handles the CLI command for testing ao2_container rbtree string lookups * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_ao2_string_rbtree(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_container *container; struct ast_vector_string *strings; struct timeval start; unsigned int count = 0; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int i; switch (cmd) { case CLI_INIT: e->command = "performance test ao2_string_rbtree"; e->usage = "Usage: performance test ao2_string_rbtree [] []\n" " Test the performance of ao2_container rbtree string key lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, rbtree_str_sort, rbtree_str_cmp); if (!container) { ast_cli(a->fd, "Failed to allocate ao2 rbtree container\n"); free_random_strings(strings); return CLI_FAILURE; } for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { const char *key = AST_VECTOR_GET(strings, i); char *obj = ao2_alloc_options(strlen(key) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!obj) { ast_cli(a->fd, "Failed to allocate object for string\n"); ao2_ref(container, -1); free_random_strings(strings); return CLI_FAILURE; } strcpy(obj, key); ao2_link(container, obj); ao2_ref(obj, -1); } ast_cli(a->fd, "Beginning ao2_string_rbtree performance test (%d keys, %d seconds)\n", key_count, duration_sec); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); ao2_cleanup(ao2_find(container, AST_VECTOR_GET(strings, idx), OBJ_SEARCH_KEY)); count++; if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "ao2_string_rbtree: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); ao2_ref(container, -1); free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief Handles the CLI command for testing ao2_container list string lookups * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_ao2_string_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_container *container; struct ast_vector_string *strings; struct timeval start; unsigned int count = 0; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int i; switch (cmd) { case CLI_INIT: e->command = "performance test ao2_string_list"; e->usage = "Usage: performance test ao2_string_list [] []\n" " Test the performance of ao2_container list string key lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, rbtree_str_sort, rbtree_str_cmp); if (!container) { ast_cli(a->fd, "Failed to allocate ao2 list container\n"); free_random_strings(strings); return CLI_FAILURE; } for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { const char *key = AST_VECTOR_GET(strings, i); char *obj = ao2_alloc_options(strlen(key) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!obj) { ast_cli(a->fd, "Failed to allocate object for string\n"); ao2_ref(container, -1); free_random_strings(strings); return CLI_FAILURE; } strcpy(obj, key); ao2_link(container, obj); ao2_ref(obj, -1); } ast_cli(a->fd, "Beginning ao2_string_list performance test (%d keys, %d seconds)\n", key_count, duration_sec); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); ao2_cleanup(ao2_find(container, AST_VECTOR_GET(strings, idx), OBJ_SEARCH_KEY)); count++; if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "ao2_string_list: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); ao2_ref(container, -1); free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief Frees a string object placed into a hashtab * \param obj The string object to free */ static void hashtab_free_string(void *obj) { ast_free(obj); } /*! * \brief Handles the CLI command for testing hashtab string lookups * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_hashtab_string(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_hashtab *tab; struct ast_vector_string *strings; struct timeval start; unsigned int count = 0; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int buckets = 0; int i; switch (cmd) { case CLI_INIT: e->command = "performance test hashtab_string"; e->usage = "Usage: performance test hashtab_string [] [] []\n" " Test the performance of hashtab string lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } if (a->argc > 5) { if (sscanf(a->argv[5], "%30d", &buckets) <= 0 || buckets <= 0) { ast_cli(a->fd, "Invalid value for buckets: '%s'\n", a->argv[5]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; if (!buckets) { buckets = next_prime(key_count); } strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } tab = ast_hashtab_create(buckets, ast_hashtab_compare_strings, NULL, NULL, ast_hashtab_hash_string, 0); if (!tab) { ast_cli(a->fd, "Failed to allocate hashtab\n"); free_random_strings(strings); return CLI_FAILURE; } for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { char *dup = ast_strdup(AST_VECTOR_GET(strings, i)); if (!dup || !ast_hashtab_insert_immediate(tab, dup)) { ast_cli(a->fd, "Failed to insert string into hashtab\n"); ast_free(dup); ast_hashtab_destroy(tab, hashtab_free_string); free_random_strings(strings); return CLI_FAILURE; } } ast_cli(a->fd, "Beginning hashtab_string performance test (%d keys, %d seconds, buckets %d)\n", key_count, duration_sec, buckets); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); ast_hashtab_lookup(tab, AST_VECTOR_GET(strings, idx)); count++; if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "hashtab_string: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); ast_hashtab_destroy(tab, hashtab_free_string); free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief A node in the linked list for string lookups */ struct list_node { char *str; AST_LIST_ENTRY(list_node) list; }; /*! * \brief Handle the 'performance test list_string' command * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return CLI_SUCCESS on success, CLI_FAILURE on error */ static char *handle_cli_list_string(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { AST_LIST_HEAD_NOLOCK(list_head, list_node) head; struct ast_vector_string *strings; struct list_node *node; struct timeval start; unsigned int count = 0; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int i; switch (cmd) { case CLI_INIT: e->command = "performance test list_string"; e->usage = "Usage: performance test list_string [] []\n" " Test the performance of linked list string key lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } AST_LIST_HEAD_INIT_NOLOCK(&head); for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { node = ast_calloc(1, sizeof(*node)); if (!node) { ast_cli(a->fd, "Failed to allocate list node\n"); goto cleanup; } node->str = ast_strdup(AST_VECTOR_GET(strings, i)); if (!node->str) { ast_free(node); ast_cli(a->fd, "Failed to duplicate list node string\n"); goto cleanup; } AST_LIST_INSERT_TAIL(&head, node, list); } ast_cli(a->fd, "Beginning list_string performance test (%d keys, %d seconds)\n", key_count, duration_sec); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); AST_LIST_TRAVERSE(&head, node, list) { if (!strcmp(node->str, AST_VECTOR_GET(strings, idx))) { break; } } count++; if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "list_string: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); cleanup: while ((node = AST_LIST_REMOVE_HEAD(&head, list))) { ast_free(node->str); ast_free(node); } free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief Handles the CLI command for testing vector string binary search lookups * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_vector_bsearch_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_vector_string *strings; struct ast_vector_string vec; struct timeval start; unsigned int count = 0; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int i; switch (cmd) { case CLI_INIT: e->command = "performance test vector_string_bsearch"; e->usage = "Usage: performance test vector_string_bsearch [] []\n" " Test the performance of vector string key binary search lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } if (AST_VECTOR_INIT(&vec, key_count)) { ast_cli(a->fd, "Failed to initialize vector\n"); free_random_strings(strings); return CLI_FAILURE; } for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { char *dup = ast_strdup(AST_VECTOR_GET(strings, i)); if (!dup) { ast_cli(a->fd, "Failed to add string to vector\n"); AST_VECTOR_RESET(&vec, ast_free); AST_VECTOR_FREE(&vec); free_random_strings(strings); return CLI_FAILURE; } /* This can't fail as we're appending to a vector that we know is large enough */ AST_VECTOR_APPEND(&vec, dup); } AST_VECTOR_SORT(&vec, vector_string_cmp); ast_cli(a->fd, "Beginning vector_string_bsearch performance test (%d keys, %d seconds)\n", key_count, duration_sec); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); const char **key; /* * Unlike the other tests this actually stores the result in a variable and uses it * for determining if the count should be increased. This is done to ensure the compiler * doesn't pull some funny business and optimize away the bsearch call. */ key = AST_VECTOR_BSEARCH(&vec, AST_VECTOR_GET(strings, idx), vector_string_cmp); if (key) { count++; } if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "vector_string_bsearch: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); AST_VECTOR_RESET(&vec, ast_free); AST_VECTOR_FREE(&vec); free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief Handles the CLI command for testing vector string linear search lookups * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_vector_linear_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_vector_string *strings; struct ast_vector_string vec; struct timeval start; unsigned int count = 0; const char *target; int key_count = DEFAULT_KEY_COUNT; int duration_sec = DEFAULT_DURATION_SEC; int duration_ms; int i; switch (cmd) { case CLI_INIT: e->command = "performance test vector_string_linear"; e->usage = "Usage: performance test vector_string_linear [] []\n" " Test the performance of vector string linear search lookups.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc > 3) { if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) { ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]); return CLI_FAILURE; } } if (a->argc > 4) { if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) { ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]); return CLI_FAILURE; } } duration_ms = duration_sec * 1000; strings = generate_random_strings(key_count); if (!strings) { ast_cli(a->fd, "Failed to generate random strings\n"); return CLI_FAILURE; } if (AST_VECTOR_INIT(&vec, key_count)) { ast_cli(a->fd, "Failed to initialize vector\n"); free_random_strings(strings); return CLI_FAILURE; } for (i = 0; i < AST_VECTOR_SIZE(strings); i++) { char *dup = ast_strdup(AST_VECTOR_GET(strings, i)); if (!dup) { ast_cli(a->fd, "Failed to add string to vector\n"); AST_VECTOR_RESET(&vec, ast_free); AST_VECTOR_FREE(&vec); free_random_strings(strings); return CLI_FAILURE; } /* This can't fail as we're appending to a vector that we know is large enough */ AST_VECTOR_APPEND(&vec, dup); } ast_cli(a->fd, "Beginning vector_string_linear performance test (%d keys, %d seconds)\n", key_count, duration_sec); start = ast_tvnow(); for (;;) { int idx = ast_random() % AST_VECTOR_SIZE(strings); target = AST_VECTOR_GET(strings, idx); for (i = 0; i < AST_VECTOR_SIZE(&vec); i++) { if (!strcmp(AST_VECTOR_GET(&vec, i), target)) { break; } } count++; if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) { break; } } ast_cli(a->fd, "vector_string_linear: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec); AST_VECTOR_RESET(&vec, ast_free); AST_VECTOR_FREE(&vec); free_random_strings(strings); return CLI_SUCCESS; } /*! * \brief Handles the CLI command for running all container key lookup performance tests * \param e The CLI entry * \param cmd The command to execute * \param a The CLI arguments * \return NULL on success, or an error message on failure */ static char *handle_cli_container_key_lookup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "performance test container_key_lookup_all"; e->usage = "Usage: performance test container_key_lookup_all [] [] []\n" " Run all performance tests.\n"; return NULL; case CLI_GENERATE: return NULL; } /* * We don't care if any of these fail because each will output a message if this occurs, * as well we just pass in the arguments unaltered as none of the tests examine the * full command and they also follow the same format as this CLI command - * "performance test " so there is no weird difference in where the * arguments they care about are. */ handle_cli_ao2_string_hash(e, cmd, a); handle_cli_ao2_string_rbtree(e, cmd, a); handle_cli_ao2_string_list(e, cmd, a); handle_cli_hashtab_string(e, cmd, a); handle_cli_list_string(e, cmd, a); handle_cli_vector_bsearch_lookup(e, cmd, a); handle_cli_vector_linear_lookup(e, cmd, a); return CLI_SUCCESS; } /*! * \brief The CLI commands for performance experimentation testing */ static struct ast_cli_entry cli_performance[] = { AST_CLI_DEFINE(handle_cli_container_key_lookup_all, "Run all performance tests"), AST_CLI_DEFINE(handle_cli_ao2_string_hash, "Test the performance of ao2_container hash string lookups"), AST_CLI_DEFINE(handle_cli_ao2_string_rbtree, "Test the performance of ao2_container rbtree string lookups"), AST_CLI_DEFINE(handle_cli_ao2_string_list, "Test the performance of ao2_container list string lookups"), AST_CLI_DEFINE(handle_cli_hashtab_string, "Test the performance of hashtab string lookups"), AST_CLI_DEFINE(handle_cli_list_string, "Test the performance of linked list string lookups"), AST_CLI_DEFINE(handle_cli_vector_bsearch_lookup, "Test the performance of vector string binary search lookups"), AST_CLI_DEFINE(handle_cli_vector_linear_lookup, "Test the performance of vector string linear search lookups"), }; /*! * \brief Unloads the performance testing module * \return 0 on success */ static int unload_module(void) { ast_cli_unregister_multiple(cli_performance, ARRAY_LEN(cli_performance)); return 0; } /*! * \brief Loads the performance testing module * \return AST_MODULE_LOAD_SUCCESS on success */ static int load_module(void) { ast_cli_register_multiple(cli_performance, ARRAY_LEN(cli_performance)); return AST_MODULE_LOAD_SUCCESS; } AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Performance Test Experimentation Module");