Asterisk - The Open Source Telephony Project GIT-master-5467495
Loading...
Searching...
No Matches
test_performance.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2026, Sangoma Technologies Inc
5 *
6 * Joshua Colp <jcolp@sangoma.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*!
20 * \file
21 * \brief Performance Experimentation Tests
22 *
23 * \author Joshua Colp <jcolp@sangoma.com>
24 *
25 */
26
27/*** MODULEINFO
28 <depend>TEST_FRAMEWORK</depend>
29 <support_level>core</support_level>
30 ***/
31
32#include "asterisk.h"
33
34#include "asterisk/module.h"
35#include "asterisk/cli.h"
36#include "asterisk/time.h"
37#include "asterisk/astobj2.h"
38#include "asterisk/vector.h"
39#include "asterisk/utils.h"
40#include "asterisk/strings.h"
41#include "asterisk/hashtab.h"
43#include <stdlib.h>
44
45/*! \brief The default number of keys to generate for performance tests */
46#define DEFAULT_KEY_COUNT 250
47
48/*! \brief The default duration of the performance test in seconds */
49#define DEFAULT_DURATION_SEC 10
50
51/*! \brief The length of each test key */
52#define TEST_KEY_LENGTH 32
53
54/*! \brief The number of attempts for random strings before we give up */
55#define RANDOM_STRING_ATTEMPTS 100
56
57/*!
58 * The following tests cover the most common usage of containers in Asterisk - a key lookup
59 * based on a string which then resolves to an underlying object. The CLI commands assume that
60 * the underlying container implementations are working properly and return the requested
61 * object. These tests also only test single threaded performance. They do not test concurrent
62 * access or thread safety.
63 */
64
65/*!
66 * \brief Frees the memory used by a vector of random strings
67 * \param vec The vector of random strings to free
68 */
69static void free_random_strings(struct ast_vector_string *vec)
70{
72 AST_VECTOR_FREE(vec);
73 ast_free(vec);
74}
75
76/*!
77 * \brief Generates a vector of random strings to use as keys
78 * \param count The number of strings to generate
79 * \return A vector of random strings, or NULL on failure
80 */
82{
83 struct ast_vector_string *vec;
84 struct ao2_container *seen;
85 int i;
86
87 vec = ast_malloc(sizeof(*vec));
88 if (!vec) {
89 return NULL;
90 }
91
92 /* Preallocate the vector to avoid reallocations during insertion */
93 if (AST_VECTOR_INIT(vec, count)) {
94 ast_free(vec);
95 return NULL;
96 }
97
98 /*
99 * To eliminate collissions keep track of seen strings and retry if a collision occurs,
100 * since this is just for keeping track of seen we don't care about optimizing the bucket
101 * size of the container.
102 */
104 if (!seen) {
106 return NULL;
107 }
108
109 for (i = 0; i < count; i++) {
110 char buf[TEST_KEY_LENGTH];
111 unsigned int attempts = 0;
112 char *dup;
113
114 /* Generate a random string and retry if it collides with a previously seen string */
115 while (1) {
116 char *existing;
117
119
120 existing = ao2_find(seen, buf, OBJ_SEARCH_KEY);
121 /* We don't care about the existing string, just that it's not NULL */
122 ao2_cleanup(existing);
123 if (!existing) {
124 break;
125 }
126
127 /* To prevent infinite loops in case of collisions, limit the number of attempts */
128 attempts++;
129 if (attempts > RANDOM_STRING_ATTEMPTS) {
130 ao2_ref(seen, -1);
132 return NULL;
133 }
134 }
135
136 /* The vector takes pointers to strings, so we need to duplicate the buffer */
137 dup = ast_strdup(buf);
138 if (!dup) {
139 ao2_ref(seen, -1);
141 return NULL;
142 }
143
144 ast_str_container_add(seen, dup);
145
146 /* This can't fail as the AST_VECTOR_INIT ensured enough space */
147 AST_VECTOR_APPEND(vec, dup);
148 }
149
150 ao2_ref(seen, -1);
151
152 return vec;
153}
154
155/*!
156 * \brief Compares two strings for sorting
157 * \param a The first string to compare
158 * \param b The second string to compare
159 * \return The result of the comparison
160 */
161static int vector_string_cmp(const void *_a, const void *_b)
162{
163 const char *a = *(const char **)_a;
164 const char *b = *(const char **)_b;
165 return strcmp(a, b);
166}
167
168/*!
169 * \brief Finds the next prime number greater than or equal to n
170 * \param n The number to find the next prime for
171 * \return The next prime number
172 */
173static int next_prime(int n)
174{
175 /*
176 * The lowest prime number is 2. It's also the only even prime number, that fact
177 * is of absolutely no relevance to this module but I put it here just for fun
178 */
179 if (n <= 2) {
180 return 2;
181 }
182
183 /* If it's divisible by 2, increment to the next odd number so we can find the next prime easier */
184 if (n % 2 == 0) {
185 n++;
186 }
187
188 /* Loop until we find a prime number */
189 while (!ast_is_prime(n)) {
190 n += 2;
191 }
192
193 return n;
194}
195
196/*!
197 * \brief Handles the CLI command for testing ao2_container hash string lookups
198 * \param e The CLI entry
199 * \param cmd The command to execute
200 * \param a The CLI arguments
201 * \return NULL on success, or an error message on failure
202 */
203static char *handle_cli_ao2_string_hash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
204{
205 struct ao2_container *container;
206 struct ast_vector_string *strings;
207 struct timeval start;
208 unsigned int count = 0;
209 int key_count = DEFAULT_KEY_COUNT;
210 int duration_sec = DEFAULT_DURATION_SEC;
211 int duration_ms;
212 int buckets = 0;
213 int i;
214
215 switch (cmd) {
216 case CLI_INIT:
217 e->command = "performance test ao2_string_hash";
218 e->usage =
219 "Usage: performance test ao2_string_hash [<num_keys>] [<duration_sec>] [<buckets>]\n"
220 " Test the performance of ao2_container hash string key lookups.\n";
221 return NULL;
222 case CLI_GENERATE:
223 return NULL;
224 }
225
226 if (a->argc > 3) {
227 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
228 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
229 return CLI_FAILURE;
230 }
231 }
232 if (a->argc > 4) {
233 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
234 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
235 return CLI_FAILURE;
236 }
237 }
238 if (a->argc > 5) {
239 if (sscanf(a->argv[5], "%30d", &buckets) <= 0 || buckets <= 0) {
240 ast_cli(a->fd, "Invalid value for buckets: '%s'\n", a->argv[5]);
241 return CLI_FAILURE;
242 }
243 }
244 duration_ms = duration_sec * 1000;
245 if (!buckets) {
246 buckets = next_prime(key_count);
247 }
248
249 strings = generate_random_strings(key_count);
250 if (!strings) {
251 ast_cli(a->fd, "Failed to generate random strings\n");
252 return CLI_FAILURE;
253 }
254
256 if (!container) {
257 ast_cli(a->fd, "Failed to allocate ao2 container\n");
258 free_random_strings(strings);
259 return CLI_FAILURE;
260 }
261
262 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
264 ast_cli(a->fd, "Failed to add string to container\n");
265 ao2_ref(container, -1);
266 free_random_strings(strings);
267 return CLI_FAILURE;
268 }
269 }
270
271 ast_cli(a->fd, "Beginning ao2_string_hash performance test (%d keys, %d seconds, buckets %d)\n",
272 key_count, duration_sec, buckets);
273
274 start = ast_tvnow();
275 for (;;) {
276 int idx = ast_random() % AST_VECTOR_SIZE(strings);
277
279 count++;
280
281 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
282 break;
283 }
284 }
285
286 ast_cli(a->fd, "ao2_string_hash: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
287
288 ao2_ref(container, -1);
289 free_random_strings(strings);
290
291 return CLI_SUCCESS;
292}
293
294/*!
295 * \brief Compares two strings for sorting in the RBTree
296 * \param lhs The first string to compare
297 * \param rhs The second string to compare
298 * \param flags The search flags
299 * \return The result of the comparison
300 */
301static int rbtree_str_sort(const void *lhs, const void *rhs, int flags)
302{
303 if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_PARTIAL_KEY) {
304 return strncmp(lhs, rhs, strlen(rhs));
305 } else {
306 return strcmp(lhs, rhs);
307 }
308}
309
310/*!
311 * \brief Compares two strings for finding in the RBTree
312 * \param lhs The first string to compare
313 * \param rhs The second string to compare
314 * \param flags The search flags
315 * \return The result of the comparison
316 */
317static int rbtree_str_cmp(void *lhs, void *rhs, int flags)
318{
319 int cmp = 0;
320
321 if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_PARTIAL_KEY) {
322 cmp = strncmp(lhs, rhs, strlen(rhs));
323 } else {
324 cmp = strcmp(lhs, rhs);
325 }
326
327 return cmp ? 0 : CMP_MATCH;
328}
329
330/*!
331 * \brief Handles the CLI command for testing ao2_container rbtree string lookups
332 * \param e The CLI entry
333 * \param cmd The command to execute
334 * \param a The CLI arguments
335 * \return NULL on success, or an error message on failure
336 */
337static char *handle_cli_ao2_string_rbtree(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
338{
339 struct ao2_container *container;
340 struct ast_vector_string *strings;
341 struct timeval start;
342 unsigned int count = 0;
343 int key_count = DEFAULT_KEY_COUNT;
344 int duration_sec = DEFAULT_DURATION_SEC;
345 int duration_ms;
346 int i;
347
348 switch (cmd) {
349 case CLI_INIT:
350 e->command = "performance test ao2_string_rbtree";
351 e->usage =
352 "Usage: performance test ao2_string_rbtree [<num_keys>] [<duration_sec>]\n"
353 " Test the performance of ao2_container rbtree string key lookups.\n";
354 return NULL;
355 case CLI_GENERATE:
356 return NULL;
357 }
358
359 if (a->argc > 3) {
360 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
361 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
362 return CLI_FAILURE;
363 }
364 }
365 if (a->argc > 4) {
366 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
367 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
368 return CLI_FAILURE;
369 }
370 }
371 duration_ms = duration_sec * 1000;
372
373 strings = generate_random_strings(key_count);
374 if (!strings) {
375 ast_cli(a->fd, "Failed to generate random strings\n");
376 return CLI_FAILURE;
377 }
378
380 if (!container) {
381 ast_cli(a->fd, "Failed to allocate ao2 rbtree container\n");
382 free_random_strings(strings);
383 return CLI_FAILURE;
384 }
385
386 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
387 const char *key = AST_VECTOR_GET(strings, i);
388 char *obj = ao2_alloc_options(strlen(key) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
389
390 if (!obj) {
391 ast_cli(a->fd, "Failed to allocate object for string\n");
392 ao2_ref(container, -1);
393 free_random_strings(strings);
394 return CLI_FAILURE;
395 }
396 strcpy(obj, key);
397 ao2_link(container, obj);
398 ao2_ref(obj, -1);
399 }
400
401 ast_cli(a->fd, "Beginning ao2_string_rbtree performance test (%d keys, %d seconds)\n",
402 key_count, duration_sec);
403
404 start = ast_tvnow();
405 for (;;) {
406 int idx = ast_random() % AST_VECTOR_SIZE(strings);
407
409 count++;
410
411 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
412 break;
413 }
414 }
415
416 ast_cli(a->fd, "ao2_string_rbtree: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
417
418 ao2_ref(container, -1);
419 free_random_strings(strings);
420
421 return CLI_SUCCESS;
422}
423
424/*!
425 * \brief Handles the CLI command for testing ao2_container list string lookups
426 * \param e The CLI entry
427 * \param cmd The command to execute
428 * \param a The CLI arguments
429 * \return NULL on success, or an error message on failure
430 */
431static char *handle_cli_ao2_string_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
432{
433 struct ao2_container *container;
434 struct ast_vector_string *strings;
435 struct timeval start;
436 unsigned int count = 0;
437 int key_count = DEFAULT_KEY_COUNT;
438 int duration_sec = DEFAULT_DURATION_SEC;
439 int duration_ms;
440 int i;
441
442 switch (cmd) {
443 case CLI_INIT:
444 e->command = "performance test ao2_string_list";
445 e->usage =
446 "Usage: performance test ao2_string_list [<num_keys>] [<duration_sec>]\n"
447 " Test the performance of ao2_container list string key lookups.\n";
448 return NULL;
449 case CLI_GENERATE:
450 return NULL;
451 }
452
453 if (a->argc > 3) {
454 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
455 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
456 return CLI_FAILURE;
457 }
458 }
459 if (a->argc > 4) {
460 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
461 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
462 return CLI_FAILURE;
463 }
464 }
465 duration_ms = duration_sec * 1000;
466
467 strings = generate_random_strings(key_count);
468 if (!strings) {
469 ast_cli(a->fd, "Failed to generate random strings\n");
470 return CLI_FAILURE;
471 }
472
474 if (!container) {
475 ast_cli(a->fd, "Failed to allocate ao2 list container\n");
476 free_random_strings(strings);
477 return CLI_FAILURE;
478 }
479
480 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
481 const char *key = AST_VECTOR_GET(strings, i);
482 char *obj = ao2_alloc_options(strlen(key) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
483
484 if (!obj) {
485 ast_cli(a->fd, "Failed to allocate object for string\n");
486 ao2_ref(container, -1);
487 free_random_strings(strings);
488 return CLI_FAILURE;
489 }
490 strcpy(obj, key);
491 ao2_link(container, obj);
492 ao2_ref(obj, -1);
493 }
494
495 ast_cli(a->fd, "Beginning ao2_string_list performance test (%d keys, %d seconds)\n",
496 key_count, duration_sec);
497
498 start = ast_tvnow();
499 for (;;) {
500 int idx = ast_random() % AST_VECTOR_SIZE(strings);
501
503 count++;
504
505 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
506 break;
507 }
508 }
509
510 ast_cli(a->fd, "ao2_string_list: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
511
512 ao2_ref(container, -1);
513 free_random_strings(strings);
514
515 return CLI_SUCCESS;
516}
517
518/*!
519 * \brief Frees a string object placed into a hashtab
520 * \param obj The string object to free
521 */
522static void hashtab_free_string(void *obj)
523{
524 ast_free(obj);
525}
526
527/*!
528 * \brief Handles the CLI command for testing hashtab string lookups
529 * \param e The CLI entry
530 * \param cmd The command to execute
531 * \param a The CLI arguments
532 * \return NULL on success, or an error message on failure
533 */
534static char *handle_cli_hashtab_string(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
535{
536 struct ast_hashtab *tab;
537 struct ast_vector_string *strings;
538 struct timeval start;
539 unsigned int count = 0;
540 int key_count = DEFAULT_KEY_COUNT;
541 int duration_sec = DEFAULT_DURATION_SEC;
542 int duration_ms;
543 int buckets = 0;
544 int i;
545
546 switch (cmd) {
547 case CLI_INIT:
548 e->command = "performance test hashtab_string";
549 e->usage =
550 "Usage: performance test hashtab_string [<num_keys>] [<duration_sec>] [<buckets>]\n"
551 " Test the performance of hashtab string lookups.\n";
552 return NULL;
553 case CLI_GENERATE:
554 return NULL;
555 }
556
557 if (a->argc > 3) {
558 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
559 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
560 return CLI_FAILURE;
561 }
562 }
563 if (a->argc > 4) {
564 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
565 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
566 return CLI_FAILURE;
567 }
568 }
569 if (a->argc > 5) {
570 if (sscanf(a->argv[5], "%30d", &buckets) <= 0 || buckets <= 0) {
571 ast_cli(a->fd, "Invalid value for buckets: '%s'\n", a->argv[5]);
572 return CLI_FAILURE;
573 }
574 }
575 duration_ms = duration_sec * 1000;
576 if (!buckets) {
577 buckets = next_prime(key_count);
578 }
579
580 strings = generate_random_strings(key_count);
581 if (!strings) {
582 ast_cli(a->fd, "Failed to generate random strings\n");
583 return CLI_FAILURE;
584 }
585
587 if (!tab) {
588 ast_cli(a->fd, "Failed to allocate hashtab\n");
589 free_random_strings(strings);
590 return CLI_FAILURE;
591 }
592
593 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
594 char *dup = ast_strdup(AST_VECTOR_GET(strings, i));
595
596 if (!dup || !ast_hashtab_insert_immediate(tab, dup)) {
597 ast_cli(a->fd, "Failed to insert string into hashtab\n");
598 ast_free(dup);
600 free_random_strings(strings);
601 return CLI_FAILURE;
602 }
603 }
604
605 ast_cli(a->fd, "Beginning hashtab_string performance test (%d keys, %d seconds, buckets %d)\n",
606 key_count, duration_sec, buckets);
607
608 start = ast_tvnow();
609 for (;;) {
610 int idx = ast_random() % AST_VECTOR_SIZE(strings);
611
612 ast_hashtab_lookup(tab, AST_VECTOR_GET(strings, idx));
613 count++;
614
615 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
616 break;
617 }
618 }
619
620 ast_cli(a->fd, "hashtab_string: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
621
623 free_random_strings(strings);
624
625 return CLI_SUCCESS;
626}
627
628/*!
629 * \brief A node in the linked list for string lookups
630 */
635
636/*!
637 * \brief Handle the 'performance test list_string' command
638 * \param e The CLI entry
639 * \param cmd The command to execute
640 * \param a The CLI arguments
641 * \return CLI_SUCCESS on success, CLI_FAILURE on error
642 */
643static char *handle_cli_list_string(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
644{
645 AST_LIST_HEAD_NOLOCK(list_head, list_node) head;
646 struct ast_vector_string *strings;
647 struct list_node *node;
648 struct timeval start;
649 unsigned int count = 0;
650 int key_count = DEFAULT_KEY_COUNT;
651 int duration_sec = DEFAULT_DURATION_SEC;
652 int duration_ms;
653 int i;
654
655 switch (cmd) {
656 case CLI_INIT:
657 e->command = "performance test list_string";
658 e->usage =
659 "Usage: performance test list_string [<num_keys>] [<duration_sec>]\n"
660 " Test the performance of linked list string key lookups.\n";
661 return NULL;
662 case CLI_GENERATE:
663 return NULL;
664 }
665
666 if (a->argc > 3) {
667 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
668 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
669 return CLI_FAILURE;
670 }
671 }
672 if (a->argc > 4) {
673 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
674 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
675 return CLI_FAILURE;
676 }
677 }
678 duration_ms = duration_sec * 1000;
679
680 strings = generate_random_strings(key_count);
681 if (!strings) {
682 ast_cli(a->fd, "Failed to generate random strings\n");
683 return CLI_FAILURE;
684 }
685
687
688 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
689 node = ast_calloc(1, sizeof(*node));
690 if (!node) {
691 ast_cli(a->fd, "Failed to allocate list node\n");
692 goto cleanup;
693 }
694
695 node->str = ast_strdup(AST_VECTOR_GET(strings, i));
696 if (!node->str) {
697 ast_free(node);
698 ast_cli(a->fd, "Failed to duplicate list node string\n");
699 goto cleanup;
700 }
701
702 AST_LIST_INSERT_TAIL(&head, node, list);
703 }
704
705 ast_cli(a->fd, "Beginning list_string performance test (%d keys, %d seconds)\n",
706 key_count, duration_sec);
707
708 start = ast_tvnow();
709 for (;;) {
710 int idx = ast_random() % AST_VECTOR_SIZE(strings);
711
712 AST_LIST_TRAVERSE(&head, node, list) {
713 if (!strcmp(node->str, AST_VECTOR_GET(strings, idx))) {
714 break;
715 }
716 }
717 count++;
718
719 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
720 break;
721 }
722 }
723
724 ast_cli(a->fd, "list_string: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
725
726cleanup:
727 while ((node = AST_LIST_REMOVE_HEAD(&head, list))) {
728 ast_free(node->str);
729 ast_free(node);
730 }
731
732 free_random_strings(strings);
733
734 return CLI_SUCCESS;
735}
736
737/*!
738 * \brief Handles the CLI command for testing vector string binary search lookups
739 * \param e The CLI entry
740 * \param cmd The command to execute
741 * \param a The CLI arguments
742 * \return NULL on success, or an error message on failure
743 */
744static char *handle_cli_vector_bsearch_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
745{
746 struct ast_vector_string *strings;
747 struct ast_vector_string vec;
748 struct timeval start;
749 unsigned int count = 0;
750 int key_count = DEFAULT_KEY_COUNT;
751 int duration_sec = DEFAULT_DURATION_SEC;
752 int duration_ms;
753 int i;
754
755 switch (cmd) {
756 case CLI_INIT:
757 e->command = "performance test vector_string_bsearch";
758 e->usage =
759 "Usage: performance test vector_string_bsearch [<num_keys>] [<duration_sec>]\n"
760 " Test the performance of vector string key binary search lookups.\n";
761 return NULL;
762 case CLI_GENERATE:
763 return NULL;
764 }
765
766 if (a->argc > 3) {
767 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
768 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
769 return CLI_FAILURE;
770 }
771 }
772 if (a->argc > 4) {
773 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
774 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
775 return CLI_FAILURE;
776 }
777 }
778 duration_ms = duration_sec * 1000;
779
780 strings = generate_random_strings(key_count);
781 if (!strings) {
782 ast_cli(a->fd, "Failed to generate random strings\n");
783 return CLI_FAILURE;
784 }
785
786 if (AST_VECTOR_INIT(&vec, key_count)) {
787 ast_cli(a->fd, "Failed to initialize vector\n");
788 free_random_strings(strings);
789 return CLI_FAILURE;
790 }
791
792 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
793 char *dup = ast_strdup(AST_VECTOR_GET(strings, i));
794
795 if (!dup) {
796 ast_cli(a->fd, "Failed to add string to vector\n");
798 AST_VECTOR_FREE(&vec);
799 free_random_strings(strings);
800 return CLI_FAILURE;
801 }
802
803 /* This can't fail as we're appending to a vector that we know is large enough */
804 AST_VECTOR_APPEND(&vec, dup);
805 }
806
808
809 ast_cli(a->fd, "Beginning vector_string_bsearch performance test (%d keys, %d seconds)\n",
810 key_count, duration_sec);
811
812 start = ast_tvnow();
813 for (;;) {
814 int idx = ast_random() % AST_VECTOR_SIZE(strings);
815 const char **key;
816
817 /*
818 * Unlike the other tests this actually stores the result in a variable and uses it
819 * for determining if the count should be increased. This is done to ensure the compiler
820 * doesn't pull some funny business and optimize away the bsearch call.
821 */
822 key = AST_VECTOR_BSEARCH(&vec, AST_VECTOR_GET(strings, idx), vector_string_cmp);
823 if (key) {
824 count++;
825 }
826
827 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
828 break;
829 }
830 }
831
832 ast_cli(a->fd, "vector_string_bsearch: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
833
835 AST_VECTOR_FREE(&vec);
836 free_random_strings(strings);
837
838 return CLI_SUCCESS;
839}
840
841/*!
842 * \brief Handles the CLI command for testing vector string linear search lookups
843 * \param e The CLI entry
844 * \param cmd The command to execute
845 * \param a The CLI arguments
846 * \return NULL on success, or an error message on failure
847 */
848static char *handle_cli_vector_linear_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
849{
850 struct ast_vector_string *strings;
851 struct ast_vector_string vec;
852 struct timeval start;
853 unsigned int count = 0;
854 const char *target;
855 int key_count = DEFAULT_KEY_COUNT;
856 int duration_sec = DEFAULT_DURATION_SEC;
857 int duration_ms;
858 int i;
859
860 switch (cmd) {
861 case CLI_INIT:
862 e->command = "performance test vector_string_linear";
863 e->usage =
864 "Usage: performance test vector_string_linear [<num_keys>] [<duration_sec>]\n"
865 " Test the performance of vector string linear search lookups.\n";
866 return NULL;
867 case CLI_GENERATE:
868 return NULL;
869 }
870
871 if (a->argc > 3) {
872 if (sscanf(a->argv[3], "%30d", &key_count) <= 0 || key_count <= 0) {
873 ast_cli(a->fd, "Invalid value for num_keys: '%s'\n", a->argv[3]);
874 return CLI_FAILURE;
875 }
876 }
877 if (a->argc > 4) {
878 if (sscanf(a->argv[4], "%30d", &duration_sec) <= 0 || duration_sec <= 0) {
879 ast_cli(a->fd, "Invalid value for duration_sec: '%s'\n", a->argv[4]);
880 return CLI_FAILURE;
881 }
882 }
883 duration_ms = duration_sec * 1000;
884
885 strings = generate_random_strings(key_count);
886 if (!strings) {
887 ast_cli(a->fd, "Failed to generate random strings\n");
888 return CLI_FAILURE;
889 }
890
891 if (AST_VECTOR_INIT(&vec, key_count)) {
892 ast_cli(a->fd, "Failed to initialize vector\n");
893 free_random_strings(strings);
894 return CLI_FAILURE;
895 }
896
897 for (i = 0; i < AST_VECTOR_SIZE(strings); i++) {
898 char *dup = ast_strdup(AST_VECTOR_GET(strings, i));
899
900 if (!dup) {
901 ast_cli(a->fd, "Failed to add string to vector\n");
903 AST_VECTOR_FREE(&vec);
904 free_random_strings(strings);
905 return CLI_FAILURE;
906 }
907
908 /* This can't fail as we're appending to a vector that we know is large enough */
909 AST_VECTOR_APPEND(&vec, dup);
910 }
911
912 ast_cli(a->fd, "Beginning vector_string_linear performance test (%d keys, %d seconds)\n",
913 key_count, duration_sec);
914
915 start = ast_tvnow();
916 for (;;) {
917 int idx = ast_random() % AST_VECTOR_SIZE(strings);
918
919 target = AST_VECTOR_GET(strings, idx);
920 for (i = 0; i < AST_VECTOR_SIZE(&vec); i++) {
921 if (!strcmp(AST_VECTOR_GET(&vec, i), target)) {
922 break;
923 }
924 }
925 count++;
926
927 if (ast_tvdiff_ms(ast_tvnow(), start) >= duration_ms) {
928 break;
929 }
930 }
931
932 ast_cli(a->fd, "vector_string_linear: %u lookups in %d seconds (%u lookups/sec)\n", count, duration_sec, count / duration_sec);
933
935 AST_VECTOR_FREE(&vec);
936 free_random_strings(strings);
937
938 return CLI_SUCCESS;
939}
940
941/*!
942 * \brief Handles the CLI command for running all container key lookup performance tests
943 * \param e The CLI entry
944 * \param cmd The command to execute
945 * \param a The CLI arguments
946 * \return NULL on success, or an error message on failure
947 */
948static char *handle_cli_container_key_lookup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
949{
950 switch (cmd) {
951 case CLI_INIT:
952 e->command = "performance test container_key_lookup_all";
953 e->usage =
954 "Usage: performance test container_key_lookup_all [<num_keys>] [<duration_sec>] [<buckets>]\n"
955 " Run all performance tests.\n";
956 return NULL;
957 case CLI_GENERATE:
958 return NULL;
959 }
960
961 /*
962 * We don't care if any of these fail because each will output a message if this occurs,
963 * as well we just pass in the arguments unaltered as none of the tests examine the
964 * full command and they also follow the same format as this CLI command -
965 * "performance test <name> <arguments>" so there is no weird difference in where the
966 * arguments they care about are.
967 */
972 handle_cli_list_string(e, cmd, a);
975
976 return CLI_SUCCESS;
977}
978
979/*!
980 * \brief The CLI commands for performance experimentation testing
981 */
983 AST_CLI_DEFINE(handle_cli_container_key_lookup_all, "Run all performance tests"),
984 AST_CLI_DEFINE(handle_cli_ao2_string_hash, "Test the performance of ao2_container hash string lookups"),
985 AST_CLI_DEFINE(handle_cli_ao2_string_rbtree, "Test the performance of ao2_container rbtree string lookups"),
986 AST_CLI_DEFINE(handle_cli_ao2_string_list, "Test the performance of ao2_container list string lookups"),
987 AST_CLI_DEFINE(handle_cli_hashtab_string, "Test the performance of hashtab string lookups"),
988 AST_CLI_DEFINE(handle_cli_list_string, "Test the performance of linked list string lookups"),
989 AST_CLI_DEFINE(handle_cli_vector_bsearch_lookup, "Test the performance of vector string binary search lookups"),
990 AST_CLI_DEFINE(handle_cli_vector_linear_lookup, "Test the performance of vector string linear search lookups"),
991};
992
993/*!
994 * \brief Unloads the performance testing module
995 * \return 0 on success
996 */
997static int unload_module(void)
998{
1000 return 0;
1001}
1002
1003/*!
1004 * \brief Loads the performance testing module
1005 * \return AST_MODULE_LOAD_SUCCESS on success
1006 */
1012
1013AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Performance Test Experimentation Module");
void ast_cli_unregister_multiple(void)
Definition ael_main.c:408
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_malloc(len)
A wrapper for malloc()
Definition astmm.h:191
#define ao2_link(container, obj)
Add an object to a container.
Definition astobj2.h:1532
@ CMP_MATCH
Definition astobj2.h:1027
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition astobj2.h:367
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_find(container, arg, flags)
Definition astobj2.h:1736
#define ao2_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn)
Allocate and initialize a red-black tree container.
Definition astobj2.h:1349
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition astobj2.h:459
#define ao2_alloc_options(data_size, destructor_fn, options)
Definition astobj2.h:404
@ OBJ_SEARCH_PARTIAL_KEY
The arg parameter is a partial search key similar to OBJ_SEARCH_KEY.
Definition astobj2.h:1116
@ OBJ_SEARCH_MASK
Search option field mask.
Definition astobj2.h:1072
@ OBJ_SEARCH_KEY
The arg parameter is a search key, but is not an object.
Definition astobj2.h:1101
#define ao2_container_alloc_list(ao2_options, container_options, sort_fn, cmp_fn)
Allocate and initialize a list container.
Definition astobj2.h:1327
Standard Command Line Interface.
#define CLI_SUCCESS
Definition cli.h:44
#define AST_CLI_DEFINE(fn, txt,...)
Definition cli.h:197
void ast_cli(int fd, const char *fmt,...)
Definition clicompat.c:6
@ CLI_INIT
Definition cli.h:152
@ CLI_GENERATE
Definition cli.h:153
#define CLI_FAILURE
Definition cli.h:46
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition cli.h:265
char buf[BUFSIZE]
Definition eagi_proxy.c:66
Generic (perhaps overly so) hashtable implementation Hash Table support in Asterisk.
void ast_hashtab_destroy(struct ast_hashtab *tab, void(*objdestroyfunc)(void *obj))
This func will free the hash table and all its memory.
Definition hashtab.c:363
int ast_is_prime(int num)
Determines if the specified number is prime.
Definition hashtab.c:101
int ast_hashtab_compare_strings(const void *a, const void *b)
Compares two strings for equality.
Definition hashtab.c:52
#define ast_hashtab_insert_immediate(tab, obj)
Insert without checking.
Definition hashtab.h:290
void * ast_hashtab_lookup(struct ast_hashtab *tab, const void *obj)
Lookup this object in the hash table.
Definition hashtab.c:486
#define ast_hashtab_create(initial_buckets, compare, resize, newsize, hash, do_locking)
Create the hashtable list.
Definition hashtab.h:254
unsigned int ast_hashtab_hash_string(const void *obj)
Hashes a string to a number.
Definition hashtab.c:153
A set of macros to manage forward-linked lists.
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Asterisk module definitions.
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition module.h:581
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition module.h:70
struct ao2_container * container
Definition res_fax.c:603
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition res_stasis.c:327
#define NULL
Definition resample.c:96
String manipulation functions.
char * ast_generate_random_string(char *buf, size_t size)
Create a pseudo-random string of a fixed length.
Definition strings.c:226
struct ao2_container * ast_str_container_alloc_options(enum ao2_alloc_opts opts, int buckets)
Allocates a hash container for bare strings.
Definition strings.c:200
int ast_str_container_add(struct ao2_container *str_container, const char *add)
Adds a string to a string container allocated by ast_str_container_alloc.
Definition strings.c:205
Generic container type.
descriptor for a cli entry.
Definition cli.h:171
char * command
Definition cli.h:186
const char * usage
Definition cli.h:177
String vector definitions.
Definition vector.h:55
A node in the linked list for string lookups.
struct list_node::@545 list
static struct test_val b
static struct test_val a
static char * handle_cli_ao2_string_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for testing ao2_container list string lookups.
static int rbtree_str_sort(const void *lhs, const void *rhs, int flags)
Compares two strings for sorting in the RBTree.
static char * handle_cli_ao2_string_hash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for testing ao2_container hash string lookups.
static char * handle_cli_vector_bsearch_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for testing vector string binary search lookups.
static char * handle_cli_hashtab_string(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for testing hashtab string lookups.
static int vector_string_cmp(const void *_a, const void *_b)
Compares two strings for sorting.
#define RANDOM_STRING_ATTEMPTS
The number of attempts for random strings before we give up.
static void hashtab_free_string(void *obj)
Frees a string object placed into a hashtab.
#define TEST_KEY_LENGTH
The length of each test key.
static char * handle_cli_container_key_lookup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for running all container key lookup performance tests.
static struct ast_cli_entry cli_performance[]
The CLI commands for performance experimentation testing.
static struct ast_vector_string * generate_random_strings(int count)
Generates a vector of random strings to use as keys.
static char * handle_cli_ao2_string_rbtree(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for testing ao2_container rbtree string lookups.
#define DEFAULT_DURATION_SEC
The default duration of the performance test in seconds.
#define DEFAULT_KEY_COUNT
The default number of keys to generate for performance tests.
static int next_prime(int n)
Finds the next prime number greater than or equal to n.
static int load_module(void)
Loads the performance testing module.
static void free_random_strings(struct ast_vector_string *vec)
Frees the memory used by a vector of random strings.
static char * handle_cli_vector_linear_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handles the CLI command for testing vector string linear search lookups.
static int unload_module(void)
Unloads the performance testing module.
static char * handle_cli_list_string(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handle the 'performance test list_string' command.
static int rbtree_str_cmp(void *lhs, void *rhs, int flags)
Compares two strings for finding in the RBTree.
Time-related functions and macros.
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition time.h:107
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition time.h:159
Utility functions.
long int ast_random(void)
Definition utils.c:2346
#define ARRAY_LEN(a)
Definition utils.h:706
Vector container support.
#define AST_VECTOR_RESET(vec, cleanup)
Reset vector.
Definition vector.h:653
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition vector.h:637
#define AST_VECTOR_BSEARCH(vec, key, cmp)
Binary search a sorted vector.
Definition vector.h:422
#define AST_VECTOR_SORT(vec, cmp)
Sort a vector in-place.
Definition vector.h:407
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition vector.h:185
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition vector.h:124
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition vector.h:267
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition vector.h:708