mirror of https://github.com/asterisk/asterisk
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1014 lines
28 KiB
1014 lines
28 KiB
/*
|
|
* Asterisk -- An open source telephony toolkit.
|
|
*
|
|
* Copyright (C) 2026, Sangoma Technologies Inc
|
|
*
|
|
* Joshua Colp <jcolp@sangoma.com>
|
|
*
|
|
* 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 <jcolp@sangoma.com>
|
|
*
|
|
*/
|
|
|
|
/*** MODULEINFO
|
|
<depend>TEST_FRAMEWORK</depend>
|
|
<support_level>core</support_level>
|
|
***/
|
|
|
|
#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 <stdlib.h>
|
|
|
|
/*! \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 [<num_keys>] [<duration_sec>] [<buckets>]\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 [<num_keys>] [<duration_sec>]\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 [<num_keys>] [<duration_sec>]\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 [<num_keys>] [<duration_sec>] [<buckets>]\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 [<num_keys>] [<duration_sec>]\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 [<num_keys>] [<duration_sec>]\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 [<num_keys>] [<duration_sec>]\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 [<num_keys>] [<duration_sec>] [<buckets>]\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 <name> <arguments>" 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");
|