From patchwork Tue Mar 26 19:04:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 138827 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id A91C443D5B; Tue, 26 Mar 2024 20:04:39 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id DFECB40EE5; Tue, 26 Mar 2024 20:04:33 +0100 (CET) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id E848040EA5 for ; Tue, 26 Mar 2024 20:04:32 +0100 (CET) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 11313339; Tue, 26 Mar 2024 12:05:06 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.16.115]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 2F2113F64C; Tue, 26 Mar 2024 12:04:30 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Luca Vizzarro , Jack Bond-Preston , Honnappa Nagarahalli Subject: [PATCH 1/6] dts: add parameters data structure Date: Tue, 26 Mar 2024 19:04:17 +0000 Message-Id: <20240326190422.577028-2-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240326190422.577028-1-luca.vizzarro@arm.com> References: <20240326190422.577028-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org This commit introduces a new "params" module, which adds a new way to manage command line parameters. The provided Params dataclass is able to read the fields of its child class and produce a string representation to supply to the command line. Any data structure that is intended to represent command line parameters can inherit it. The representation can then manipulated by using the dataclass field metadata in conjunction with the provided functions: * value_only, used to supply a value without forming an option/flag * options_end, used to prefix with an options end delimiter (`--`) * short, used to define a short parameter name, e.g. `-p` * long, used to define a long parameter name, e.g. `--parameter` * multiple, used to turn a list into repeating parameters * field_mixins, used to manipulate the string representation of the value Signed-off-by: Luca Vizzarro Reviewed-by: Jack Bond-Preston Reviewed-by: Honnappa Nagarahalli --- dts/framework/params.py | 232 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 dts/framework/params.py diff --git a/dts/framework/params.py b/dts/framework/params.py new file mode 100644 index 0000000000..6b48c8353e --- /dev/null +++ b/dts/framework/params.py @@ -0,0 +1,232 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Arm Limited + +"""Parameter manipulation module. + +This module provides :class:`~Params` which can be used to model any data structure +that is meant to represent any command parameters. +""" + +from dataclasses import dataclass, field, fields +from typing import Any, Callable, Literal, Reversible, TypeVar, Iterable +from enum import Flag + + +T = TypeVar("T") +#: Type for a Mixin. +Mixin = Callable[[Any], str] +#: Type for an option parameter. +Option = Literal[True, None] +#: Type for a yes/no option parameter. +BooleanOption = Literal[True, False, None] + +META_VALUE_ONLY = "value_only" +META_OPTIONS_END = "options_end" +META_SHORT_NAME = "short_name" +META_LONG_NAME = "long_name" +META_MULTIPLE = "multiple" +META_MIXINS = "mixins" + + +def value_only(metadata: dict[str, Any] = {}) -> dict[str, Any]: + """Injects the value of the attribute as-is without flag. Metadata modifier for :func:`dataclasses.field`.""" + return {**metadata, META_VALUE_ONLY: True} + + +def short(name: str, metadata: dict[str, Any] = {}) -> dict[str, Any]: + """Overrides any parameter name with the given short option. Metadata modifier for :func:`dataclasses.field`. + + .. code:: python + + logical_cores: str | None = field(default="1-4", metadata=short("l")) + + will render as ``-l=1-4`` instead of ``--logical-cores=1-4``. + """ + return {**metadata, META_SHORT_NAME: name} + + +def long(name: str, metadata: dict[str, Any] = {}) -> dict[str, Any]: + """Overrides the inferred parameter name to the specified one. Metadata modifier for :func:`dataclasses.field`. + + .. code:: python + + x_name: str | None = field(default="y", metadata=long("x")) + + will render as ``--x=y``, but the field is accessed and modified through ``x_name``. + """ + return {**metadata, META_LONG_NAME: name} + + +def options_end(metadata: dict[str, Any] = {}) -> dict[str, Any]: + """Precedes the value with an options end delimiter (``--``). Metadata modifier for :func:`dataclasses.field`.""" + return {**metadata, META_OPTIONS_END: True} + + +def multiple(metadata: dict[str, Any] = {}) -> dict[str, Any]: + """Specifies that this parameter is set multiple times. Must be a list. Metadata modifier for :func:`dataclasses.field`. + + .. code:: python + + ports: list[int] | None = field(default_factory=lambda: [0, 1, 2], metadata=multiple(param_name("port"))) + + will render as ``--port=0 --port=1 --port=2``. Note that modifiers can be chained like in this example. + """ + return {**metadata, META_MULTIPLE: True} + + +def field_mixins(*mixins: Mixin, metadata: dict[str, Any] = {}) -> dict[str, Any]: + """Takes in a variable number of mixins to manipulate the value's rendering. Metadata modifier for :func:`dataclasses.field`. + + The ``metadata`` keyword argument can be used to chain metadata modifiers together. + + Mixins can be chained together, executed from right to left in the arguments list order. + + Example: + + .. code:: python + + hex_bitmask: int | None = field(default=0b1101, metadata=field_mixins(hex, metadata=param_name("mask"))) + + will render as ``--mask=0xd``. The :func:`hex` built-in can be used as a mixin turning a valid integer into a hexadecimal representation. + """ + return {**metadata, META_MIXINS: mixins} + + +def _reduce_mixins(mixins: Reversible[Mixin], value: Any) -> str: + for mixin in reversed(mixins): + value = mixin(value) + return value + + +def str_mixins(*mixins: Mixin): + """Decorator which modifies the ``__str__`` method, enabling support for mixins. + + Mixins can be chained together, executed from right to left in the arguments list order. + + Example: + + .. code:: python + + @str_mixins(hex_from_flag_value) + class BitMask(enum.Flag): + A = auto() + B = auto() + + will allow ``BitMask`` to render as a hexadecimal value. + """ + + def _class_decorator(original_class): + original_class.__str__ = lambda self: _reduce_mixins(mixins, self) + return original_class + + return _class_decorator + + +def comma_separated(values: Iterable[T]) -> str: + """Mixin which renders an iterable in a comma-separated string.""" + return ",".join([str(value).strip() for value in values if value is not None]) + + +def bracketed(value: str) -> str: + """Mixin which adds round brackets to the input.""" + return f"({value})" + + +def str_from_flag_value(flag: Flag) -> str: + """Mixin which returns the value from a :class:`enum.Flag` as a string.""" + return str(flag.value) + + +def hex_from_flag_value(flag: Flag) -> str: + """Mixin which turns a :class:`enum.Flag` value into hexadecimal.""" + return hex(flag.value) + + +def _make_option(param_name: str, short: bool = False, no: bool = False) -> str: + param_name = param_name.replace("_", "-") + return f"{'-' if short else '--'}{'no-' if no else ''}{param_name}" + + +@dataclass +class Params: + """Helper abstract dataclass that renders its fields into command line arguments. + + The parameter name is taken from the field name by default. The following: + + .. code:: python + + name: str | None = "value" + + is rendered as ``--name=value``. + Through :func:`dataclasses.field` the resulting parameter can be manipulated by applying + appropriate metadata. This class can be used with the following metadata modifiers: + + * :func:`value_only` + * :func:`options_end` + * :func:`short` + * :func:`long` + * :func:`multiple` + * :func:`field_mixins` + + To use fields as option switches set the value to ``True`` to enable them. If you + use a yes/no option switch you can also set ``False`` which would enable an option + prefixed with ``--no-``. Examples: + + .. code:: python + + interactive: Option = True # renders --interactive + numa: BooleanOption = False # renders --no-numa + + Setting ``None`` will disable any option. The :attr:`~Option` type alias is provided for + regular option switches, whereas :attr:`~BooleanOption` is offered for yes/no ones. + + An instance of a dataclass inheriting ``Params`` can also be assigned to an attribute, this helps with grouping parameters + together. The attribute holding the dataclass will be ignored and the latter will just be rendered as expected. + """ + + def __str__(self) -> str: + arguments: list[str] = [] + + for field in fields(self): + value = getattr(self, field.name) + + if value is None: + continue + + options_end = field.metadata.get(META_OPTIONS_END, False) + if options_end: + arguments.append("--") + + value_only = field.metadata.get(META_VALUE_ONLY, False) + if isinstance(value, Params) or value_only or options_end: + arguments.append(str(value)) + continue + + # take "short_name" metadata, or "long_name" metadata, or infer from field name + option_name = field.metadata.get( + META_SHORT_NAME, field.metadata.get(META_LONG_NAME, field.name) + ) + is_short = META_SHORT_NAME in field.metadata + + if isinstance(value, bool): + arguments.append(_make_option(option_name, short=is_short, no=(not value))) + continue + + option = _make_option(option_name, short=is_short) + separator = " " if is_short else "=" + str_mixins = field.metadata.get(META_MIXINS, []) + multiple = field.metadata.get(META_MULTIPLE, False) + + values = value if multiple else [value] + for entry_value in values: + entry_value = _reduce_mixins(str_mixins, entry_value) + arguments.append(f"{option}{separator}{entry_value}") + + return " ".join(arguments) + + +@dataclass +class StrParams(Params): + """A drop-in replacement for parameters passed as a string.""" + + value: str = field(metadata=value_only()) From patchwork Tue Mar 26 19:04:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 138828 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 034C043D5B; Tue, 26 Mar 2024 20:04:48 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 0EAB0410E6; Tue, 26 Mar 2024 20:04:36 +0100 (CET) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 46028410D3 for ; Tue, 26 Mar 2024 20:04:34 +0100 (CET) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 6E5952F4; Tue, 26 Mar 2024 12:05:07 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.16.115]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id AB0643F64C; Tue, 26 Mar 2024 12:04:32 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Luca Vizzarro , Jack Bond-Preston , Honnappa Nagarahalli Subject: [PATCH 2/6] dts: use Params for interactive shells Date: Tue, 26 Mar 2024 19:04:18 +0000 Message-Id: <20240326190422.577028-3-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240326190422.577028-1-luca.vizzarro@arm.com> References: <20240326190422.577028-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Make it so that interactive shells accept an implementation of `Params` for app arguments. Convert EalParameters to use `Params` instead. String command line parameters can still be supplied by using the `StrParams` implementation. Signed-off-by: Luca Vizzarro Reviewed-by: Jack Bond-Preston Reviewed-by: Honnappa Nagarahalli --- .../remote_session/interactive_shell.py | 8 +- dts/framework/remote_session/testpmd_shell.py | 12 +- dts/framework/testbed_model/__init__.py | 2 +- dts/framework/testbed_model/node.py | 4 +- dts/framework/testbed_model/os_session.py | 4 +- dts/framework/testbed_model/sut_node.py | 106 ++++++++---------- dts/tests/TestSuite_pmd_buffer_scatter.py | 3 +- 7 files changed, 73 insertions(+), 66 deletions(-) diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py index 5cfe202e15..a2c7b30d9f 100644 --- a/dts/framework/remote_session/interactive_shell.py +++ b/dts/framework/remote_session/interactive_shell.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2023 University of New Hampshire +# Copyright(c) 2024 Arm Limited """Common functionality for interactive shell handling. @@ -21,6 +22,7 @@ from paramiko import Channel, SSHClient, channel # type: ignore[import] from framework.logger import DTSLogger +from framework.params import Params from framework.settings import SETTINGS @@ -40,7 +42,7 @@ class InteractiveShell(ABC): _ssh_channel: Channel _logger: DTSLogger _timeout: float - _app_args: str + _app_args: Params | None #: Prompt to expect at the end of output when sending a command. #: This is often overridden by subclasses. @@ -63,7 +65,7 @@ def __init__( interactive_session: SSHClient, logger: DTSLogger, get_privileged_command: Callable[[str], str] | None, - app_args: str = "", + app_args: Params | None = None, timeout: float = SETTINGS.timeout, ) -> None: """Create an SSH channel during initialization. @@ -100,7 +102,7 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None get_privileged_command: A function (but could be any callable) that produces the version of the command with elevated privileges. """ - start_command = f"{self.path} {self._app_args}" + start_command = f"{self.path} {self._app_args or ''}" if get_privileged_command is not None: start_command = get_privileged_command(start_command) self.send_command(start_command) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index cb2ab6bd00..db3abb7600 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -21,6 +21,7 @@ from typing import Callable, ClassVar from framework.exception import InteractiveCommandExecutionError +from framework.params import StrParams from framework.settings import SETTINGS from framework.utils import StrEnum @@ -118,8 +119,15 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None Also find the number of pci addresses which were allowed on the command line when the app was started. """ - self._app_args += " -i --mask-event intr_lsc" - self.number_of_ports = self._app_args.count("-a ") + from framework.testbed_model.sut_node import EalParameters + + assert isinstance(self._app_args, EalParameters) + + if isinstance(self._app_args.app_params, StrParams): + self._app_args.app_params.value += " -i --mask-event intr_lsc" + + self.number_of_ports = len(self._app_args.ports) if self._app_args.ports is not None else 0 + super()._start_application(get_privileged_command) def start(self, verify: bool = True) -> None: diff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbed_model/__init__.py index 6086512ca2..ef9520df4c 100644 --- a/dts/framework/testbed_model/__init__.py +++ b/dts/framework/testbed_model/__init__.py @@ -23,6 +23,6 @@ from .cpu import LogicalCoreCount, LogicalCoreCountFilter, LogicalCoreList from .node import Node from .port import Port, PortLink -from .sut_node import SutNode +from .sut_node import SutNode, EalParameters from .tg_node import TGNode from .virtual_device import VirtualDevice diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py index 74061f6262..ec9512d618 100644 --- a/dts/framework/testbed_model/node.py +++ b/dts/framework/testbed_model/node.py @@ -2,6 +2,7 @@ # Copyright(c) 2010-2014 Intel Corporation # Copyright(c) 2022-2023 PANTHEON.tech s.r.o. # Copyright(c) 2022-2023 University of New Hampshire +# Copyright(c) 2024 Arm Limited """Common functionality for node management. @@ -24,6 +25,7 @@ ) from framework.exception import ConfigurationError from framework.logger import DTSLogger, get_dts_logger +from framework.params import Params from framework.settings import SETTINGS from .cpu import ( @@ -199,7 +201,7 @@ def create_interactive_shell( shell_cls: Type[InteractiveShellType], timeout: float = SETTINGS.timeout, privileged: bool = False, - app_args: str = "", + app_args: Params | None = None, ) -> InteractiveShellType: """Factory for interactive session handlers. diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py index d5bf7e0401..7234c975c8 100644 --- a/dts/framework/testbed_model/os_session.py +++ b/dts/framework/testbed_model/os_session.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2023 PANTHEON.tech s.r.o. # Copyright(c) 2023 University of New Hampshire +# Copyright(c) 2024 Arm Limited """OS-aware remote session. @@ -29,6 +30,7 @@ from framework.config import Architecture, NodeConfiguration, NodeInfo from framework.logger import DTSLogger +from framework.params import Params from framework.remote_session import ( CommandResult, InteractiveRemoteSession, @@ -134,7 +136,7 @@ def create_interactive_shell( shell_cls: Type[InteractiveShellType], timeout: float, privileged: bool, - app_args: str, + app_args: Params | None, ) -> InteractiveShellType: """Factory for interactive session handlers. diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbed_model/sut_node.py index 97aa26d419..3f8c3807b3 100644 --- a/dts/framework/testbed_model/sut_node.py +++ b/dts/framework/testbed_model/sut_node.py @@ -2,6 +2,7 @@ # Copyright(c) 2010-2014 Intel Corporation # Copyright(c) 2023 PANTHEON.tech s.r.o. # Copyright(c) 2023 University of New Hampshire +# Copyright(c) 2024 Arm Limited """System under test (DPDK + hardware) node. @@ -11,6 +12,7 @@ """ +from dataclasses import dataclass, field import os import tarfile import time @@ -23,6 +25,8 @@ NodeInfo, SutNodeConfiguration, ) +from framework import params +from framework.params import Params, StrParams from framework.remote_session import CommandResult from framework.settings import SETTINGS from framework.utils import MesonArgs @@ -34,62 +38,51 @@ from .virtual_device import VirtualDevice -class EalParameters(object): +def _port_to_pci(port: Port) -> str: + return port.pci + + +@dataclass(kw_only=True) +class EalParameters(Params): """The environment abstraction layer parameters. The string representation can be created by converting the instance to a string. """ - def __init__( - self, - lcore_list: LogicalCoreList, - memory_channels: int, - prefix: str, - no_pci: bool, - vdevs: list[VirtualDevice], - ports: list[Port], - other_eal_param: str, - ): - """Initialize the parameters according to inputs. - - Process the parameters into the format used on the command line. + lcore_list: LogicalCoreList = field(metadata=params.short("l")) + """The list of logical cores to use.""" - Args: - lcore_list: The list of logical cores to use. - memory_channels: The number of memory channels to use. - prefix: Set the file prefix string with which to start DPDK, e.g.: ``prefix='vf'``. - no_pci: Switch to disable PCI bus e.g.: ``no_pci=True``. - vdevs: Virtual devices, e.g.:: + memory_channels: int = field(metadata=params.short("n")) + """The number of memory channels to use.""" - vdevs=[ - VirtualDevice('net_ring0'), - VirtualDevice('net_ring1') - ] - ports: The list of ports to allow. - other_eal_param: user defined DPDK EAL parameters, e.g.: - ``other_eal_param='--single-file-segments'`` - """ - self._lcore_list = f"-l {lcore_list}" - self._memory_channels = f"-n {memory_channels}" - self._prefix = prefix - if prefix: - self._prefix = f"--file-prefix={prefix}" - self._no_pci = "--no-pci" if no_pci else "" - self._vdevs = " ".join(f"--vdev {vdev}" for vdev in vdevs) - self._ports = " ".join(f"-a {port.pci}" for port in ports) - self._other_eal_param = other_eal_param - - def __str__(self) -> str: - """Create the EAL string.""" - return ( - f"{self._lcore_list} " - f"{self._memory_channels} " - f"{self._prefix} " - f"{self._no_pci} " - f"{self._vdevs} " - f"{self._ports} " - f"{self._other_eal_param}" - ) + prefix: str = field(metadata=params.long("file-prefix")) + """Set the file prefix string with which to start DPDK, e.g.: ``prefix="vf"``.""" + + no_pci: params.Option + """Switch to disable PCI bus e.g.: ``no_pci=True``.""" + + vdevs: list[VirtualDevice] | None = field( + default=None, metadata=params.multiple(params.long("vdev")) + ) + """Virtual devices, e.g.:: + + vdevs=[ + VirtualDevice("net_ring0"), + VirtualDevice("net_ring1") + ] + """ + + ports: list[Port] | None = field( + default=None, + metadata=params.field_mixins(_port_to_pci, metadata=params.multiple(params.short("a"))), + ) + """The list of ports to allow.""" + + other_eal_param: StrParams | None = None + """Any other EAL parameter(s).""" + + app_params: Params | None = field(default=None, metadata=params.options_end()) + """Parameters to pass to the underlying DPDK app.""" class SutNode(Node): @@ -350,7 +343,7 @@ def create_eal_parameters( ascending_cores: bool = True, prefix: str = "dpdk", append_prefix_timestamp: bool = True, - no_pci: bool = False, + no_pci: params.Option = None, vdevs: list[VirtualDevice] | None = None, ports: list[Port] | None = None, other_eal_param: str = "", @@ -393,9 +386,6 @@ def create_eal_parameters( if prefix: self._dpdk_prefix_list.append(prefix) - if vdevs is None: - vdevs = [] - if ports is None: ports = self.ports @@ -406,7 +396,7 @@ def create_eal_parameters( no_pci=no_pci, vdevs=vdevs, ports=ports, - other_eal_param=other_eal_param, + other_eal_param=StrParams(other_eal_param), ) def run_dpdk_app( @@ -442,7 +432,7 @@ def create_interactive_shell( shell_cls: Type[InteractiveShellType], timeout: float = SETTINGS.timeout, privileged: bool = False, - app_parameters: str = "", + app_parameters: Params | None = None, eal_parameters: EalParameters | None = None, ) -> InteractiveShellType: """Extend the factory for interactive session handlers. @@ -459,6 +449,7 @@ def create_interactive_shell( reading from the buffer and don't receive any data within the timeout it will throw an error. privileged: Whether to run the shell with administrative privileges. + app_args: The arguments to be passed to the application. eal_parameters: List of EAL parameters to use to launch the app. If this isn't provided or an empty string is passed, it will default to calling :meth:`create_eal_parameters`. @@ -470,9 +461,10 @@ def create_interactive_shell( """ # We need to append the build directory and add EAL parameters for DPDK apps if shell_cls.dpdk_app: - if not eal_parameters: + if eal_parameters is None: eal_parameters = self.create_eal_parameters() - app_parameters = f"{eal_parameters} -- {app_parameters}" + eal_parameters.app_params = app_parameters + app_parameters = eal_parameters shell_cls.path = self.main_session.join_remote_path( self.remote_dpdk_build_dir, shell_cls.path diff --git a/dts/tests/TestSuite_pmd_buffer_scatter.py b/dts/tests/TestSuite_pmd_buffer_scatter.py index 3701c47408..4cdbdc4272 100644 --- a/dts/tests/TestSuite_pmd_buffer_scatter.py +++ b/dts/tests/TestSuite_pmd_buffer_scatter.py @@ -22,6 +22,7 @@ from scapy.packet import Raw # type: ignore[import] from scapy.utils import hexstr # type: ignore[import] +from framework.params import StrParams from framework.remote_session.testpmd_shell import TestPmdForwardingModes, TestPmdShell from framework.test_suite import TestSuite @@ -103,7 +104,7 @@ def pmd_scatter(self, mbsize: int) -> None: """ testpmd = self.sut_node.create_interactive_shell( TestPmdShell, - app_parameters=( + app_parameters=StrParams( "--mbcache=200 " f"--mbuf-size={mbsize} " "--max-pkt-len=9000 " From patchwork Tue Mar 26 19:04:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 138829 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 1732643D5B; Tue, 26 Mar 2024 20:04:59 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id B7C3741156; Tue, 26 Mar 2024 20:04:37 +0100 (CET) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 73CBF410E6 for ; Tue, 26 Mar 2024 20:04:35 +0100 (CET) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id A802F339; Tue, 26 Mar 2024 12:05:08 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.16.115]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 1FEA13F64C; Tue, 26 Mar 2024 12:04:33 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Luca Vizzarro , Jack Bond-Preston , Honnappa Nagarahalli Subject: [PATCH 3/6] dts: add testpmd shell params Date: Tue, 26 Mar 2024 19:04:19 +0000 Message-Id: <20240326190422.577028-4-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240326190422.577028-1-luca.vizzarro@arm.com> References: <20240326190422.577028-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Implement all the testpmd shell parameters into a data structure. Signed-off-by: Luca Vizzarro Reviewed-by: Jack Bond-Preston Reviewed-by: Honnappa Nagarahalli --- dts/framework/remote_session/testpmd_shell.py | 633 +++++++++++++++++- 1 file changed, 615 insertions(+), 18 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index db3abb7600..a823dc53be 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2023 University of New Hampshire # Copyright(c) 2023 PANTHEON.tech s.r.o. +# Copyright(c) 2024 Arm Limited """Testpmd interactive shell. @@ -15,10 +16,25 @@ testpmd_shell.close() """ +from dataclasses import dataclass, field +from enum import auto, Enum, Flag, unique import time -from enum import auto from pathlib import PurePath -from typing import Callable, ClassVar +from typing import Callable, ClassVar, Literal, NamedTuple +from framework.params import ( + BooleanOption, + Params, + bracketed, + comma_separated, + Option, + field_mixins, + hex_from_flag_value, + multiple, + long, + short, + str_from_flag_value, + str_mixins, +) from framework.exception import InteractiveCommandExecutionError from framework.params import StrParams @@ -28,26 +44,79 @@ from .interactive_shell import InteractiveShell -class TestPmdDevice(object): - """The data of a device that testpmd can recognize. +@str_mixins(bracketed, comma_separated) +class TestPmdPortNUMAConfig(NamedTuple): + """DPDK port to NUMA socket association tuple.""" - Attributes: - pci_address: The PCI address of the device. + port: int + socket: int + + +@str_mixins(str_from_flag_value) +@unique +class TestPmdFlowDirection(Flag): + """Flag indicating the direction of the flow. + + A bi-directional flow can be specified with the pipe: + + >>> TestPmdFlowDirection.RX | TestPmdFlowDirection.TX + """ - pci_address: str + #: + RX = 1 << 0 + #: + TX = 1 << 1 - def __init__(self, pci_address_line: str): - """Initialize the device from the testpmd output line string. - Args: - pci_address_line: A line of testpmd output that contains a device. - """ - self.pci_address = pci_address_line.strip().split(": ")[1].strip() +@str_mixins(bracketed, comma_separated) +class TestPmdRingNUMAConfig(NamedTuple): + """Tuple associating DPDK port, direction of the flow and NUMA socket.""" + + port: int + direction: TestPmdFlowDirection + socket: int + + +@str_mixins(comma_separated) +class TestPmdEthPeer(NamedTuple): + """Tuple associating a MAC address to the specified DPDK port.""" + + port_no: int + mac_address: str + + +@str_mixins(comma_separated) +class TestPmdTxIPAddrPair(NamedTuple): + """Tuple specifying the source and destination IPs for the packets.""" + + source_ip: str + dest_ip: str - def __str__(self) -> str: - """The PCI address captures what the device is.""" - return self.pci_address + +@str_mixins(comma_separated) +class TestPmdTxUDPPortPair(NamedTuple): + """Tuple specifying the UDP source and destination ports for the packets. + + If leaving ``dest_port`` unspecified, ``source_port`` will be used for the destination port as well. + """ + + source_port: int + dest_port: int | None = None + + +class TestPmdPortTopology(StrEnum): + paired = auto() + """In paired mode, the forwarding is between pairs of ports, + for example: (0,1), (2,3), (4,5).""" + chained = auto() + """In chained mode, the forwarding is to the next available port in the port mask, + for example: (0,1), (1,2), (2,0). + + The ordering of the ports can be changed using the portlist testpmd runtime function. + """ + loop = auto() + """In loop mode, ingress traffic is simply transmitted back on the same interface.""" class TestPmdForwardingModes(StrEnum): @@ -81,6 +150,534 @@ class TestPmdForwardingModes(StrEnum): recycle_mbufs = auto() +@str_mixins(comma_separated) +class XYPair(NamedTuple): + #: + X: int + #: + Y: int | None = None + + +@str_mixins(hex_from_flag_value) +@unique +class TestPmdRXMultiQueueMode(Flag): + #: + RSS = 1 << 0 + #: + DCB = 1 << 1 + #: + VMDQ = 1 << 2 + + +@str_mixins(hex_from_flag_value) +@unique +class TestPmdHairpinMode(Flag): + TWO_PORTS_LOOP = 1 << 0 + """Two hairpin ports loop.""" + TWO_PORTS_PAIRED = 1 << 1 + """Two hairpin ports paired.""" + EXPLICIT_TX_FLOW = 1 << 4 + """Explicit Tx flow rule.""" + FORCE_RX_QUEUE_MEM_SETTINGS = 1 << 8 + """Force memory settings of hairpin RX queue.""" + FORCE_TX_QUEUE_MEM_SETTINGS = 1 << 9 + """Force memory settings of hairpin TX queue.""" + RX_QUEUE_USE_LOCKED_DEVICE_MEMORY = 1 << 12 + """Hairpin RX queues will use locked device memory.""" + RX_QUEUE_USE_RTE_MEMORY = 1 << 13 + """Hairpin RX queues will use RTE memory.""" + TX_QUEUE_USE_LOCKED_DEVICE_MEMORY = 1 << 16 + """Hairpin TX queues will use locked device memory.""" + TX_QUEUE_USE_RTE_MEMORY = 1 << 18 + """Hairpin TX queues will use RTE memory.""" + + +class TestPmdEvent(StrEnum): + #: + unknown = auto() + #: + queue_state = auto() + #: + vf_mbox = auto() + #: + macsec = auto() + #: + intr_lsc = auto() + #: + intr_rmv = auto() + #: + intr_reset = auto() + #: + dev_probed = auto() + #: + dev_released = auto() + #: + flow_aged = auto() + #: + err_recovering = auto() + #: + recovery_success = auto() + #: + recovery_failed = auto() + #: + all = auto() + + +class TestPmdMempoolAllocationMode(StrEnum): + native = auto() + """Create and populate mempool using native DPDK memory.""" + anon = auto() + """Create mempool using native DPDK memory, but populate using anonymous memory.""" + xmem = auto() + """Create and populate mempool using externally and anonymously allocated area.""" + xmemhuge = auto() + """Create and populate mempool using externally and anonymously allocated hugepage area.""" + + +@dataclass(kw_only=True) +class TestPmdTXOnlyForwardingMode(Params): + __forward_mode: Literal[TestPmdForwardingModes.txonly] = field( + default=TestPmdForwardingModes.txonly, init=False, metadata=long("forward-mode") + ) + multi_flow: Option = field(default=None, metadata=long("txonly-multi-flow")) + """Generate multiple flows.""" + segments_length: XYPair | None = field(default=None, metadata=long("txpkts")) + """Set TX segment sizes or total packet length.""" + + +@dataclass(kw_only=True) +class TestPmdFlowGenForwardingMode(Params): + __forward_mode: Literal[TestPmdForwardingModes.flowgen] = field( + default=TestPmdForwardingModes.flowgen, init=False, metadata=long("forward-mode") + ) + clones: int | None = field(default=None, metadata=long("flowgen-clones")) + """Set the number of each packet clones to be sent. Sending clones reduces host CPU load on + creating packets and may help in testing extreme speeds or maxing out Tx packet performance. + N should be not zero, but less than ‘burst’ parameter. + """ + flows: int | None = field(default=None, metadata=long("flowgen-flows")) + """Set the number of flows to be generated, where 1 <= N <= INT32_MAX.""" + segments_length: XYPair | None = field(default=None, metadata=long("txpkts")) + """Set TX segment sizes or total packet length.""" + + +@dataclass(kw_only=True) +class TestPmdNoisyForwardingMode(Params): + __forward_mode: Literal[TestPmdForwardingModes.noisy] = field( + default=TestPmdForwardingModes.noisy, init=False, metadata=long("forward-mode") + ) + forward_mode: ( + Literal[ + TestPmdForwardingModes.io, + TestPmdForwardingModes.mac, + TestPmdForwardingModes.macswap, + TestPmdForwardingModes.fivetswap, + ] + | None + ) = field(default=TestPmdForwardingModes.io, metadata=long("noisy-forward-mode")) + """Set the noisy vnf forwarding mode.""" + tx_sw_buffer_size: int | None = field(default=None, metadata=long("noisy-tx-sw-buffer-size")) + """Set the maximum number of elements of the FIFO queue to be created for buffering packets. + The default value is 0. + """ + tx_sw_buffer_flushtime: int | None = field( + default=None, metadata=long("noisy-tx-sw-buffer-flushtime") + ) + """Set the time before packets in the FIFO queue are flushed. The default value is 0.""" + lkup_memory: int | None = field(default=None, metadata=long("noisy-lkup-memory")) + """Set the size of the noisy neighbor simulation memory buffer in MB to N. The default value is 0.""" + lkup_num_reads: int | None = field(default=None, metadata=long("noisy-lkup-num-reads")) + """Set the number of reads to be done in noisy neighbor simulation memory buffer to N. + The default value is 0. + """ + lkup_num_writes: int | None = field(default=None, metadata=long("noisy-lkup-num-writes")) + """Set the number of writes to be done in noisy neighbor simulation memory buffer to N. + The default value is 0. + """ + lkup_num_reads_writes: int | None = field( + default=None, metadata=long("noisy-lkup-num-reads-writes") + ) + """Set the number of r/w accesses to be done in noisy neighbor simulation memory buffer to N. + The default value is 0. + """ + + +@dataclass(kw_only=True) +class TestPmdAnonMempoolAllocationMode(Params): + __mp_alloc: Literal[TestPmdMempoolAllocationMode.anon] = field( + default=TestPmdMempoolAllocationMode.anon, init=False, metadata=long("mp-alloc") + ) + no_iova_contig: Option = None + """Enable to create mempool which is not IOVA contiguous.""" + + +@dataclass(kw_only=True) +class TestPmdRXRingParams(Params): + descriptors: int | None = field(default=None, metadata=long("rxd")) + """Set the number of descriptors in the RX rings to N, where N > 0. The default value is 128.""" + prefetch_threshold: int | None = field(default=None, metadata=long("rxpt")) + """Set the prefetch threshold register of RX rings to N, where N >= 0. The default value is 8.""" + host_threshold: int | None = field(default=None, metadata=long("rxht")) + """Set the host threshold register of RX rings to N, where N >= 0. The default value is 8.""" + write_back_threshold: int | None = field(default=None, metadata=long("rxwt")) + """Set the write-back threshold register of RX rings to N, where N >= 0. The default value is 4.""" + free_threshold: int | None = field(default=None, metadata=long("rxfreet")) + """Set the free threshold of RX descriptors to N, where 0 <= N < value of ``-–rxd``. + The default value is 0. + """ + + +@dataclass +class TestPmdDisableRSS(Params): + """Disable RSS (Receive Side Scaling).""" + + __disable_rss: Literal[True] = field(default=True, init=False, metadata=long("disable-rss")) + + +@dataclass +class TestPmdSetRSSIPOnly(Params): + """Set RSS functions for IPv4/IPv6 only.""" + + __rss_ip: Literal[True] = field(default=True, init=False, metadata=long("rss-ip")) + + +@dataclass +class TestPmdSetRSSUDP(Params): + """Set RSS functions for IPv4/IPv6 and UDP.""" + + __rss_udp: Literal[True] = field(default=True, init=False, metadata=long("rss-udp")) + + +@dataclass(kw_only=True) +class TestPmdTXRingParams(Params): + descriptors: int | None = field(default=None, metadata=long("txd")) + """Set the number of descriptors in the TX rings to N, where N > 0. The default value is 512.""" + rs_bit_threshold: int | None = field(default=None, metadata=long("txrst")) + """Set the transmit RS bit threshold of TX rings to N, where 0 <= N <= value of ``--txd``. + The default value is 0. + """ + prefetch_threshold: int | None = field(default=None, metadata=long("txpt")) + """Set the prefetch threshold register of TX rings to N, where N >= 0. The default value is 36.""" + host_threshold: int | None = field(default=None, metadata=long("txht")) + """Set the host threshold register of TX rings to N, where N >= 0. The default value is 0.""" + write_back_threshold: int | None = field(default=None, metadata=long("txwt")) + """Set the write-back threshold register of TX rings to N, where N >= 0. The default value is 0.""" + free_threshold: int | None = field(default=None, metadata=long("txfreet")) + """Set the transmit free threshold of TX rings to N, where 0 <= N <= value of ``--txd``. + The default value is 0. + """ + + +@dataclass(slots=True, kw_only=True) +class TestPmdParameters(Params): + """The testpmd shell parameters. + + The string representation can be created by converting the instance to a string. + """ + + interactive_mode: Option = field(default=True, metadata=short("i")) + """Runs testpmd in interactive mode.""" + auto_start: Option = field(default=None, metadata=short("a")) + """Start forwarding on initialization.""" + tx_first: Option = None + """Start forwarding, after sending a burst of packets first.""" + + stats_period: int | None = None + """Display statistics every ``PERIOD`` seconds, if interactive mode is disabled. + The default value is 0, which means that the statistics will not be displayed. + + .. note:: This flag should be used only in non-interactive mode. + """ + + display_xstats: list[str] | None = field(default=None, metadata=field_mixins(comma_separated)) + """Display comma-separated list of extended statistics every ``PERIOD`` seconds as specified in + ``--stats-period`` or when used with interactive commands that show Rx/Tx statistics + (i.e. ‘show port stats’). + """ + + nb_cores: int | None = 1 + """Set the number of forwarding cores, where 1 <= N <= “number of cores” or ``RTE_MAX_LCORE`` + from the configuration file. The default value is 1. + """ + coremask: int | None = field(default=None, metadata=field_mixins(hex)) + """Set the hexadecimal bitmask of the cores running the packet forwarding test. The main lcore + is reserved for command line parsing only and cannot be masked on for packet forwarding. + """ + + nb_ports: int | None = None + """Set the number of forwarding ports, where 1 <= N <= “number of ports” on the board or + ``RTE_MAX_ETHPORTS`` from the configuration file. The default value is the number of ports + on the board. + """ + port_topology: TestPmdPortTopology | None = TestPmdPortTopology.paired + """Set port topology, where mode is paired (the default), chained or loop.""" + portmask: int | None = field(default=None, metadata=field_mixins(hex)) + """Set the hexadecimal bitmask of the ports used by the packet forwarding test.""" + portlist: str | None = None # TODO: can be ranges 0,1-3 + """Set the forwarding ports based on the user input used by the packet forwarding test. + ‘-‘ denotes a range of ports to set including the two specified port IDs ‘,’ separates + multiple port values. Possible examples like –portlist=0,1 or –portlist=0-2 or –portlist=0,1-2 etc + """ + + numa: BooleanOption = True + """Enable/disable NUMA-aware allocation of RX/TX rings and of RX memory buffers (mbufs). Enabled by default.""" + socket_num: int | None = None + """Set the socket from which all memory is allocated in NUMA mode, where 0 <= N < number of sockets on the board.""" + port_numa_config: list[TestPmdPortNUMAConfig] | None = field( + default=None, metadata=field_mixins(comma_separated) + ) + """Specify the socket on which the memory pool to be used by the port will be allocated.""" + ring_numa_config: list[TestPmdRingNUMAConfig] | None = field( + default=None, metadata=field_mixins(comma_separated) + ) + """Specify the socket on which the TX/RX rings for the port will be allocated. + Where flag is 1 for RX, 2 for TX, and 3 for RX and TX. + """ + + # Mbufs + total_num_mbufs: int | None = None + """Set the number of mbufs to be allocated in the mbuf pools, where N > 1024.""" + mbuf_size: list[int] | None = field(default=None, metadata=field_mixins(comma_separated)) + """Set the data size of the mbufs used to N bytes, where N < 65536. The default value is 2048. + If multiple mbuf-size values are specified the extra memory pools will be created for + allocating mbufs to receive packets with buffer splitting features. + """ + mbcache: int | None = None + """Set the cache of mbuf memory pools to N, where 0 <= N <= 512. The default value is 16.""" + + max_pkt_len: int | None = None + """Set the maximum packet size to N bytes, where N >= 64. The default value is 1518.""" + + eth_peers_configfile: PurePath | None = None + """Use a configuration file containing the Ethernet addresses of the peer ports.""" + eth_peer: list[TestPmdEthPeer] | None = field(default=None, metadata=multiple()) + """Set the MAC address XX:XX:XX:XX:XX:XX of the peer port N, where 0 <= N < RTE_MAX_ETHPORTS.""" + + tx_ip: TestPmdTxIPAddrPair | None = TestPmdTxIPAddrPair( + source_ip="198.18.0.1", dest_ip="198.18.0.2" + ) + """Set the source and destination IP address used when doing transmit only test. + The defaults address values are source 198.18.0.1 and destination 198.18.0.2. + These are special purpose addresses reserved for benchmarking (RFC 5735). + """ + tx_udp: TestPmdTxUDPPortPair | None = TestPmdTxUDPPortPair(9) + """Set the source and destination UDP port number for transmit test only test. + The default port is the port 9 which is defined for the discard protocol (RFC 863).""" + + enable_lro: Option = None + """Enable large receive offload.""" + max_lro_pkt_size: int | None = None + """Set the maximum LRO aggregated packet size to N bytes, where N >= 64.""" + + disable_crc_strip: Option = None + """Disable hardware CRC stripping.""" + enable_scatter: Option = None + """Enable scatter (multi-segment) RX.""" + enable_hw_vlan: Option = None + """Enable hardware VLAN.""" + enable_hw_vlan_filter: Option = None + """Enable hardware VLAN filter.""" + enable_hw_vlan_strip: Option = None + """Enable hardware VLAN strip.""" + enable_hw_vlan_extend: Option = None + """Enable hardware VLAN extend.""" + enable_hw_qinq_strip: Option = None + """Enable hardware QINQ strip.""" + pkt_drop_enabled: Option = field(default=None, metadata=long("enable-drop-en")) + """Enable per-queue packet drop for packets with no descriptors.""" + + rss: TestPmdDisableRSS | TestPmdSetRSSIPOnly | TestPmdSetRSSUDP | None = None + """RSS option setting. + + The value can be one of: + * :class:`TestPmdDisableRSS`, to disable RSS + * :class:`TestPmdSetRSSIPOnly`, to set RSS for IPv4/IPv6 only + * :class:`TestPmdSetRSSUDP`, to set RSS for IPv4/IPv6 and UDP + """ + + forward_mode: ( + Literal[ + TestPmdForwardingModes.io, + TestPmdForwardingModes.mac, + TestPmdForwardingModes.macswap, + TestPmdForwardingModes.rxonly, + TestPmdForwardingModes.csum, + TestPmdForwardingModes.icmpecho, + TestPmdForwardingModes.ieee1588, + TestPmdForwardingModes.fivetswap, + TestPmdForwardingModes.shared_rxq, + TestPmdForwardingModes.recycle_mbufs, + ] + | TestPmdFlowGenForwardingMode + | TestPmdTXOnlyForwardingMode + | TestPmdNoisyForwardingMode + | None + ) = TestPmdForwardingModes.io + """Set the forwarding mode. + + The value can be one of: + * :attr:`TestPmdForwardingModes.io` (default) + * :attr:`TestPmdForwardingModes.mac` + * :attr:`TestPmdForwardingModes.rxonly` + * :attr:`TestPmdForwardingModes.csum` + * :attr:`TestPmdForwardingModes.icmpecho` + * :attr:`TestPmdForwardingModes.ieee1588` + * :attr:`TestPmdForwardingModes.fivetswap` + * :attr:`TestPmdForwardingModes.shared_rxq` + * :attr:`TestPmdForwardingModes.recycle_mbufs` + * :class:`FlowGenForwardingMode` + * :class:`TXOnlyForwardingMode` + * :class:`NoisyForwardingMode` + """ + + hairpin_mode: TestPmdHairpinMode | None = TestPmdHairpinMode(0) + """Set the hairpin port configuration.""" + hairpin_queues: int | None = field(default=None, metadata=long("hairpinq")) + """Set the number of hairpin queues per port to N, where 1 <= N <= 65535. The default value is 0.""" + + burst: int | None = None + """Set the number of packets per burst to N, where 1 <= N <= 512. The default value is 32. + If set to 0, driver default is used if defined. + Else, if driver default is not defined, default of 32 is used. + """ + + # RX data parameters + enable_rx_cksum: Option = None + """Enable hardware RX checksum offload.""" + rx_queues: int | None = field(default=None, metadata=long("rxq")) + """Set the number of RX queues per port to N, where 1 <= N <= 65535. The default value is 1.""" + rx_ring: TestPmdRXRingParams | None = None + """Set the RX rings parameters.""" + no_flush_rx: Option = None + """Don’t flush the RX streams before starting forwarding. Used mainly with the PCAP PMD.""" + rx_segments_offsets: XYPair | None = field(default=None, metadata=long("rxoffs")) + """Set the offsets of packet segments on receiving if split feature is engaged. + Affects only the queues configured with split offloads (currently BUFFER_SPLIT is supported only). + """ + rx_segments_length: XYPair | None = field(default=None, metadata=long("rxpkts")) + """Set the length of segments to scatter packets on receiving if split feature is engaged. + Affects only the queues configured with split offloads (currently BUFFER_SPLIT is supported only). + Optionally the multiple memory pools can be specified with –mbuf-size command line parameter and + the mbufs to receive will be allocated sequentially from these extra memory pools. + """ + multi_rx_mempool: Option = None + """Enable multiple mbuf pools per Rx queue.""" + rx_shared_queue: Option | int = field(default=None, metadata=long("rxq-share")) + """Create queues in shared Rx queue mode if device supports. Shared Rx queues are grouped per X ports. + X defaults to UINT32_MAX, implies all ports join share group 1. + Forwarding engine “shared-rxq” should be used for shared Rx queues. + This engine does Rx only and update stream statistics accordingly. + """ + rx_offloads: int | None = field(default=0, metadata=field_mixins(hex)) + """Set the hexadecimal bitmask of RX queue offloads. The default value is 0.""" + rx_mq_mode: TestPmdRXMultiQueueMode | None = ( + TestPmdRXMultiQueueMode.DCB | TestPmdRXMultiQueueMode.RSS | TestPmdRXMultiQueueMode.VMDQ + ) + """Set the hexadecimal bitmask of RX multi queue mode which can be enabled.""" + + # TX data parameters + tx_queues: int | None = field(default=None, metadata=long("txq")) + """Set the number of TX queues per port to N, where 1 <= N <= 65535. The default value is 1.""" + tx_ring: TestPmdTXRingParams | None = None + """Set the TX rings params.""" + tx_offloads: int | None = field(default=0, metadata=field_mixins(hex)) + """Set the hexadecimal bitmask of TX queue offloads. The default value is 0.""" + + eth_link_speed: int | None = None + """Set a forced link speed to the ethernet port. E.g. 1000 for 1Gbps.""" + disable_link_check: Option = None + """Disable check on link status when starting/stopping ports.""" + disable_device_start: Option = None + """Do not automatically start all ports. + This allows testing configuration of rx and tx queues before device is started for the first time. + """ + no_lsc_interrupt: Option = None + """Disable LSC interrupts for all ports, even those supporting it.""" + no_rmv_interrupt: Option = None + """Disable RMV interrupts for all ports, even those supporting it.""" + bitrate_stats: int | None = None + """Set the logical core N to perform bitrate calculation.""" + latencystats: int | None = None + """Set the logical core N to perform latency and jitter calculations.""" + print_events: list[TestPmdEvent] | None = field( + default=None, metadata=multiple(long("print-event")) + ) + """Enable printing the occurrence of the designated events. + Using :attr:`TestPmdEvent.ALL` will enable all of them. + """ + mask_events: list[TestPmdEvent] | None = field( + default_factory=lambda: [TestPmdEvent.intr_lsc], metadata=multiple(long("mask-event")) + ) + """Disable printing the occurrence of the designated events. + Using :attr:`TestPmdEvent.ALL` will disable all of them. + """ + + flow_isolate_all: Option = None + """Providing this parameter requests flow API isolated mode on all ports at initialization time. + It ensures all traffic is received through the configured flow rules only (see flow command). + + Ports that do not support this mode are automatically discarded. + """ + disable_flow_flush: Option = None + """Disable port flow flush when stopping port. + This allows testing keep flow rules or shared flow objects across restart. + """ + + hot_plug: Option = None + """Enable device event monitor mechanism for hotplug.""" + vxlan_gpe_port: int | None = None + """Set the UDP port number of tunnel VXLAN-GPE to N. The default value is 4790.""" + geneve_parsed_port: int | None = None + """Set the UDP port number that is used for parsing the GENEVE protocol to N. + HW may be configured with another tunnel Geneve port. The default value is 6081. + """ + lock_all_memory: BooleanOption = field(default=False, metadata=long("mlockall")) + """Enable/disable locking all memory. Disabled by default.""" + mempool_allocation_mode: ( + Literal[ + TestPmdMempoolAllocationMode.native, + TestPmdMempoolAllocationMode.xmem, + TestPmdMempoolAllocationMode.xmemhuge, + ] + | TestPmdAnonMempoolAllocationMode + | None + ) = field(default=None, metadata=long("mp-alloc")) + """Select mempool allocation mode. + + The value can be one of: + * :attr:`TestPmdMempoolAllocationMode.native` + * :class:`TestPmdAnonMempoolAllocationMode` + * :attr:`TestPmdMempoolAllocationMode.xmem` + * :attr:`TestPmdMempoolAllocationMode.xmemhuge` + """ + record_core_cycles: Option = None + """Enable measurement of CPU cycles per packet.""" + record_burst_status: Option = None + """Enable display of RX and TX burst stats.""" + + +class TestPmdDevice(object): + """The data of a device that testpmd can recognize. + + Attributes: + pci_address: The PCI address of the device. + """ + + pci_address: str + + def __init__(self, pci_address_line: str): + """Initialize the device from the testpmd output line string. + + Args: + pci_address_line: A line of testpmd output that contains a device. + """ + self.pci_address = pci_address_line.strip().split(": ")[1].strip() + + def __str__(self) -> str: + """The PCI address captures what the device is.""" + return self.pci_address + + class TestPmdShell(InteractiveShell): """Testpmd interactive shell. @@ -123,8 +720,8 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None assert isinstance(self._app_args, EalParameters) - if isinstance(self._app_args.app_params, StrParams): - self._app_args.app_params.value += " -i --mask-event intr_lsc" + if self._app_args.app_params is None: + self._app_args.app_params = TestPmdParameters() self.number_of_ports = len(self._app_args.ports) if self._app_args.ports is not None else 0 From patchwork Tue Mar 26 19:04:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 138830 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id D3B1043D5B; Tue, 26 Mar 2024 20:05:06 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id F08BD427D7; Tue, 26 Mar 2024 20:04:38 +0100 (CET) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id BEDE2410F2 for ; Tue, 26 Mar 2024 20:04:36 +0100 (CET) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id E2E6F2F4; Tue, 26 Mar 2024 12:05:09 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.16.115]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 4EF233F64C; Tue, 26 Mar 2024 12:04:35 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Luca Vizzarro , Jack Bond-Preston , Honnappa Nagarahalli Subject: [PATCH 4/6] dts: use testpmd params for scatter test suite Date: Tue, 26 Mar 2024 19:04:20 +0000 Message-Id: <20240326190422.577028-5-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240326190422.577028-1-luca.vizzarro@arm.com> References: <20240326190422.577028-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Update the buffer scatter test suite to use TestPmdParameters instead of the StrParams implementation. Signed-off-by: Luca Vizzarro Reviewed-by: Jack Bond-Preston Reviewed-by: Honnappa Nagarahalli --- dts/tests/TestSuite_pmd_buffer_scatter.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/dts/tests/TestSuite_pmd_buffer_scatter.py b/dts/tests/TestSuite_pmd_buffer_scatter.py index 4cdbdc4272..c6d313fc64 100644 --- a/dts/tests/TestSuite_pmd_buffer_scatter.py +++ b/dts/tests/TestSuite_pmd_buffer_scatter.py @@ -23,7 +23,11 @@ from scapy.utils import hexstr # type: ignore[import] from framework.params import StrParams -from framework.remote_session.testpmd_shell import TestPmdForwardingModes, TestPmdShell +from framework.remote_session.testpmd_shell import ( + TestPmdForwardingModes, + TestPmdShell, + TestPmdParameters, +) from framework.test_suite import TestSuite @@ -104,16 +108,15 @@ def pmd_scatter(self, mbsize: int) -> None: """ testpmd = self.sut_node.create_interactive_shell( TestPmdShell, - app_parameters=StrParams( - "--mbcache=200 " - f"--mbuf-size={mbsize} " - "--max-pkt-len=9000 " - "--port-topology=paired " - "--tx-offloads=0x00008000" + app_parameters=TestPmdParameters( + forward_mode=TestPmdForwardingModes.mac, + mbcache=200, + mbuf_size=[mbsize], + max_pkt_len=9000, + tx_offloads=0x00008000, ), privileged=True, ) - testpmd.set_forward_mode(TestPmdForwardingModes.mac) testpmd.start() for offset in [-1, 0, 1, 4, 5]: From patchwork Tue Mar 26 19:04:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 138831 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id D004A43D5B; Tue, 26 Mar 2024 20:05:13 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 30195427DA; Tue, 26 Mar 2024 20:04:42 +0100 (CET) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id EEF334161A for ; Tue, 26 Mar 2024 20:04:37 +0100 (CET) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 2546B2F4; Tue, 26 Mar 2024 12:05:11 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.16.115]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 7EAC23F64C; Tue, 26 Mar 2024 12:04:36 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Luca Vizzarro , Jack Bond-Preston , Honnappa Nagarahalli Subject: [PATCH 5/6] dts: add statefulness to InteractiveShell Date: Tue, 26 Mar 2024 19:04:21 +0000 Message-Id: <20240326190422.577028-6-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240326190422.577028-1-luca.vizzarro@arm.com> References: <20240326190422.577028-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org The InteractiveShell class can be started in privileged mode, but this is not saved for reference to the tests developer. Moreover, originally a command timeout could only be set at initialisation, this can now be amended and reset back as needed. Signed-off-by: Luca Vizzarro Reviewed-by: Jack Bond-Preston Reviewed-by: Honnappa Nagarahalli --- .../remote_session/interactive_shell.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py index a2c7b30d9f..5d80061e8d 100644 --- a/dts/framework/remote_session/interactive_shell.py +++ b/dts/framework/remote_session/interactive_shell.py @@ -41,8 +41,10 @@ class InteractiveShell(ABC): _stdout: channel.ChannelFile _ssh_channel: Channel _logger: DTSLogger + __default_timeout: float _timeout: float _app_args: Params | None + _is_privileged: bool = False #: Prompt to expect at the end of output when sending a command. #: This is often overridden by subclasses. @@ -88,7 +90,7 @@ def __init__( self._ssh_channel.settimeout(timeout) self._ssh_channel.set_combine_stderr(True) # combines stdout and stderr streams self._logger = logger - self._timeout = timeout + self._timeout = self.__default_timeout = timeout self._app_args = app_args self._start_application(get_privileged_command) @@ -105,6 +107,7 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None start_command = f"{self.path} {self._app_args or ''}" if get_privileged_command is not None: start_command = get_privileged_command(start_command) + self._is_privileged = True self.send_command(start_command) def send_command(self, command: str, prompt: str | None = None) -> str: @@ -150,3 +153,16 @@ def close(self) -> None: def __del__(self) -> None: """Make sure the session is properly closed before deleting the object.""" self.close() + + @property + def is_privileged(self) -> bool: + """Property specifying if the interactive shell is running in privileged mode.""" + return self._is_privileged + + def set_timeout(self, timeout: float): + """Set the timeout to use with the next commands.""" + self._timeout = timeout + + def reset_timeout(self): + """Reset the timeout to the default setting.""" + self._timeout = self.__default_timeout From patchwork Tue Mar 26 19:04:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 138832 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 0A69343D5B; Tue, 26 Mar 2024 20:05:20 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 72C5C427E3; Tue, 26 Mar 2024 20:04:43 +0100 (CET) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id D8B7A40ED6 for ; Tue, 26 Mar 2024 20:04:40 +0100 (CET) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 109B1339; Tue, 26 Mar 2024 12:05:14 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.16.115]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id C18B13F64C; Tue, 26 Mar 2024 12:04:37 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Luca Vizzarro , Jack Bond-Preston , Honnappa Nagarahalli Subject: [PATCH 6/6] dts: add statefulness to TestPmdShell Date: Tue, 26 Mar 2024 19:04:22 +0000 Message-Id: <20240326190422.577028-7-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240326190422.577028-1-luca.vizzarro@arm.com> References: <20240326190422.577028-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org This commit provides a state container for TestPmdShell. It currently only indicates whether the packet forwarding has started or not, and the number of ports which were given to the shell. This also fixes the behaviour of `wait_link_status_up` to use the command timeout as inherited from InteractiveShell. Signed-off-by: Luca Vizzarro Reviewed-by: Jack Bond-Preston Reviewed-by: Honnappa Nagarahalli --- dts/framework/remote_session/testpmd_shell.py | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index a823dc53be..ea1d254f86 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -678,19 +678,27 @@ def __str__(self) -> str: return self.pci_address +@dataclass(slots=True) +class TestPmdState: + """Session state container.""" + + #: + packet_forwarding_started: bool = False + + #: The number of ports which were allowed on the command-line when testpmd was started. + number_of_ports: int = 0 + + class TestPmdShell(InteractiveShell): """Testpmd interactive shell. The testpmd shell users should never use the :meth:`~.interactive_shell.InteractiveShell.send_command` method directly, but rather call specialized methods. If there isn't one that satisfies a need, it should be added. - - Attributes: - number_of_ports: The number of ports which were allowed on the command-line when testpmd - was started. """ - number_of_ports: int + #: Current state + state: TestPmdState = TestPmdState() #: The path to the testpmd executable. path: ClassVar[PurePath] = PurePath("app", "dpdk-testpmd") @@ -723,7 +731,13 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None if self._app_args.app_params is None: self._app_args.app_params = TestPmdParameters() - self.number_of_ports = len(self._app_args.ports) if self._app_args.ports is not None else 0 + assert isinstance(self._app_args.app_params, TestPmdParameters) + + if self._app_args.app_params.auto_start: + self.state.packet_forwarding_started = True + + if self._app_args.ports is not None: + self.state.number_of_ports = len(self._app_args.ports) super()._start_application(get_privileged_command) @@ -746,12 +760,14 @@ def start(self, verify: bool = True) -> None: self._logger.debug(f"Failed to start packet forwarding: \n{start_cmd_output}") raise InteractiveCommandExecutionError("Testpmd failed to start packet forwarding.") - for port_id in range(self.number_of_ports): + for port_id in range(self.state.number_of_ports): if not self.wait_link_status_up(port_id): raise InteractiveCommandExecutionError( "Not all ports came up after starting packet forwarding in testpmd." ) + self.state.packet_forwarding_started = True + def stop(self, verify: bool = True) -> None: """Stop packet forwarding. @@ -773,6 +789,8 @@ def stop(self, verify: bool = True) -> None: self._logger.debug(f"Failed to stop packet forwarding: \n{stop_cmd_output}") raise InteractiveCommandExecutionError("Testpmd failed to stop packet forwarding.") + self.state.packet_forwarding_started = False + def get_devices(self) -> list[TestPmdDevice]: """Get a list of device names that are known to testpmd. @@ -788,19 +806,16 @@ def get_devices(self) -> list[TestPmdDevice]: dev_list.append(TestPmdDevice(line)) return dev_list - def wait_link_status_up(self, port_id: int, timeout=SETTINGS.timeout) -> bool: - """Wait until the link status on the given port is "up". + def wait_link_status_up(self, port_id: int) -> bool: + """Wait until the link status on the given port is "up". Times out. Arguments: port_id: Port to check the link status on. - timeout: Time to wait for the link to come up. The default value for this - argument may be modified using the :option:`--timeout` command-line argument - or the :envvar:`DTS_TIMEOUT` environment variable. Returns: Whether the link came up in time or not. """ - time_to_stop = time.time() + timeout + time_to_stop = time.time() + self._timeout port_info: str = "" while time.time() < time_to_stop: port_info = self.send_command(f"show port info {port_id}")