Start a new app for running some performance tests of mempool drivers with different parameters. If run with no parameters, prompt for the options to use.
Signed-off-by: Bruce Richardson <[email protected]> --- app/meson.build | 1 + app/test-mempool-perf/main.c | 330 ++++++++++++++++++++++++++++++ app/test-mempool-perf/meson.build | 8 + doc/guides/tools/index.rst | 1 + doc/guides/tools/mempoolperf.rst | 139 +++++++++++++ 5 files changed, 479 insertions(+) create mode 100644 app/test-mempool-perf/main.c create mode 100644 app/test-mempool-perf/meson.build create mode 100644 doc/guides/tools/mempoolperf.rst diff --git a/app/meson.build b/app/meson.build index 1798db3ae4..81f36f497b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -31,6 +31,7 @@ apps = [ 'test-pipeline', 'test-pmd', 'test-regex', + 'test-mempool-perf', 'test-sad', 'test-security-perf', ] diff --git a/app/test-mempool-perf/main.c b/app/test-mempool-perf/main.c new file mode 100644 index 0000000000..877abc2170 --- /dev/null +++ b/app/test-mempool-perf/main.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Intel Corporation + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <inttypes.h> + +#include <rte_argparse.h> +#include <rte_eal.h> +#include <rte_lcore.h> +#include <rte_mempool.h> +#include <rte_string_fns.h> + +#define DEFAULT_CACHE_SIZE 512 +#define DEFAULT_RAND_FACTOR 8 +#define DEFAULT_BURST_SIZE 32 +#define DEFAULT_NB_BUFS_PER_LCORE 1024 + +struct test_config { + char mempool_type[RTE_MEMPOOL_NAMESIZE]; + uint32_t nb_bufs; + uint32_t cache_size; + uint32_t nb_threads; + uint32_t rand_factor; + uint32_t burst_size; + bool access_on_alloc; +}; + +static struct test_config cfg = { + .mempool_type = "", + .nb_bufs = 0, /* 0 means: compute from lcore count */ + .cache_size = DEFAULT_CACHE_SIZE, + .nb_threads = 0, /* 0 means: use all worker lcores */ + .rand_factor = DEFAULT_RAND_FACTOR, + .burst_size = DEFAULT_BURST_SIZE, + .access_on_alloc = true, +}; + +static void +apply_defaults(void) +{ + unsigned int nb_workers; + + nb_workers = rte_lcore_count() > 1 ? rte_lcore_count() - 1 : 1; + + if (cfg.nb_bufs == 0) + cfg.nb_bufs = DEFAULT_NB_BUFS_PER_LCORE * rte_lcore_count(); + if (cfg.nb_threads == 0) + cfg.nb_threads = nb_workers; +} + +static bool +is_valid_mempool_type(const char *name) +{ + uint32_t i; + + for (i = 0; i < rte_mempool_ops_table.num_ops; i++) + if (strcmp(name, rte_mempool_ops_table.ops[i].name) == 0) + return true; + return false; +} + +static void +list_mempool_types(void) +{ + uint32_t i; + + printf("Available mempool types:\n"); + for (i = 0; i < rte_mempool_ops_table.num_ops; i++) + printf(" [%u] %s\n", i, rte_mempool_ops_table.ops[i].name); +} + +static void +print_config(void) +{ + printf("\n=== test-mempool-perf configuration ===\n"); + printf(" Mempool type : %s\n", cfg.mempool_type); + printf(" Num buffers : %" PRIu32 "\n", cfg.nb_bufs); + printf(" Cache size : %" PRIu32 "\n", cfg.cache_size); + printf(" Thread count : %" PRIu32 "\n", cfg.nb_threads); + printf(" Randomness factor: %" PRIu32 "\n", cfg.rand_factor); + printf(" Burst size : %" PRIu32 "\n", cfg.burst_size); + printf(" Access on alloc : %s\n", cfg.access_on_alloc ? "yes" : "no"); + printf("========================================\n\n"); +} + +static void +print_reproduce_cmd(void) +{ + printf("Reproduce using parameters:" + " -M %s -n %" PRIu32 " -c %" PRIu32 " -t %" PRIu32 " -r %" PRIu32 " -b %" PRIu32 " %s\n\n", + cfg.mempool_type, cfg.nb_bufs, cfg.cache_size, cfg.nb_threads, cfg.rand_factor, + cfg.burst_size, cfg.access_on_alloc ? "-A" : "-N"); +} + +static void +trim_newline(char *s) +{ + size_t len = strlen(s); + + if (len > 0 && s[len - 1] == '\n') + s[len - 1] = '\0'; +} + +static int +prompt_uint32(const char *prompt, uint32_t *val) +{ + char buf[64]; + char *end; + unsigned long v; + + fputs(prompt, stdout); + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) + return -1; + trim_newline(buf); + if (buf[0] == '\0') + return 0; /* keep default */ + + v = strtoul(buf, &end, 0); + if (*end != '\0') { + fprintf(stderr, "Invalid number: %s\n", buf); + return -1; + } + *val = (uint32_t)v; + return 1; +} + +static int +run_interactive_mode(void) +{ + char prompt[128]; + char buf[RTE_MEMPOOL_NAMESIZE]; + + printf("\n=== test-mempool-perf interactive setup ===\n"); + printf("Press Enter to accept the default value.\n\n"); + + list_mempool_types(); + printf("\n"); + + /* Mempool type is required; loop until a valid name is entered */ + for (;;) { + printf("Mempool type (required): "); + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) + return -1; + trim_newline(buf); + if (buf[0] == '\0') { + printf(" A mempool type is required; please enter one of the names above.\n"); + continue; + } + if (is_valid_mempool_type(buf)) + break; + printf(" Unknown mempool type '%s'; please enter one of the names above.\n", buf); + } + rte_strscpy(cfg.mempool_type, buf, sizeof(cfg.mempool_type)); + + /* Compute numeric defaults now that EAL is initialised */ + apply_defaults(); + + /* Number of buffers */ + snprintf(prompt, sizeof(prompt), + "Number of buffers [%" PRIu32 "]: ", cfg.nb_bufs); + if (prompt_uint32(prompt, &cfg.nb_bufs) < 0) + return -1; + + /* Cache size */ + snprintf(prompt, sizeof(prompt), + "Cache size [%" PRIu32 "]: ", cfg.cache_size); + if (prompt_uint32(prompt, &cfg.cache_size) < 0) + return -1; + + /* Thread count */ + snprintf(prompt, sizeof(prompt), + "Thread count [%" PRIu32 "]: ", cfg.nb_threads); + if (prompt_uint32(prompt, &cfg.nb_threads) < 0) + return -1; + + /* Randomness factor */ + snprintf(prompt, sizeof(prompt), + "Randomness factor [%" PRIu32 "]: ", cfg.rand_factor); + if (prompt_uint32(prompt, &cfg.rand_factor) < 0) + return -1; + + /* Burst size */ + snprintf(prompt, sizeof(prompt), + "Burst size [%" PRIu32 "]: ", cfg.burst_size); + if (prompt_uint32(prompt, &cfg.burst_size) < 0) + return -1; + /* Access on allocation */ + printf("Access buffers on allocation [%s]: ", + cfg.access_on_alloc ? "yes" : "no"); + fflush(stdout); + { + char yn[16]; + + if (fgets(yn, sizeof(yn), stdin) == NULL) + return -1; + trim_newline(yn); + if (yn[0] == 'y' || yn[0] == 'Y') + cfg.access_on_alloc = true; + else if (yn[0] == 'n' || yn[0] == 'N') + cfg.access_on_alloc = false; + /* else keep default */ + } + + return 0; +} + +static bool summary_only; + +/* Used only in non-interactive mode to receive the --mempool-type string */ +static const char *mempool_type_arg; + +static int +parse_args(int argc, char **argv) +{ + static struct rte_argparse obj = { + .prog_name = "test-mempool-perf", + .usage = "[EAL options] -- [options]", + .descriptor = "Mempool performance tester", + .exit_on_error = true, + .args = { + { "--mempool-type", "-M", + "Mempool driver to test (required in non-interactive mode)", + (void *)&mempool_type_arg, NULL, + RTE_ARGPARSE_VALUE_REQUIRED, RTE_ARGPARSE_VALUE_TYPE_STR, + }, + { "--nb-bufs", "-n", + "Number of buffers in the pool (default: 1024 * lcore count)", + (void *)&cfg.nb_bufs, NULL, + RTE_ARGPARSE_VALUE_REQUIRED, RTE_ARGPARSE_VALUE_TYPE_U32, + }, + { "--cache-size", "-c", + "Per-lcore object cache size (default: 512)", + (void *)&cfg.cache_size, NULL, + RTE_ARGPARSE_VALUE_REQUIRED, RTE_ARGPARSE_VALUE_TYPE_U32, + }, + { "--nb-threads", "-t", + "Number of worker threads to use (default: all worker lcores)", + (void *)&cfg.nb_threads, NULL, + RTE_ARGPARSE_VALUE_REQUIRED, RTE_ARGPARSE_VALUE_TYPE_U32, + }, + { "--rand-factor", "-r", + "Randomness factor for alloc/free burst sizes (default: 8)", + (void *)&cfg.rand_factor, NULL, + RTE_ARGPARSE_VALUE_REQUIRED, RTE_ARGPARSE_VALUE_TYPE_U32, + }, + { "--burst-size", "-b", + "Number of objects per alloc/free burst (default: 32)", + (void *)&cfg.burst_size, NULL, + RTE_ARGPARSE_VALUE_REQUIRED, RTE_ARGPARSE_VALUE_TYPE_U32, + }, + { "--access-on-alloc", "-A", + "Enable touching buffer memory on allocation (default: enabled)", + (void *)&cfg.access_on_alloc, (void *)true, + RTE_ARGPARSE_VALUE_NONE, RTE_ARGPARSE_VALUE_TYPE_BOOL, + }, + { "--no-access-on-alloc", "-N", + "Disable touching buffer memory on allocation", + (void *)&cfg.access_on_alloc, (void *)false, + RTE_ARGPARSE_VALUE_NONE, RTE_ARGPARSE_VALUE_TYPE_BOOL, + }, + { "--summary", "-s", + "Print only the aggregate total, not per-lcore results", + (void *)&summary_only, (void *)true, + RTE_ARGPARSE_VALUE_NONE, RTE_ARGPARSE_VALUE_TYPE_BOOL, + }, + ARGPARSE_ARG_END(), + }, + }; + int ret; + + ret = rte_argparse_parse(&obj, argc, argv); + if (ret < 0) + return ret; + + if (mempool_type_arg != NULL) + rte_strscpy(cfg.mempool_type, mempool_type_arg, + sizeof(cfg.mempool_type)); + + return 0; +} + +int +main(int argc, char **argv) +{ + int ret; + + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n"); + argc -= ret; + argv += ret; + + if (argc == 1) { + /* No app-specific arguments: enter interactive configuration */ + ret = run_interactive_mode(); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Interactive configuration failed\n"); + print_reproduce_cmd(); + } else { + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid application arguments\n"); + + if (cfg.mempool_type[0] == '\0') { + fprintf(stderr, + "Error: --mempool-type is required in non-interactive mode\n"); + list_mempool_types(); + rte_exit(EXIT_FAILURE, "Mempool type not specified\n"); + } + if (!is_valid_mempool_type(cfg.mempool_type)) { + fprintf(stderr, "Error: unknown mempool type '%s'\n", + cfg.mempool_type); + list_mempool_types(); + rte_exit(EXIT_FAILURE, "Invalid mempool type\n"); + } + + apply_defaults(); + } + + print_config(); + + rte_eal_cleanup(); + return 0; +} diff --git a/app/test-mempool-perf/meson.build b/app/test-mempool-perf/meson.build new file mode 100644 index 0000000000..2d61ac62e9 --- /dev/null +++ b/app/test-mempool-perf/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Intel Corporation + +deps += ['mempool', 'argparse'] + +sources = files( + 'main.c', +) diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst index 13f75a5bc6..cb71ba7b55 100644 --- a/doc/guides/tools/index.rst +++ b/doc/guides/tools/index.rst @@ -17,6 +17,7 @@ DPDK Tools User Guides telemetrywatcher dmaperf flow-perf + mempoolperf securityperf testbbdev cryptoperf diff --git a/doc/guides/tools/mempoolperf.rst b/doc/guides/tools/mempoolperf.rst new file mode 100644 index 0000000000..e0ad859a29 --- /dev/null +++ b/doc/guides/tools/mempoolperf.rst @@ -0,0 +1,139 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2024 Intel Corporation + +dpdk-test-mempool-perf Application +==================================== + +The ``dpdk-test-mempool-perf`` tool measures the alloc/free throughput of DPDK mempool implementations. +Worker threads repeatedly allocate and free objects in configurable burst sizes following a randomised pattern, +exercising the pool under varying levels of occupancy. +Any mempool driver registered with the DPDK mempool ops table can be tested. + + +Running the Application +----------------------- + +.. code-block:: console + + dpdk-test-mempool-perf [EAL options] -- [application options] + +See the *DPDK Getting Started Guide* for a description of EAL options. + +The application operates in two modes depending on whether application-specific options are supplied after ``--``: + +interactive + Invoked with no options or only EAL options (nothing after ``--``, or ``--`` omitted). + The tool prompts for each parameter in turn; pressing Enter accepts the displayed default. + After configuration, a command line is printed that reproduces the same settings non-interactively. + +non-interactive + All configuration is supplied on the command line. + ``--mempool-type`` is required; all other parameters are optional. + + +Application Options +~~~~~~~~~~~~~~~~~~~ + +``--mempool-type <name>`` / ``-M <name>`` + Name of the mempool driver to test. + Required in non-interactive mode. + To list the drivers available on the current system, + run the application in interactive mode; the available names are printed at startup. + Common names include ``ring_mp_mc`` and ``stack``. + +``--nb-bufs <n>`` / ``-n <n>`` + Total number of objects in the pool. + Default: 1024 multiplied by the total lcore count. + The pool must be large enough that it is not exhausted when all workers hold their maximum simultaneous in-flight objects, + which is ``(rand-factor / 2) * burst-size`` objects per worker. + +``--cache-size <n>`` / ``-c <n>`` + Per-lcore object cache size, in objects. + Default: 512. + A larger cache reduces contention on the central pool at the cost of higher per-core memory usage. + Set to 0 to disable the per-lcore cache and measure underlying data structure throughput. + +``--nb-threads <n>`` / ``-t <n>`` + Number of worker lcores to launch. + Default: all available worker lcores (total lcores minus the main lcore). + +``--rand-factor <n>`` / ``-r <n>`` + Controls the width of the randomised allocation pattern. + Default: 8. + The value is rounded down to the nearest even number (minimum 2). + Half of the resulting slots perform bulk allocations and half perform bulk frees; + the order is reshuffled randomly at regular intervals. + A larger value means workers hold more in-flight objects on average + and vary their occupancy over a wider range, + exercising the pool under a more realistic mix of pressure levels. + +``--burst-size <n>`` / ``-b <n>`` + Number of objects per alloc or free call. + Default: 32. + Higher burst sizes amortise per-call overhead + and can reveal differences between pool implementations that batch internal operations. + +``--access-on-alloc`` / ``-A`` + Touch every cache line of each allocated object immediately after allocation (default behaviour). + This models workloads that initialise or write packet data after allocation, + ensuring that the measured throughput reflects both pool overhead and memory bandwidth pressure. + +``--no-access-on-alloc`` / ``-N`` + Skip the memory-access step after allocation. + Use this to isolate pure pool ring or lock overhead from memory bandwidth effects. + +``--summary`` / ``-s`` + Print only the aggregate total in the results, suppressing the per-worker-lcore breakdown. + Useful when scripting comparisons across pool types or configurations. + + +Interactive Mode +---------------- + +Running the tool with only EAL options enters interactive mode:: + + dpdk-test-mempool-perf [EAL options] + +The application lists all available mempool drivers then prompts for each parameter. +Pressing Enter at any prompt keeps the displayed default value. +``--mempool-type`` is the only mandatory entry. + +After configuration the tool prints an equivalent non-interactive command:: + + Reproduce using parameters: -M ring_mp_mc -n 4096 -c 512 -t 3 -r 8 -b 32 -A + +Append this output after the EAL options on subsequent runs to reproduce the exact same configuration without prompting. + + +Examples +-------- + +Run interactively, letting the tool prompt for all settings: + +.. code-block:: console + + dpdk-test-mempool-perf -l 0-3 + +Run non-interactively with four worker threads: + +.. code-block:: console + + dpdk-test-mempool-perf -l 0-4 -- -M ring_mp_mc -t 4 -n 20480 + +Disable the per-lcore cache to measure raw ring throughput: + +.. code-block:: console + + dpdk-test-mempool-perf -l 0-1 -- -M ring_mp_mc -c 0 + +Measure without memory access to isolate pool overhead from bandwidth: + +.. code-block:: console + + dpdk-test-mempool-perf -l 0-4 -- -M ring_mp_mc -N + +Print only the aggregate total, suitable for scripted comparisons: + +.. code-block:: console + + dpdk-test-mempool-perf -l 0-4 -- -M ring_mp_mc -s -- 2.53.0

