From patchwork Fri Apr 12 11:11:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 139237 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 8A23743E51; Fri, 12 Apr 2024 13:11:57 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id AD1704068A; Fri, 12 Apr 2024 13:11:54 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 574C5402D4 for ; Fri, 12 Apr 2024 13:11:53 +0200 (CEST) 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 E7EAC339; Fri, 12 Apr 2024 04:12:21 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.19.212]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 7F1363F766; Fri, 12 Apr 2024 04:11:51 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Jeremy Spewock , Luca Vizzarro , Paul Szczepanek , Jack Bond-Preston Subject: [PATCH 1/5] dts: fix InteractiveShell command prompt filtering Date: Fri, 12 Apr 2024 12:11:32 +0100 Message-Id: <20240412111136.3470304-2-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240412111136.3470304-1-luca.vizzarro@arm.com> References: <20240412111136.3470304-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 When sending a command using an instance of InteractiveShell the output is meant to filter out the leading shell prompt. The filtering logic is present but the line is appended anyways. Bugzilla ID: 1411 Fixes: 88489c0501af ("dts: add smoke tests") Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek Reviewed-by: Jack Bond-Preston --- Cc: Jeremy Spewock --- dts/framework/remote_session/interactive_shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py index 5cfe202e15..8a9bf96ea9 100644 --- a/dts/framework/remote_session/interactive_shell.py +++ b/dts/framework/remote_session/interactive_shell.py @@ -132,11 +132,11 @@ def send_command(self, command: str, prompt: str | None = None) -> str: self._stdin.flush() out: str = "" for line in self._stdout: - out += line if prompt in line and not line.rstrip().endswith( command.rstrip() ): # ignore line that sent command break + out += line self._logger.debug(f"Got output: {out}") return out From patchwork Fri Apr 12 11:11:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 139238 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 8CB3C43E51; Fri, 12 Apr 2024 13:12:03 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id D3A7D406BA; Fri, 12 Apr 2024 13:11:55 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 0E8D1400D6 for ; Fri, 12 Apr 2024 13:11:55 +0200 (CEST) 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 B4772339; Fri, 12 Apr 2024 04:12:23 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.19.212]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 176973F766; Fri, 12 Apr 2024 04:11:52 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Jeremy Spewock , Luca Vizzarro , Paul Szczepanek , Jack Bond-Preston Subject: [PATCH 2/5] dts: skip first line of send_command output Date: Fri, 12 Apr 2024 12:11:33 +0100 Message-Id: <20240412111136.3470304-3-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240412111136.3470304-1-luca.vizzarro@arm.com> References: <20240412111136.3470304-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 first line of the InteractiveShell send_command method is generally the command input field. This sometimes is unwanted, therefore this commit enables the possibility of omitting the first line from the returned output. Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek Reviewed-by: Jack Bond-Preston --- dts/framework/remote_session/interactive_shell.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py index 8a9bf96ea9..e290a083e9 100644 --- a/dts/framework/remote_session/interactive_shell.py +++ b/dts/framework/remote_session/interactive_shell.py @@ -105,7 +105,9 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None start_command = get_privileged_command(start_command) self.send_command(start_command) - def send_command(self, command: str, prompt: str | None = None) -> str: + def send_command( + self, command: str, prompt: str | None = None, skip_first_line: bool = False + ) -> str: """Send `command` and get all output before the expected ending string. Lines that expect input are not included in the stdout buffer, so they cannot @@ -121,6 +123,7 @@ def send_command(self, command: str, prompt: str | None = None) -> str: command: The command to send. prompt: After sending the command, `send_command` will be expecting this string. If :data:`None`, will use the class's default prompt. + skip_first_line: Skip the first line when capturing the output. Returns: All output in the buffer before expected string. @@ -132,6 +135,9 @@ def send_command(self, command: str, prompt: str | None = None) -> str: self._stdin.flush() out: str = "" for line in self._stdout: + if skip_first_line: + skip_first_line = False + continue if prompt in line and not line.rstrip().endswith( command.rstrip() ): # ignore line that sent command From patchwork Fri Apr 12 11:11:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 139239 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 0468F43E51; Fri, 12 Apr 2024 13:12:10 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id EEE8B40A72; Fri, 12 Apr 2024 13:11:57 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 6623A40A4B for ; Fri, 12 Apr 2024 13:11:56 +0200 (CEST) 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 34C5B339; Fri, 12 Apr 2024 04:12:25 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.19.212]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id CEC4E3F766; Fri, 12 Apr 2024 04:11:54 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Jeremy Spewock , Luca Vizzarro , Paul Szczepanek Subject: [PATCH 3/5] dts: add parsing utility module Date: Fri, 12 Apr 2024 12:11:34 +0100 Message-Id: <20240412111136.3470304-4-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240412111136.3470304-1-luca.vizzarro@arm.com> References: <20240412111136.3470304-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 Adds parsing text into a custom data structure. It provides a new `TextParser` dataclass to be inherited. This implements the `parse` method, which combined with the parser functions, it can automatically parse the value for each field. Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/parser.py | 147 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 dts/framework/parser.py diff --git a/dts/framework/parser.py b/dts/framework/parser.py new file mode 100644 index 0000000000..5a2ba0c93a --- /dev/null +++ b/dts/framework/parser.py @@ -0,0 +1,147 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Arm Limited + +"""Parsing utility module. + +This module provides :class:`~TextParser` which can be used to model any data structure +that can parse a block of text. +""" + +from dataclasses import dataclass, fields, MISSING +import re +from typing import TypeVar +from typing_extensions import Self + +T = TypeVar("T") + + +META_PARSERS = "parsers" + + +def chain(parser, metadata): + """Chain a parser function. + + The parser function can take and return a single argument of any type. It is + up to the user to ensure that the chained functions have compatible types. + + Args: + parser: the parser function pointer + metadata: pre-existing metadata to chain if any + """ + parsers = metadata.get(META_PARSERS) or [] + parsers.append(parser) + return {**metadata, META_PARSERS: parsers} + + +def to_int(metadata={}, base=0): + """Converts a string to an integer. + + Args: + metadata: pre-existing metadata to chain if any + base: argument passed to the constructor of ``int`` + """ + return chain(lambda v: int(v, base), metadata) + + +def eq(v2, metadata={}): + """Compares two values and returns a boolean. + + Args: + v2: value to compare with the incoming value + metadata: pre-existing metadata to chain if any + """ + return chain(lambda v1: v1 == v2, metadata) + + +def to_bool(metadata={}): + """Evaluates a string into a boolean. + + The following case-insensitive words yield ``True``: on, yes, enabled, true. + + Args: + metadata: pre-existing metadata to chain if any + """ + return chain(lambda s: s.lower() in ["on", "yes", "enabled", "true"], metadata) + + +def regex( + pattern: str | re.Pattern[str], + flags: re.RegexFlag = re.RegexFlag(0), + named: bool = False, + metadata={}, +): + """Searches for a regular expression in a text. + + If there is only one capture group, its value is returned, otherwise a tuple containing all the + capture groups values is returned instead. + + Args: + pattern: the regular expression pattern + flags: the regular expression flags + named: if set to True only the named capture groups will be returned as a dictionary + metadata: pre-existing metadata to chain if any + """ + pattern = re.compile(pattern, flags) + + def regex_parser(text: str): + m = pattern.search(text) + if m is None: + return m + + if named: + return m.groupdict() + + matches = m.groups() + if len(matches) == 1: + return matches[0] + + return matches + + return chain(regex_parser, metadata) + + +@dataclass +class TextParser: + """Helper abstract dataclass that parses a text according to the fields' rules. + + This class is accompanied by a selection of parser functions and a generic chaining function, + that are to be set to the fields' metadata, to enable parsing. If a field metadata is not set with + any parser function, this is skipped. + """ + + @classmethod + def parse(cls, text: str) -> Self: + """The parsing class method. + + This function loops through every field that has any parser function associated with it and runs + each parser chain to the supplied text. If a parser function returns None, it expects that parsing + has failed and continues to the next field. + + Args: + text: the text to parse + Raises: + RuntimeError: if the parser did not find a match and the field does not have a default value + or default factory. + """ + fields_values = {} + for field in fields(cls): + parsers = field.metadata.get(META_PARSERS) + if parsers is None: + continue + + field_value = text + for parser_fn in parsers: + field_value = parser_fn(field_value) + if field_value is None: + # nothing was actually parsed, move on + break + + if field_value is None: + if field.default is MISSING and field.default_factory is MISSING: + raise RuntimeError( + f"parsers for field {field.name} returned None, but the field has no default" + ) + else: + fields_values[field.name] = field_value + + return cls(**fields_values) From patchwork Fri Apr 12 11:11:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 139240 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 73A4543E51; Fri, 12 Apr 2024 13:12:16 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 5FAF840A7D; Fri, 12 Apr 2024 13:11:59 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id A74D940A4B for ; Fri, 12 Apr 2024 13:11:57 +0200 (CEST) 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 61C4E113E; Fri, 12 Apr 2024 04:12:26 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.19.212]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 4E05E3F766; Fri, 12 Apr 2024 04:11:56 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Jeremy Spewock , Luca Vizzarro , Paul Szczepanek Subject: [PATCH 4/5] dts: add `show port info` command to TestPmdShell Date: Fri, 12 Apr 2024 12:11:35 +0100 Message-Id: <20240412111136.3470304-5-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240412111136.3470304-1-luca.vizzarro@arm.com> References: <20240412111136.3470304-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 Add a new TestPmdPort data structure to represent the output returned by `show port info`, which is implemented as part of TestPmdShell. The TestPmdPort data structure and its derived classes are modelled based on the relevant testpmd source code. This implementation makes extensive use of regular expressions, which all parse individually. The rationale behind this is to lower the risk of the testpmd output changing as part of development. Therefore minimising breakage. Bugzilla ID: 1407 Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 473 +++++++++++++++++- 1 file changed, 472 insertions(+), 1 deletion(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index cb2ab6bd00..3cf123ff57 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,14 +16,19 @@ testpmd_shell.close() """ +from dataclasses import dataclass, field +import re import time -from enum import auto +from enum import Flag, auto from pathlib import PurePath from typing import Callable, ClassVar +from typing_extensions import Self from framework.exception import InteractiveCommandExecutionError from framework.settings import SETTINGS from framework.utils import StrEnum +from framework import parser +from framework.parser import TextParser from .interactive_shell import InteractiveShell @@ -80,6 +86,439 @@ class TestPmdForwardingModes(StrEnum): recycle_mbufs = auto() +class VLANOffloadFlag(Flag): + #: + STRIP = auto() + #: + FILTER = auto() + #: + EXTEND = auto() + #: + QINQ_STRIP = auto() + + @classmethod + def from_str_dict(cls, d): + """Makes an instance from a dictionary containing the flag member names with an "on" value.""" + flag = cls(0) + for name in cls.__members__: + if d.get(name) == "on": + flag |= cls[name] + return flag + + +class RSSOffloadTypesFlag(Flag): + #: + ipv4 = auto() + #: + ipv4_frag = auto() + #: + ipv4_tcp = auto() + #: + ipv4_udp = auto() + #: + ipv4_sctp = auto() + #: + ipv4_other = auto() + #: + ipv6 = auto() + #: + ipv6_frag = auto() + #: + ipv6_tcp = auto() + #: + ipv6_udp = auto() + #: + ipv6_sctp = auto() + #: + ipv6_other = auto() + #: + l2_payload = auto() + #: + ipv6_ex = auto() + #: + ipv6_tcp_ex = auto() + #: + ipv6_udp_ex = auto() + #: + port = auto() + #: + vxlan = auto() + #: + geneve = auto() + #: + nvgre = auto() + #: + user_defined_22 = auto() + #: + gtpu = auto() + #: + eth = auto() + #: + s_vlan = auto() + #: + c_vlan = auto() + #: + esp = auto() + #: + ah = auto() + #: + l2tpv3 = auto() + #: + pfcp = auto() + #: + pppoe = auto() + #: + ecpri = auto() + #: + mpls = auto() + #: + ipv4_chksum = auto() + #: + l4_chksum = auto() + #: + l2tpv2 = auto() + #: + ipv6_flow_label = auto() + #: + user_defined_38 = auto() + #: + user_defined_39 = auto() + #: + user_defined_40 = auto() + #: + user_defined_41 = auto() + #: + user_defined_42 = auto() + #: + user_defined_43 = auto() + #: + user_defined_44 = auto() + #: + user_defined_45 = auto() + #: + user_defined_46 = auto() + #: + user_defined_47 = auto() + #: + user_defined_48 = auto() + #: + user_defined_49 = auto() + #: + user_defined_50 = auto() + #: + user_defined_51 = auto() + #: + l3_pre96 = auto() + #: + l3_pre64 = auto() + #: + l3_pre56 = auto() + #: + l3_pre48 = auto() + #: + l3_pre40 = auto() + #: + l3_pre32 = auto() + #: + l2_dst_only = auto() + #: + l2_src_only = auto() + #: + l4_dst_only = auto() + #: + l4_src_only = auto() + #: + l3_dst_only = auto() + #: + l3_src_only = auto() + + #: + ip = ipv4 | ipv4_frag | ipv4_other | ipv6 | ipv6_frag | ipv6_other | ipv6_ex + #: + udp = ipv4_udp | ipv6_udp | ipv6_udp_ex + #: + tcp = ipv4_tcp | ipv6_tcp | ipv6_tcp_ex + #: + sctp = ipv4_sctp | ipv6_sctp + #: + tunnel = vxlan | geneve | nvgre + #: + vlan = s_vlan | c_vlan + #: + all = ( + eth + | vlan + | ip + | tcp + | udp + | sctp + | l2_payload + | l2tpv3 + | esp + | ah + | pfcp + | gtpu + | ecpri + | mpls + | l2tpv2 + ) + + @classmethod + def from_list_string(cls, names: str) -> Self: + flag = cls(0) + for name in names.split(): + flag |= cls.from_str(name) + return flag + + @classmethod + def from_str(cls, name: str) -> Self: + member_name = name.strip().replace("-", "_") + return cls[member_name] + + def __str__(self): + return self.name.replace("_", "-") + + +class DeviceCapabilitiesFlag(Flag): + RUNTIME_RX_QUEUE_SETUP = auto() + """Device supports Rx queue setup after device started.""" + RUNTIME_TX_QUEUE_SETUP = auto() + """Device supports Tx queue setup after device started.""" + RXQ_SHARE = auto() + """Device supports shared Rx queue among ports within Rx domain and switch domain.""" + FLOW_RULE_KEEP = auto() + """Device supports keeping flow rules across restart.""" + FLOW_SHARED_OBJECT_KEEP = auto() + """Device supports keeping shared flow objects across restart.""" + + +class DeviceErrorHandlingMode(StrEnum): + #: + none = auto() + #: + passive = auto() + #: + proactive = auto() + #: + unknown = auto() + + +def _validate_device_private_info(info: str) -> str | None: + """Ensure that we are not parsing invalid device private info output.""" + info = info.strip() + if info == "none" or info.startswith("Invalid file") or info.startswith("Failed to dump"): + return None + return info + + +@dataclass +class TestPmdPort(TextParser): + #: + id: int = field(metadata=parser.to_int(parser.regex(r"Infos for port (\d+)\b"))) + #: + device_name: str = field(metadata=parser.regex(r"Device name: ([^\r\n]+)")) + #: + driver_name: str = field(metadata=parser.regex(r"Driver name: ([^\r\n]+)")) + #: + socket_id: int = field(metadata=parser.to_int(parser.regex(r"Connect to socket: (\d+)"))) + #: + is_link_up: bool = field(metadata=parser.eq("up", parser.regex(r"Link status: (up|down)"))) + #: + link_speed: str = field(metadata=parser.regex(r"Link speed: ([^\r\n]+)")) + #: + is_link_full_duplex: bool = field( + metadata=parser.eq("full", parser.regex(r"Link duplex: (full|half)-duplex")) + ) + #: + is_link_autonegotiated: bool = field( + metadata=parser.to_bool(parser.regex(r"Autoneg status: (On|Off)")) + ) + #: + is_promiscuous_mode_enabled: bool = field( + metadata=parser.to_bool(parser.regex(r"Promiscuous mode: (enabled|disabled)")) + ) + #: + is_allmulticast_mode_enabled: bool = field( + metadata=parser.to_bool(parser.regex(r"Allmulticast mode: (enabled|disabled)")) + ) + #: Maximum number of MAC addresses + max_mac_addresses_num: int = field( + metadata=parser.to_int(parser.regex(r"Maximum number of MAC addresses: (\d+)")) + ) + #: Maximum configurable length of RX packet + max_hash_mac_addresses_num: int = field( + metadata=parser.to_int( + parser.regex(r"Maximum number of MAC addresses of hash filtering: (\d+)") + ) + ) + #: Minimum size of RX buffer + min_rx_bufsize: int = field( + metadata=parser.to_int(parser.regex(r"Minimum size of RX buffer: (\d+)")) + ) + #: Maximum configurable length of RX packet + max_rx_packet_length: int = field( + metadata=parser.to_int(parser.regex(r"Maximum configurable length of RX packet: (\d+)")) + ) + #: Maximum configurable size of LRO aggregated packet + max_lro_packet_size: int = field( + metadata=parser.to_int( + parser.regex(r"Maximum configurable size of LRO aggregated packet: (\d+)") + ) + ) + + #: Current number of RX queues + rx_queues_num: int = field( + metadata=parser.to_int(parser.regex(r"Current number of RX queues: (\d+)")) + ) + #: Max possible RX queues + max_rx_queues_num: int = field( + metadata=parser.to_int(parser.regex(r"Max possible RX queues: (\d+)")) + ) + #: Max possible number of RXDs per queue + max_queue_rxd_num: int = field( + metadata=parser.to_int(parser.regex(r"Max possible number of RXDs per queue: (\d+)")) + ) + #: Min possible number of RXDs per queue + min_queue_rxd_num: int = field( + metadata=parser.to_int(parser.regex(r"Min possible number of RXDs per queue: (\d+)")) + ) + #: RXDs number alignment + rxd_alignment_num: int = field( + metadata=parser.to_int(parser.regex(r"RXDs number alignment: (\d+)")) + ) + + #: Current number of TX queues + tx_queues_num: int = field( + metadata=parser.to_int(parser.regex(r"Current number of TX queues: (\d+)")) + ) + #: Max possible TX queues + max_tx_queues_num: int = field( + metadata=parser.to_int(parser.regex(r"Max possible TX queues: (\d+)")) + ) + #: Max possible number of TXDs per queue + max_queue_txd_num: int = field( + metadata=parser.to_int(parser.regex(r"Max possible number of TXDs per queue: (\d+)")) + ) + #: Min possible number of TXDs per queue + min_queue_txd_num: int = field( + metadata=parser.to_int(parser.regex(r"Min possible number of TXDs per queue: (\d+)")) + ) + #: TXDs number alignment + txd_alignment_num: int = field( + metadata=parser.to_int(parser.regex(r"TXDs number alignment: (\d+)")) + ) + #: Max segment number per packet + max_packet_segment_num: int = field( + metadata=parser.to_int(parser.regex(r"Max segment number per packet: (\d+)")) + ) + #: Max segment number per MTU/TSO + max_mtu_segment_num: int = field( + metadata=parser.to_int(parser.regex(r"Max segment number per MTU\/TSO: (\d+)")) + ) + + #: + device_capabilities: DeviceCapabilitiesFlag = field( + metadata=parser.chain( + DeviceCapabilitiesFlag, + parser.to_int(parser.regex(r"Device capabilities: (0x[A-Fa-f\d]+)")), + ) + ) + #: + device_error_handling_mode: DeviceErrorHandlingMode = field( + metadata=parser.chain( + DeviceErrorHandlingMode, parser.regex(r"Device error handling mode: (\w+)") + ) + ) + #: + device_private_info: str | None = field( + default=None, + metadata=parser.chain( + _validate_device_private_info, + parser.regex(r"Device private info:\s+([\s\S]+)", re.MULTILINE), + ), + ) + + #: + hash_key_size: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Hash key size in bytes: (\d+)")) + ) + #: + redirection_table_size: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Redirection table size: (\d+)")) + ) + #: + supported_rss_offload_flow_types: RSSOffloadTypesFlag = field( + default=RSSOffloadTypesFlag(0), + metadata=parser.chain( + RSSOffloadTypesFlag.from_list_string, + parser.regex(r"Supported RSS offload flow types:((?:\r?\n? \S+)+)", re.MULTILINE), + ), + ) + + #: + mac_address: str | None = field( + default=None, metadata=parser.regex(r"MAC address: ([A-Fa-f0-9:]+)") + ) + #: + fw_version: str | None = field( + default=None, metadata=parser.regex(r"Firmware-version: ([^\r\n]+)") + ) + #: + dev_args: str | None = field(default=None, metadata=parser.regex(r"Devargs: ([^\r\n]+)")) + #: Socket id of the memory allocation + mem_alloc_socket_id: int | None = field( + default=None, + metadata=parser.to_int(parser.regex(r"memory allocation on the socket: (\d+)")), + ) + #: + mtu: int | None = field(default=None, metadata=parser.to_int(parser.regex(r"MTU: (\d+)"))) + + #: + vlan_offload: VLANOffloadFlag | None = field( + default=None, + metadata=parser.chain( + VLANOffloadFlag.from_str_dict, + parser.regex( + r"VLAN offload:\s+" + r"strip (?Pon|off), " + r"filter (?Pon|off), " + r"extend (?Pon|off), " + r"qinq strip (?Pon|off)$", + re.MULTILINE, + named=True, + ), + ), + ) + + #: Maximum size of RX buffer + max_rx_bufsize: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Maximum size of RX buffer: (\d+)")) + ) + #: Maximum number of VFs + max_vfs_num: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Maximum number of VFs: (\d+)")) + ) + #: Maximum number of VMDq pools + max_vmdq_pools_num: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Maximum number of VMDq pools: (\d+)")) + ) + + #: + switch_name: str | None = field(default=None, metadata=parser.regex(r"Switch name: ([\r\n]+)")) + #: + switch_domain_id: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Switch domain Id: (\d+)")) + ) + #: + switch_port_id: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Switch Port Id: (\d+)")) + ) + #: + switch_rx_domain: int | None = field( + default=None, metadata=parser.to_int(parser.regex(r"Switch Rx domain: (\d+)")) + ) + + class TestPmdShell(InteractiveShell): """Testpmd interactive shell. @@ -225,6 +664,38 @@ def set_forward_mode(self, mode: TestPmdForwardingModes, verify: bool = True): f"Test pmd failed to set fwd mode to {mode.value}" ) + def show_port_info_all(self) -> list[TestPmdPort]: + """Returns the information of all the ports.""" + output = self.send_command("show port info all") + + ports = [] + iter = re.finditer(r"\*+.+\*+", output) + if next(iter, False): # we are slicing retrospectively, skip first block + start_pos = 0 + for block in iter: + end_pos = block.start() + ports.append(TestPmdPort.parse(output[start_pos:end_pos])) + start_pos = end_pos + + ports.append(TestPmdPort.parse(output[start_pos:])) + + return ports + + def show_port_info(self, port_id: int) -> TestPmdPort: + """Returns the given port information. + + Args: + port_id: The port ID to gather information for. + + Raises: + InteractiveCommandExecutionError: If `port_id` is invalid. + """ + output = self.send_command(f"show port info {port_id}", skip_first_line=True) + if output.startswith("Invalid port"): + raise InteractiveCommandExecutionError("invalid port given") + + return TestPmdPort.parse(output) + def close(self) -> None: """Overrides :meth:`~.interactive_shell.close`.""" self.send_command("quit", "") From patchwork Fri Apr 12 11:11:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 139241 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 94EF943E51; Fri, 12 Apr 2024 13:12:25 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id F094D40DCD; Fri, 12 Apr 2024 13:12:00 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id F0BEF40A75 for ; Fri, 12 Apr 2024 13:11:58 +0200 (CEST) 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 A97DA113E; Fri, 12 Apr 2024 04:12:27 -0700 (PDT) Received: from localhost.localdomain (unknown [10.57.19.212]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 734A33F766; Fri, 12 Apr 2024 04:11:57 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: =?utf-8?q?Juraj_Linke=C5=A1?= , Jeremy Spewock , Luca Vizzarro , Paul Szczepanek Subject: [PATCH 5/5] dts: add `show port stats` command to TestPmdShell Date: Fri, 12 Apr 2024 12:11:36 +0100 Message-Id: <20240412111136.3470304-6-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240412111136.3470304-1-luca.vizzarro@arm.com> References: <20240412111136.3470304-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 Add a new TestPmdPortStats data structure to represent the output returned by `show port stats`, which is implemented as part of TestPmdShell. Bugzilla ID: 1407 Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek Acked-by: Juraj Linkeš --- dts/framework/remote_session/testpmd_shell.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index 3cf123ff57..baf47d1a32 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -519,6 +519,42 @@ class TestPmdPort(TextParser): ) +@dataclass +class TestPmdPortStats(TextParser): + """Port statistics.""" + + #: + port_id: int = field(metadata=parser.to_int(parser.regex(r"NIC statistics for port (\d+)"))) + + #: + rx_packets: int = field(metadata=parser.to_int(parser.regex(r"RX-packets:\s+(\d+)"))) + #: + rx_missed: int = field(metadata=parser.to_int(parser.regex(r"RX-missed:\s+(\d+)"))) + #: + rx_bytes: int = field(metadata=parser.to_int(parser.regex(r"RX-bytes:\s+(\d+)"))) + #: + rx_errors: int = field(metadata=parser.to_int(parser.regex(r"RX-errors:\s+(\d+)"))) + #: + rx_nombuf: int = field(metadata=parser.to_int(parser.regex(r"RX-nombuf:\s+(\d+)"))) + + #: + tx_packets: int = field(metadata=parser.to_int(parser.regex(r"TX-packets:\s+(\d+)"))) + #: + tx_errors: int = field(metadata=parser.to_int(parser.regex(r"TX-errors:\s+(\d+)"))) + #: + tx_bytes: int = field(metadata=parser.to_int(parser.regex(r"TX-bytes:\s+(\d+)"))) + + #: + rx_pps: int = field(metadata=parser.to_int(parser.regex(r"Rx-pps:\s+(\d+)"))) + #: + rx_bps: int = field(metadata=parser.to_int(parser.regex(r"Rx-bps:\s+(\d+)"))) + + #: + tx_pps: int = field(metadata=parser.to_int(parser.regex(r"Tx-pps:\s+(\d+)"))) + #: + tx_bps: int = field(metadata=parser.to_int(parser.regex(r"Tx-bps:\s+(\d+)"))) + + class TestPmdShell(InteractiveShell): """Testpmd interactive shell. @@ -696,6 +732,28 @@ def show_port_info(self, port_id: int) -> TestPmdPort: return TestPmdPort.parse(output) + def show_port_stats_all(self) -> list[TestPmdPortStats]: + """Returns the statistics of all the ports.""" + output = self.send_command("show port stats all") + + iter = re.finditer(r"(^ #*.+#*$[^#]+)^ #*$", output, re.MULTILINE) + return [TestPmdPortStats.parse(block.group(1)) for block in iter] + + def show_port_stats(self, port_id: int) -> TestPmdPortStats: + """Returns the given port statistics. + + Args: + port_id: The port ID to gather information for. + + Raises: + InteractiveCommandExecutionError: If `port_id` is invalid. + """ + output = self.send_command(f"show port stats {port_id}") + if output.startswith("Invalid port"): + raise InteractiveCommandExecutionError("invalid port given") + + return TestPmdPortStats.parse(output) + def close(self) -> None: """Overrides :meth:`~.interactive_shell.close`.""" self.send_command("quit", "")