DPDK patches and discussions
 help / color / mirror / Atom feed
* [PATCH v1 0/1] dts: testpmd verbose parser
@ 2024-07-29 20:39 jspewock
  2024-07-29 20:39 ` [PATCH v1 1/1] dts: add text parser for testpmd verbose output jspewock
                   ` (4 more replies)
  0 siblings, 5 replies; 27+ messages in thread
From: jspewock @ 2024-07-29 20:39 UTC (permalink / raw)
  To: Luca.Vizzarro, juraj.linkes, npratte, wathsala.vithanage, thomas,
	paul.szczepanek, probb, Honnappa.Nagarahalli, yoan.picchi
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

This series adds a new text parser that is able to extract all verbose
messages from testpmd output into a more organized data structure.

Jeremy Spewock (1):
  dts: add text parser for testpmd verbose output

 dts/framework/parser.py                       |  30 ++++
 dts/framework/remote_session/testpmd_shell.py | 146 +++++++++++++++++-
 dts/framework/utils.py                        |   1 +
 3 files changed, 175 insertions(+), 2 deletions(-)

-- 
2.45.2


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v1 1/1] dts: add text parser for testpmd verbose output
  2024-07-29 20:39 [PATCH v1 0/1] dts: testpmd verbose parser jspewock
@ 2024-07-29 20:39 ` jspewock
  2024-07-30 13:34 ` [PATCH v2 0/1] dts: testpmd verbose parser jspewock
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 27+ messages in thread
From: jspewock @ 2024-07-29 20:39 UTC (permalink / raw)
  To: Luca.Vizzarro, juraj.linkes, npratte, wathsala.vithanage, thomas,
	paul.szczepanek, probb, Honnappa.Nagarahalli, yoan.picchi
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

Multiple test suites from the old DTS framework rely on being able to
consume and interpret the verbose output of testpmd. The new framework
doesn't have an elegant way for handling the verbose output, but test
suites are starting to be written that rely on it. This patch creates a
TextParser class that can be used to extract the verbose information
from any testpmd output and also adjusts the `stop` method of the shell
to return all output that it collected.

Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
---

One thing to note here is I don't love the regex in
extract_verbose_output(). It works great when there is a bunch of
verbose output in a row, but any chunk that isn't followed by another
piece of verbose output will contain everything that comes after it in
the match group. This could be solved by changing the regex to look
ahead only for the next port X/queue Y line instead of also including
the end of the string, and then having another alternate route which is
solely dedicated to the last block of verbose output which greedily
consumes everything until the end of ol_flags, but I didn't want to over
complicate the regex since the text parser will extract the specific
information it needs anyways. For reference, I was thinking it could be
something like this:

r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+)|port \d+/queue \d+:.*ol_flags: [\w ]+)"

but this has a lot of repition (some of which that could be ripped out
with a simple variable) and it is a little more confusing to read I
think.

 dts/framework/parser.py                       |  30 ++++
 dts/framework/remote_session/testpmd_shell.py | 146 +++++++++++++++++-
 dts/framework/utils.py                        |   1 +
 3 files changed, 175 insertions(+), 2 deletions(-)

diff --git a/dts/framework/parser.py b/dts/framework/parser.py
index 741dfff821..0b39025a48 100644
--- a/dts/framework/parser.py
+++ b/dts/framework/parser.py
@@ -160,6 +160,36 @@ def _find(text: str) -> Any:
 
         return ParserFn(TextParser_fn=_find)
 
+    @staticmethod
+    def find_all(
+        pattern: str | re.Pattern[str],
+        flags: re.RegexFlag = re.RegexFlag(0),
+    ) -> ParserFn:
+        """Makes a parser function that finds all of the regular expression matches in the text.
+
+        If there are no matches found in the text than None will be returned, otherwise a list
+        containing all matches will be returned. Patterns that contain multiple groups will pack
+        the matches for each group into a tuple.
+
+        Args:
+            pattern: The regular expression pattern.
+            flags: The regular expression flags. Ignored if the given pattern is already compiled.
+
+        Returns:
+            A :class:`ParserFn` that can be used as metadata for a dataclass field.
+        """
+        if isinstance(pattern, str):
+            pattern = re.compile(pattern, flags)
+
+        def _find_all(text: str) -> list[str] | None:
+            m = pattern.findall(text)
+            if len(m) == 0:
+                return None
+
+            return m
+
+        return ParserFn(TextParser_fn=_find_all)
+
     @staticmethod
     def find_int(
         pattern: str | re.Pattern[str],
diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
index 43e9f56517..9f09a98490 100644
--- a/dts/framework/remote_session/testpmd_shell.py
+++ b/dts/framework/remote_session/testpmd_shell.py
@@ -31,7 +31,7 @@
 from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
 from framework.testbed_model.sut_node import SutNode
-from framework.utils import StrEnum
+from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
 
 
 class TestPmdDevice:
@@ -577,6 +577,128 @@ class TestPmdPortStats(TextParser):
     tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
 
 
+class VerboseOLFlag(Flag):
+    """Flag representing the OL flags of a packet from Testpmd verbose output."""
+
+    #:
+    RTE_MBUF_F_RX_RSS_HASH = auto()
+
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = auto()
+
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = auto()
+
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing ol_flag values.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in cls.__members__:
+            if name in arr:
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls) -> ParserFn:
+        """Makes a parser function.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+@dataclass
+class TestPmdVerbosePacket(TextParser):
+    """Packet information provided by verbose output in Testpmd.
+
+    The "receive/sent queue" information is not included in this dataclass because this value is
+    captured on the outer layer of input found in :class:`TestPmdVerboseOutput`.
+    """
+
+    #:
+    src_mac: str = field(metadata=TextParser.find(r"src=({})".format(REGEX_FOR_MAC_ADDRESS)))
+    #:
+    dst_mac: str = field(metadata=TextParser.find(r"dst=({})".format(REGEX_FOR_MAC_ADDRESS)))
+    #: Memory pool the packet was handled on.
+    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
+    #: Packet type in hex.
+    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
+    #:
+    length: int = field(metadata=TextParser.find_int(r"length=(\d+)"))
+    #: Number of segments in the packet.
+    nb_segs: int = field(metadata=TextParser.find_int(r"nb_segs=(\d+)"))
+    #: Hardware packet type. Collected as a string delimited by whitespace.
+    hw_ptype: str = field(metadata=TextParser.find(r"hw ptype: ([^-]+)"))
+    #: Software packet type. Collected as a string delimited by whitespace.
+    sw_ptype: str = field(metadata=TextParser.find(r"sw ptype: ([^-]+)"))
+    #:
+    l2_len: int = field(metadata=TextParser.find_int(r"l2_len=(\d+)"))
+    #:
+    ol_flags: VerboseOLFlag = field(metadata=VerboseOLFlag.make_parser())
+    #: RSS has of the packet in hex.
+    rss_hash: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS hash=(0x[a-fA-F\d]+)")
+    )
+    #: RSS queue that handled the packet in hex.
+    rss_queue: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS queue=(0x[a-fA-F\d]+)")
+    )
+    #:
+    l3_len: int | None = field(default=None, metadata=TextParser.find_int(r"l3_len=(\d+)"))
+    #:
+    l4_len: int | None = field(default=None, metadata=TextParser.find_int(r"l4_len=(\d+)"))
+
+
+@dataclass
+class TestPmdVerboseOutput(TextParser):
+    """Verbose output generated by Testpmd.
+
+    This class is the top level of the output, containing verbose output delimited by
+    "port X/queue Y: sent/received Z packets".
+    """
+
+    #: ID of the port that handled the packet.
+    port_id: int = field(metadata=TextParser.find(r"port (\d+)/queue \d+"))
+    #: ID of the queue that handled the packet.
+    queue_id: int = field(metadata=TextParser.find(r"port \d+/queue (\d+)"))
+    #: Whether the packet was received or sent by the queue/port.
+    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
+    #: List of packets handed by the port/queue in this section.
+    packets: list[TestPmdVerbosePacket] = field(
+        metadata=TextParser.wrap(
+            TextParser.find_all(r"(src=[\w\s=:-]+ol_flags: [\w ]+)"),
+            lambda matches_arr: list(map(TestPmdVerbosePacket.parse, matches_arr)),
+        )
+    )
+
+
 class TestPmdShell(DPDKShell):
     """Testpmd interactive shell.
 
@@ -645,7 +767,7 @@ def start(self, verify: bool = True) -> None:
                         "Not all ports came up after starting packet forwarding in testpmd."
                     )
 
-    def stop(self, verify: bool = True) -> None:
+    def stop(self, verify: bool = True) -> str:
         """Stop packet forwarding.
 
         Args:
@@ -656,6 +778,9 @@ def stop(self, verify: bool = True) -> None:
         Raises:
             InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
                 forwarding results in an error.
+
+        Returns:
+            Output gathered from sending the stop command.
         """
         stop_cmd_output = self.send_command("stop")
         if verify:
@@ -665,6 +790,7 @@ 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.")
+        return stop_cmd_output
 
     def get_devices(self) -> list[TestPmdDevice]:
         """Get a list of device names that are known to testpmd.
@@ -806,6 +932,22 @@ def show_port_stats(self, port_id: int) -> TestPmdPortStats:
 
         return TestPmdPortStats.parse(output)
 
+    @staticmethod
+    def extract_verbose_output(output: str) -> list[TestPmdVerboseOutput]:
+        """Extract the verbose information present in given testpmd output.
+
+        This method extracts sections of verbose output that begin with the line
+        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
+
+        Args:
+            output: Testpmd output that contains verbose information
+
+        Returns:
+            List of parsed packet information gathered from verbose information in `output`.
+        """
+        iter = re.finditer(r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+|$))", output, re.S)
+        return [TestPmdVerboseOutput.parse(s.group(0)) for s in iter]
+
     def _close(self) -> None:
         """Overrides :meth:`~.interactive_shell.close`."""
         self.stop()
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 6b5d5a805f..9c64cf497f 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -27,6 +27,7 @@
 from .exception import ConfigurationError
 
 REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
+REGEX_FOR_MAC_ADDRESS: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
 
 
 def expand_range(range_str: str) -> list[int]:
-- 
2.45.2


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v2 0/1] dts: testpmd verbose parser
  2024-07-29 20:39 [PATCH v1 0/1] dts: testpmd verbose parser jspewock
  2024-07-29 20:39 ` [PATCH v1 1/1] dts: add text parser for testpmd verbose output jspewock
@ 2024-07-30 13:34 ` jspewock
  2024-07-30 13:34   ` [PATCH v2 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-08-08 20:36 ` [PATCH v3 0/1] dts: testpmd verbose parser jspewock
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 27+ messages in thread
From: jspewock @ 2024-07-30 13:34 UTC (permalink / raw)
  To: yoan.picchi, probb, paul.szczepanek, npratte, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

v2:
 * fix queue and port IDs types in the testpmd verbose output dataclass
   using find_int().

Jeremy Spewock (1):
  dts: add text parser for testpmd verbose output

 dts/framework/parser.py                       |  30 ++++
 dts/framework/remote_session/testpmd_shell.py | 146 +++++++++++++++++-
 dts/framework/utils.py                        |   1 +
 3 files changed, 175 insertions(+), 2 deletions(-)

-- 
2.45.2


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 13:34 ` [PATCH v2 0/1] dts: testpmd verbose parser jspewock
@ 2024-07-30 13:34   ` jspewock
  2024-07-30 15:41     ` Nicholas Pratte
                       ` (2 more replies)
  0 siblings, 3 replies; 27+ messages in thread
From: jspewock @ 2024-07-30 13:34 UTC (permalink / raw)
  To: yoan.picchi, probb, paul.szczepanek, npratte, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

Multiple test suites from the old DTS framework rely on being able to
consume and interpret the verbose output of testpmd. The new framework
doesn't have an elegant way for handling the verbose output, but test
suites are starting to be written that rely on it. This patch creates a
TextParser class that can be used to extract the verbose information
from any testpmd output and also adjusts the `stop` method of the shell
to return all output that it collected.

Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
---

One thing to note here is I don't love the regex in
extract_verbose_output(). It works great when there is a bunch of
verbose output in a row, but any chunk that isn't followed by another
piece of verbose output will contain everything that comes after it in
the match group. This could be solved by changing the regex to look
ahead only for the next port X/queue Y line instead of also including
the end of the string, and then having another alternate route which is
solely dedicated to the last block of verbose output which greedily
consumes everything until the end of ol_flags, but I didn't want to over
complicate the regex since the text parser will extract the specific
information it needs anyways. For reference, I was thinking it could be
something like this:

r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+)|port \d+/queue \d+:.*ol_flags: [\w ]+)"

but this has a lot of repition (some of which that could be ripped out
with a simple variable) and it is a little more confusing to read I
think.

 dts/framework/parser.py                       |  30 ++++
 dts/framework/remote_session/testpmd_shell.py | 146 +++++++++++++++++-
 dts/framework/utils.py                        |   1 +
 3 files changed, 175 insertions(+), 2 deletions(-)

diff --git a/dts/framework/parser.py b/dts/framework/parser.py
index 741dfff821..0b39025a48 100644
--- a/dts/framework/parser.py
+++ b/dts/framework/parser.py
@@ -160,6 +160,36 @@ def _find(text: str) -> Any:
 
         return ParserFn(TextParser_fn=_find)
 
+    @staticmethod
+    def find_all(
+        pattern: str | re.Pattern[str],
+        flags: re.RegexFlag = re.RegexFlag(0),
+    ) -> ParserFn:
+        """Makes a parser function that finds all of the regular expression matches in the text.
+
+        If there are no matches found in the text than None will be returned, otherwise a list
+        containing all matches will be returned. Patterns that contain multiple groups will pack
+        the matches for each group into a tuple.
+
+        Args:
+            pattern: The regular expression pattern.
+            flags: The regular expression flags. Ignored if the given pattern is already compiled.
+
+        Returns:
+            A :class:`ParserFn` that can be used as metadata for a dataclass field.
+        """
+        if isinstance(pattern, str):
+            pattern = re.compile(pattern, flags)
+
+        def _find_all(text: str) -> list[str] | None:
+            m = pattern.findall(text)
+            if len(m) == 0:
+                return None
+
+            return m
+
+        return ParserFn(TextParser_fn=_find_all)
+
     @staticmethod
     def find_int(
         pattern: str | re.Pattern[str],
diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
index 43e9f56517..dedf1553cf 100644
--- a/dts/framework/remote_session/testpmd_shell.py
+++ b/dts/framework/remote_session/testpmd_shell.py
@@ -31,7 +31,7 @@
 from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
 from framework.testbed_model.sut_node import SutNode
-from framework.utils import StrEnum
+from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
 
 
 class TestPmdDevice:
@@ -577,6 +577,128 @@ class TestPmdPortStats(TextParser):
     tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
 
 
+class VerboseOLFlag(Flag):
+    """Flag representing the OL flags of a packet from Testpmd verbose output."""
+
+    #:
+    RTE_MBUF_F_RX_RSS_HASH = auto()
+
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = auto()
+
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = auto()
+
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing ol_flag values.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in cls.__members__:
+            if name in arr:
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls) -> ParserFn:
+        """Makes a parser function.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+@dataclass
+class TestPmdVerbosePacket(TextParser):
+    """Packet information provided by verbose output in Testpmd.
+
+    The "receive/sent queue" information is not included in this dataclass because this value is
+    captured on the outer layer of input found in :class:`TestPmdVerboseOutput`.
+    """
+
+    #:
+    src_mac: str = field(metadata=TextParser.find(r"src=({})".format(REGEX_FOR_MAC_ADDRESS)))
+    #:
+    dst_mac: str = field(metadata=TextParser.find(r"dst=({})".format(REGEX_FOR_MAC_ADDRESS)))
+    #: Memory pool the packet was handled on.
+    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
+    #: Packet type in hex.
+    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
+    #:
+    length: int = field(metadata=TextParser.find_int(r"length=(\d+)"))
+    #: Number of segments in the packet.
+    nb_segs: int = field(metadata=TextParser.find_int(r"nb_segs=(\d+)"))
+    #: Hardware packet type. Collected as a string delimited by whitespace.
+    hw_ptype: str = field(metadata=TextParser.find(r"hw ptype: ([^-]+)"))
+    #: Software packet type. Collected as a string delimited by whitespace.
+    sw_ptype: str = field(metadata=TextParser.find(r"sw ptype: ([^-]+)"))
+    #:
+    l2_len: int = field(metadata=TextParser.find_int(r"l2_len=(\d+)"))
+    #:
+    ol_flags: VerboseOLFlag = field(metadata=VerboseOLFlag.make_parser())
+    #: RSS has of the packet in hex.
+    rss_hash: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS hash=(0x[a-fA-F\d]+)")
+    )
+    #: RSS queue that handled the packet in hex.
+    rss_queue: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS queue=(0x[a-fA-F\d]+)")
+    )
+    #:
+    l3_len: int | None = field(default=None, metadata=TextParser.find_int(r"l3_len=(\d+)"))
+    #:
+    l4_len: int | None = field(default=None, metadata=TextParser.find_int(r"l4_len=(\d+)"))
+
+
+@dataclass
+class TestPmdVerboseOutput(TextParser):
+    """Verbose output generated by Testpmd.
+
+    This class is the top level of the output, containing verbose output delimited by
+    "port X/queue Y: sent/received Z packets".
+    """
+
+    #: ID of the port that handled the packet.
+    port_id: int = field(metadata=TextParser.find_int(r"port (\d+)/queue \d+"))
+    #: ID of the queue that handled the packet.
+    queue_id: int = field(metadata=TextParser.find_int(r"port \d+/queue (\d+)"))
+    #: Whether the packet was received or sent by the queue/port.
+    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
+    #: List of packets handed by the port/queue in this section.
+    packets: list[TestPmdVerbosePacket] = field(
+        metadata=TextParser.wrap(
+            TextParser.find_all(r"(src=[\w\s=:-]+ol_flags: [\w ]+)"),
+            lambda matches_arr: list(map(TestPmdVerbosePacket.parse, matches_arr)),
+        )
+    )
+
+
 class TestPmdShell(DPDKShell):
     """Testpmd interactive shell.
 
@@ -645,7 +767,7 @@ def start(self, verify: bool = True) -> None:
                         "Not all ports came up after starting packet forwarding in testpmd."
                     )
 
-    def stop(self, verify: bool = True) -> None:
+    def stop(self, verify: bool = True) -> str:
         """Stop packet forwarding.
 
         Args:
@@ -656,6 +778,9 @@ def stop(self, verify: bool = True) -> None:
         Raises:
             InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
                 forwarding results in an error.
+
+        Returns:
+            Output gathered from sending the stop command.
         """
         stop_cmd_output = self.send_command("stop")
         if verify:
@@ -665,6 +790,7 @@ 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.")
+        return stop_cmd_output
 
     def get_devices(self) -> list[TestPmdDevice]:
         """Get a list of device names that are known to testpmd.
@@ -806,6 +932,22 @@ def show_port_stats(self, port_id: int) -> TestPmdPortStats:
 
         return TestPmdPortStats.parse(output)
 
+    @staticmethod
+    def extract_verbose_output(output: str) -> list[TestPmdVerboseOutput]:
+        """Extract the verbose information present in given testpmd output.
+
+        This method extracts sections of verbose output that begin with the line
+        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
+
+        Args:
+            output: Testpmd output that contains verbose information
+
+        Returns:
+            List of parsed packet information gathered from verbose information in `output`.
+        """
+        iter = re.finditer(r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+|$))", output, re.S)
+        return [TestPmdVerboseOutput.parse(s.group(0)) for s in iter]
+
     def _close(self) -> None:
         """Overrides :meth:`~.interactive_shell.close`."""
         self.stop()
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 6b5d5a805f..9c64cf497f 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -27,6 +27,7 @@
 from .exception import ConfigurationError
 
 REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
+REGEX_FOR_MAC_ADDRESS: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
 
 
 def expand_range(range_str: str) -> list[int]:
-- 
2.45.2


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 13:34   ` [PATCH v2 1/1] dts: add text parser for testpmd verbose output jspewock
@ 2024-07-30 15:41     ` Nicholas Pratte
  2024-07-30 21:30       ` Jeremy Spewock
  2024-07-30 21:33     ` Jeremy Spewock
  2024-08-01  8:41     ` Luca Vizzarro
  2 siblings, 1 reply; 27+ messages in thread
From: Nicholas Pratte @ 2024-07-30 15:41 UTC (permalink / raw)
  To: jspewock
  Cc: yoan.picchi, probb, paul.szczepanek, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage, dev

Good work! I left some probing/clarifying comments below for you.

<snip>
> +@dataclass
> +class TestPmdVerboseOutput(TextParser):
> +    """Verbose output generated by Testpmd.
> +
> +    This class is the top level of the output, containing verbose output delimited by
> +    "port X/queue Y: sent/received Z packets".
> +    """
> +
> +    #: ID of the port that handled the packet.
> +    port_id: int = field(metadata=TextParser.find_int(r"port (\d+)/queue \d+"))
> +    #: ID of the queue that handled the packet.
> +    queue_id: int = field(metadata=TextParser.find_int(r"port \d+/queue (\d+)"))
> +    #: Whether the packet was received or sent by the queue/port.
> +    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
> +    #: List of packets handed by the port/queue in this section.
> +    packets: list[TestPmdVerbosePacket] = field(
> +        metadata=TextParser.wrap(
> +            TextParser.find_all(r"(src=[\w\s=:-]+ol_flags: [\w ]+)"),
> +            lambda matches_arr: list(map(TestPmdVerbosePacket.parse, matches_arr)),
> +        )
> +    )
> +
> +
>  class TestPmdShell(DPDKShell):
>      """Testpmd interactive shell.
>
> @@ -645,7 +767,7 @@ def start(self, verify: bool = True) -> None:
>                          "Not all ports came up after starting packet forwarding in testpmd."
>                      )
>
> -    def stop(self, verify: bool = True) -> None:
> +    def stop(self, verify: bool = True) -> str:
>          """Stop packet forwarding.
>
>          Args:
> @@ -656,6 +778,9 @@ def stop(self, verify: bool = True) -> None:
>          Raises:
>              InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
>                  forwarding results in an error.
> +
> +        Returns:
> +            Output gathered from sending the stop command.
>          """
>          stop_cmd_output = self.send_command("stop")
>          if verify:
> @@ -665,6 +790,7 @@ 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.")
> +        return stop_cmd_output
<snip>
>
> +    @staticmethod
> +    def extract_verbose_output(output: str) -> list[TestPmdVerboseOutput]:
> +        """Extract the verbose information present in given testpmd output.
> +
> +        This method extracts sections of verbose output that begin with the line
> +        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
> +
> +        Args:
> +            output: Testpmd output that contains verbose information
> +
> +        Returns:
> +            List of parsed packet information gathered from verbose information in `output`.
> +        """
> +        iter = re.finditer(r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+|$))", output, re.S)
> +        return [TestPmdVerboseOutput.parse(s.group(0)) for s in iter]
> +

I'm trying to think of circumstances where we as developers would be
looking for anything else besides verbose output from the stop method.
Running the command outputs some statistics, but information like port
stats is displayed after the stop command is initiated. I'm
implementing this system right now for one of my test suites, and I'm
wondering if there might be any feasible way to extract output without
needing to input any explicit outputs into this method. I'm putting
output = testpmd.stop() and then calling this method. It looks
something like this:

verbose_output = testpmd.extract_verbose_output(testpmd.stop())

This is easy enough, but it might be a bit confusing for someone new
to the framework. The way that output is gathered is still elusive to
me, and I'm guessing that any commands run in-between setting verbose
mode and when you stop testpmd might influence how output is
generated. But in my experience so far, any statistics or information
I need is gathered before packets are sent, and the need for verbose
output in test cases is one after the other (i send a packet, look at
verbose output, and then move onto the next packet).
<snip>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 15:41     ` Nicholas Pratte
@ 2024-07-30 21:30       ` Jeremy Spewock
  2024-08-02 14:54         ` Nicholas Pratte
  0 siblings, 1 reply; 27+ messages in thread
From: Jeremy Spewock @ 2024-07-30 21:30 UTC (permalink / raw)
  To: Nicholas Pratte
  Cc: yoan.picchi, probb, paul.szczepanek, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage, dev

On Tue, Jul 30, 2024 at 11:41 AM Nicholas Pratte <npratte@iol.unh.edu> wrote:
>
> Good work! I left some probing/clarifying comments below for you.
>
> <snip>
> > +@dataclass
> > +class TestPmdVerboseOutput(TextParser):
> > +    """Verbose output generated by Testpmd.
> > +
> > +    This class is the top level of the output, containing verbose output delimited by
> > +    "port X/queue Y: sent/received Z packets".
> > +    """
> > +
> > +    #: ID of the port that handled the packet.
> > +    port_id: int = field(metadata=TextParser.find_int(r"port (\d+)/queue \d+"))
> > +    #: ID of the queue that handled the packet.
> > +    queue_id: int = field(metadata=TextParser.find_int(r"port \d+/queue (\d+)"))
> > +    #: Whether the packet was received or sent by the queue/port.
> > +    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
> > +    #: List of packets handed by the port/queue in this section.
> > +    packets: list[TestPmdVerbosePacket] = field(
> > +        metadata=TextParser.wrap(
> > +            TextParser.find_all(r"(src=[\w\s=:-]+ol_flags: [\w ]+)"),
> > +            lambda matches_arr: list(map(TestPmdVerbosePacket.parse, matches_arr)),
> > +        )
> > +    )
> > +
> > +
> >  class TestPmdShell(DPDKShell):
> >      """Testpmd interactive shell.
> >
> > @@ -645,7 +767,7 @@ def start(self, verify: bool = True) -> None:
> >                          "Not all ports came up after starting packet forwarding in testpmd."
> >                      )
> >
> > -    def stop(self, verify: bool = True) -> None:
> > +    def stop(self, verify: bool = True) -> str:
> >          """Stop packet forwarding.
> >
> >          Args:
> > @@ -656,6 +778,9 @@ def stop(self, verify: bool = True) -> None:
> >          Raises:
> >              InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
> >                  forwarding results in an error.
> > +
> > +        Returns:
> > +            Output gathered from sending the stop command.
> >          """
> >          stop_cmd_output = self.send_command("stop")
> >          if verify:
> > @@ -665,6 +790,7 @@ 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.")
> > +        return stop_cmd_output
> <snip>
> >
> > +    @staticmethod
> > +    def extract_verbose_output(output: str) -> list[TestPmdVerboseOutput]:
> > +        """Extract the verbose information present in given testpmd output.
> > +
> > +        This method extracts sections of verbose output that begin with the line
> > +        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
> > +
> > +        Args:
> > +            output: Testpmd output that contains verbose information
> > +
> > +        Returns:
> > +            List of parsed packet information gathered from verbose information in `output`.
> > +        """
> > +        iter = re.finditer(r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+|$))", output, re.S)
> > +        return [TestPmdVerboseOutput.parse(s.group(0)) for s in iter]
> > +
>
> I'm trying to think of circumstances where we as developers would be
> looking for anything else besides verbose output from the stop method.
> Running the command outputs some statistics, but information like port
> stats is displayed after the stop command is initiated. I'm
> implementing this system right now for one of my test suites, and I'm
> wondering if there might be any feasible way to extract output without
> needing to input any explicit outputs into this method. I'm putting
> output = testpmd.stop() and then calling this method. It looks
> something like this:
>
> verbose_output = testpmd.extract_verbose_output(testpmd.stop())
>
> This is easy enough, but it might be a bit confusing for someone new
> to the framework. The way that output is gathered is still elusive to
> me, and I'm guessing that any commands run in-between setting verbose
> mode and when you stop testpmd might influence how output is
> generated. But in my experience so far, any statistics or information
> I need is gathered before packets are sent, and the need for verbose
> output in test cases is one after the other (i send a packet, look at
> verbose output, and then move onto the next packet).

You're right that in most cases it would come from the stop output,
but the output from that stop command has one other thing as well that
I would consider valuable which is statistics of packets handled by
ports specifically for the duration of the packet forwarding you are
stopping. It is also true that sending other testpmd commands while
verbose output is being sent will change what is collected, so I
didn't want to tie the method specifically to the stop command since
if you did a start command then checked port statistics for example,
it would consume all of the verbose output up until the command to
check port statistics.

For the reason stated above I think it actually might make sense to
make it so that every testpmd method (including ones that currently
return dataclasses) return their original, unmodified collected output
from the testpmd shell as well. Things like port stats can return both
in a tuple potentially. This way if there is asynchronous output like
there is with verbose output other commands don't completely remove
it.

> <snip>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 13:34   ` [PATCH v2 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-07-30 15:41     ` Nicholas Pratte
@ 2024-07-30 21:33     ` Jeremy Spewock
  2024-08-01  8:43       ` Luca Vizzarro
  2024-08-01  8:41     ` Luca Vizzarro
  2 siblings, 1 reply; 27+ messages in thread
From: Jeremy Spewock @ 2024-07-30 21:33 UTC (permalink / raw)
  To: yoan.picchi, probb, paul.szczepanek, npratte, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage
  Cc: dev

On Tue, Jul 30, 2024 at 9:37 AM <jspewock@iol.unh.edu> wrote:
<snip>
>
>  class TestPmdDevice:
> @@ -577,6 +577,128 @@ class TestPmdPortStats(TextParser):
>      tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
>
>
> +class VerboseOLFlag(Flag):
> +    """Flag representing the OL flags of a packet from Testpmd verbose output."""
> +
> +    #:
> +    RTE_MBUF_F_RX_RSS_HASH = auto()
> +
> +    #:
> +    RTE_MBUF_F_RX_L4_CKSUM_GOOD = auto()
> +    #:
> +    RTE_MBUF_F_RX_L4_CKSUM_BAD = auto()
> +    #:
> +    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = auto()
> +
> +    #:
> +    RTE_MBUF_F_RX_IP_CKSUM_GOOD = auto()
> +    #:
> +    RTE_MBUF_F_RX_IP_CKSUM_BAD = auto()
> +    #:
> +    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = auto()
> +
> +    #:
> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = auto()
> +    #:
> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = auto()
> +    #:
> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = auto()
> +

After reading more of the API and using this patch to write a test
suite, I believe there is more expansion of these OL flags that should
take place. For starters, there are the Tx OL flags that, while not
seeming to be very useful for the current test suites we are writing,
wouldn't hurt to also include as they seem to be fairly different.
Additionally, there are some other less common RX OL flags that should
be included here just to cover all options. I will work on adding
these into the next version.

<snip>
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 13:34   ` [PATCH v2 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-07-30 15:41     ` Nicholas Pratte
  2024-07-30 21:33     ` Jeremy Spewock
@ 2024-08-01  8:41     ` Luca Vizzarro
  2024-08-02 13:35       ` Jeremy Spewock
  2 siblings, 1 reply; 27+ messages in thread
From: Luca Vizzarro @ 2024-08-01  8:41 UTC (permalink / raw)
  To: jspewock, yoan.picchi, probb, paul.szczepanek, npratte, thomas,
	Honnappa.Nagarahalli, juraj.linkes, wathsala.vithanage
  Cc: dev

Great work Jeremy! Just a couple of minor passable improvement points.

On 30/07/2024 14:34, jspewock@iol.unh.edu wrote:

> +@dataclass
> +class TestPmdVerbosePacket(TextParser):
> +    """Packet information provided by verbose output in Testpmd.
> +
> +    The "receive/sent queue" information is not included in this dataclass because this value is
> +    captured on the outer layer of input found in :class:`TestPmdVerboseOutput`.
> +    """
> +
> +    #:
> +    src_mac: str = field(metadata=TextParser.find(r"src=({})".format(REGEX_FOR_MAC_ADDRESS)))
Just a(n optional) nit: TextParser.find(f"src=({REGEX_FOR_MAC_ADDRESS})")
The raw string is only needed to prevent escaping, which we don't do here.
> +    #:
> +    dst_mac: str = field(metadata=TextParser.find(r"dst=({})".format(REGEX_FOR_MAC_ADDRESS)))
As above.
> +    #: Memory pool the packet was handled on.
> +    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
> +    #: Packet type in hex.
> +    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
> +    #:

<snip>

> +    @staticmethod
> +    def extract_verbose_output(output: str) -> list[TestPmdVerboseOutput]:
> +        """Extract the verbose information present in given testpmd output.
> +
> +        This method extracts sections of verbose output that begin with the line
> +        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
> +
> +        Args:
> +            output: Testpmd output that contains verbose information
> +
> +        Returns:
> +            List of parsed packet information gathered from verbose information in `output`.
> +        """
> +        iter = re.finditer(r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+|$))", output, re.S)

How about using a regex that matches what you described? ;) Keeping re.S:

    (port \d+/queue \d+.+?ol_flags: [\w ]+)

Would spare you from using complex lookahead constructs and 4.6x less 
steps. Maybe it doesn't work with every scenario? Looks like it works 
well with the sample output I have. Let me know if it works for you.

Best,
Luca


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 21:33     ` Jeremy Spewock
@ 2024-08-01  8:43       ` Luca Vizzarro
  2024-08-02 13:40         ` Jeremy Spewock
  0 siblings, 1 reply; 27+ messages in thread
From: Luca Vizzarro @ 2024-08-01  8:43 UTC (permalink / raw)
  To: Jeremy Spewock, yoan.picchi, probb, paul.szczepanek, npratte,
	thomas, Honnappa.Nagarahalli, juraj.linkes, wathsala.vithanage
  Cc: dev

On 30/07/2024 22:33, Jeremy Spewock wrote:
> On Tue, Jul 30, 2024 at 9:37 AM <jspewock@iol.unh.edu> wrote:
> <snip>
>> +class VerboseOLFlag(Flag):
>> +    """Flag representing the OL flags of a packet from Testpmd verbose output."""
>> +
>> +    #:
>> +    RTE_MBUF_F_RX_RSS_HASH = auto()
>> +
>> +    #:
>> +    RTE_MBUF_F_RX_L4_CKSUM_GOOD = auto()
>> +    #:
>> +    RTE_MBUF_F_RX_L4_CKSUM_BAD = auto()
>> +    #:
>> +    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = auto()
>> +
>> +    #:
>> +    RTE_MBUF_F_RX_IP_CKSUM_GOOD = auto()
>> +    #:
>> +    RTE_MBUF_F_RX_IP_CKSUM_BAD = auto()
>> +    #:
>> +    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = auto()
>> +
>> +    #:
>> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = auto()
>> +    #:
>> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = auto()
>> +    #:
>> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = auto()
>> +
> After reading more of the API and using this patch to write a test
> suite, I believe there is more expansion of these OL flags that should
> take place. For starters, there are the Tx OL flags that, while not
> seeming to be very useful for the current test suites we are writing,
> wouldn't hurt to also include as they seem to be fairly different.
> Additionally, there are some other less common RX OL flags that should
> be included here just to cover all options. I will work on adding
> these into the next version.

If you wanted to cover even more, hw_ptype and sw_ptype look like they 
could use a data structure. I reckon a flag like the above would also work.


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-08-01  8:41     ` Luca Vizzarro
@ 2024-08-02 13:35       ` Jeremy Spewock
  0 siblings, 0 replies; 27+ messages in thread
From: Jeremy Spewock @ 2024-08-02 13:35 UTC (permalink / raw)
  To: Luca Vizzarro
  Cc: yoan.picchi, probb, paul.szczepanek, npratte, thomas,
	Honnappa.Nagarahalli, juraj.linkes, wathsala.vithanage, dev

On Thu, Aug 1, 2024 at 4:41 AM Luca Vizzarro <Luca.Vizzarro@arm.com> wrote:
>
> Great work Jeremy! Just a couple of minor passable improvement points.
>
> On 30/07/2024 14:34, jspewock@iol.unh.edu wrote:
>
> > +@dataclass
> > +class TestPmdVerbosePacket(TextParser):
> > +    """Packet information provided by verbose output in Testpmd.
> > +
> > +    The "receive/sent queue" information is not included in this dataclass because this value is
> > +    captured on the outer layer of input found in :class:`TestPmdVerboseOutput`.
> > +    """
> > +
> > +    #:
> > +    src_mac: str = field(metadata=TextParser.find(r"src=({})".format(REGEX_FOR_MAC_ADDRESS)))
> Just a(n optional) nit: TextParser.find(f"src=({REGEX_FOR_MAC_ADDRESS})")
> The raw string is only needed to prevent escaping, which we don't do here.

Ack. I really just left it this way because it also adjusts
highlighting in some IDEs, but there isn't much to see here anyway.

> > +    #:
> > +    dst_mac: str = field(metadata=TextParser.find(r"dst=({})".format(REGEX_FOR_MAC_ADDRESS)))
> As above.

Ack.

> > +    #: Memory pool the packet was handled on.
> > +    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
> > +    #: Packet type in hex.
> > +    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
> > +    #:
>
> <snip>
>
> > +    @staticmethod
> > +    def extract_verbose_output(output: str) -> list[TestPmdVerboseOutput]:
> > +        """Extract the verbose information present in given testpmd output.
> > +
> > +        This method extracts sections of verbose output that begin with the line
> > +        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
> > +
> > +        Args:
> > +            output: Testpmd output that contains verbose information
> > +
> > +        Returns:
> > +            List of parsed packet information gathered from verbose information in `output`.
> > +        """
> > +        iter = re.finditer(r"(port \d+/queue \d+:.*?(?=port \d+/queue \d+|$))", output, re.S)
>
> How about using a regex that matches what you described? ;) Keeping re.S:
>
>     (port \d+/queue \d+.+?ol_flags: [\w ]+)
>
> Would spare you from using complex lookahead constructs and 4.6x less
> steps. Maybe it doesn't work with every scenario? Looks like it works
> well with the sample output I have. Let me know if it works for you.
>

I tried using something like this actually which is probably why the
docstring reflects that type of language, but I didn't use it because
it doesn't match one specific case. I'm not sure how common it is, and
I haven't seen it happen in my recent testing, but since the verbose
output specifically states that it sent/received X number of packets,
I presume there is a case where that number will be more than 1, and
there will be more than one set of packet information after that first
line. I think I've seen it happen in the past, but I couldn't recreate
it in testing.

For context to this next section, if it wasn't clear, I consider the
`port \d+/queue \d+` line to be the header line and the start of a
"block" of verbose output.

Basically though the problem with this is that if there are more than
one packet under that header line, the lazy approach will only consume
up until the ol_flags of the first packet of a block, and the greedy
approach consumes everything until the last packet of the entire
output. You could use the lazy approach with the next port/queue line
as your delimiter, but then the opening line of the next block of
output is included in the previous block's group. The only way I could
find to get around this and go with the idea of "take everything from
the start of this group until another group starts" but without
capturing the opening of the next block was a look ahead. Again
though, I definitely don't love the regex that I wrote and would love
to find a better alternative.

> Best,
> Luca
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-08-01  8:43       ` Luca Vizzarro
@ 2024-08-02 13:40         ` Jeremy Spewock
  0 siblings, 0 replies; 27+ messages in thread
From: Jeremy Spewock @ 2024-08-02 13:40 UTC (permalink / raw)
  To: Luca Vizzarro
  Cc: yoan.picchi, probb, paul.szczepanek, npratte, thomas,
	Honnappa.Nagarahalli, juraj.linkes, wathsala.vithanage, dev

On Thu, Aug 1, 2024 at 4:43 AM Luca Vizzarro <Luca.Vizzarro@arm.com> wrote:
>
> On 30/07/2024 22:33, Jeremy Spewock wrote:
> > On Tue, Jul 30, 2024 at 9:37 AM <jspewock@iol.unh.edu> wrote:
> > <snip>
> >> +class VerboseOLFlag(Flag):
> >> +    """Flag representing the OL flags of a packet from Testpmd verbose output."""
> >> +
> >> +    #:
> >> +    RTE_MBUF_F_RX_RSS_HASH = auto()
> >> +
> >> +    #:
> >> +    RTE_MBUF_F_RX_L4_CKSUM_GOOD = auto()
> >> +    #:
> >> +    RTE_MBUF_F_RX_L4_CKSUM_BAD = auto()
> >> +    #:
> >> +    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = auto()
> >> +
> >> +    #:
> >> +    RTE_MBUF_F_RX_IP_CKSUM_GOOD = auto()
> >> +    #:
> >> +    RTE_MBUF_F_RX_IP_CKSUM_BAD = auto()
> >> +    #:
> >> +    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = auto()
> >> +
> >> +    #:
> >> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = auto()
> >> +    #:
> >> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = auto()
> >> +    #:
> >> +    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = auto()
> >> +
> > After reading more of the API and using this patch to write a test
> > suite, I believe there is more expansion of these OL flags that should
> > take place. For starters, there are the Tx OL flags that, while not
> > seeming to be very useful for the current test suites we are writing,
> > wouldn't hurt to also include as they seem to be fairly different.
> > Additionally, there are some other less common RX OL flags that should
> > be included here just to cover all options. I will work on adding
> > these into the next version.
>
> If you wanted to cover even more, hw_ptype and sw_ptype look like they
> could use a data structure. I reckon a flag like the above would also work.

Ack, it's probably worth making those a little more structured as well.

>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-07-30 21:30       ` Jeremy Spewock
@ 2024-08-02 14:54         ` Nicholas Pratte
  2024-08-02 17:38           ` Jeremy Spewock
  0 siblings, 1 reply; 27+ messages in thread
From: Nicholas Pratte @ 2024-08-02 14:54 UTC (permalink / raw)
  To: Jeremy Spewock
  Cc: yoan.picchi, probb, paul.szczepanek, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage, dev

<snip>
> You're right that in most cases it would come from the stop output,
> but the output from that stop command has one other thing as well that
> I would consider valuable which is statistics of packets handled by
> ports specifically for the duration of the packet forwarding you are
> stopping. It is also true that sending other testpmd commands while
> verbose output is being sent will change what is collected, so I
> didn't want to tie the method specifically to the stop command since
> if you did a start command then checked port statistics for example,
> it would consume all of the verbose output up until the command to
> check port statistics.
>
> For the reason stated above I think it actually might make sense to
> make it so that every testpmd method (including ones that currently
> return dataclasses) return their original, unmodified collected output
> from the testpmd shell as well. Things like port stats can return both
> in a tuple potentially. This way if there is asynchronous output like
> there is with verbose output other commands don't completely remove
> it.

I agree! I think giving each testpmd method its own output would add
more consistency. An idea I had floating around that kind of relates
to your suggestion above was introducing some instance variables that
could enable the testpmd shell object to be smart enough to
automatically scan, and keep a record of, any verbose output that
comes out across any command run. The TestPMDShell class could track
whether verbose mode is on or not, and if True, run additional logic
to scan for verbose output and add it to a data structure for access
every time a command is run. Then users, from the perspective of
writing a test suite, could do something like 'output in
testpmd.verbose_output', where verbose_output is an instance data
structure of the TestPMDShell. This might be overcomplicated to
implement, but it was an idea I had that might make using verbose mode
more streamlined. What are your thoughts?

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-08-02 14:54         ` Nicholas Pratte
@ 2024-08-02 17:38           ` Jeremy Spewock
  2024-08-05 13:20             ` Nicholas Pratte
  0 siblings, 1 reply; 27+ messages in thread
From: Jeremy Spewock @ 2024-08-02 17:38 UTC (permalink / raw)
  To: Nicholas Pratte
  Cc: yoan.picchi, probb, paul.szczepanek, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage, dev

On Fri, Aug 2, 2024 at 10:54 AM Nicholas Pratte <npratte@iol.unh.edu> wrote:
>
> <snip>
> > You're right that in most cases it would come from the stop output,
> > but the output from that stop command has one other thing as well that
> > I would consider valuable which is statistics of packets handled by
> > ports specifically for the duration of the packet forwarding you are
> > stopping. It is also true that sending other testpmd commands while
> > verbose output is being sent will change what is collected, so I
> > didn't want to tie the method specifically to the stop command since
> > if you did a start command then checked port statistics for example,
> > it would consume all of the verbose output up until the command to
> > check port statistics.
> >
> > For the reason stated above I think it actually might make sense to
> > make it so that every testpmd method (including ones that currently
> > return dataclasses) return their original, unmodified collected output
> > from the testpmd shell as well. Things like port stats can return both
> > in a tuple potentially. This way if there is asynchronous output like
> > there is with verbose output other commands don't completely remove
> > it.
>
> I agree! I think giving each testpmd method its own output would add
> more consistency. An idea I had floating around that kind of relates
> to your suggestion above was introducing some instance variables that
> could enable the testpmd shell object to be smart enough to
> automatically scan, and keep a record of, any verbose output that
> comes out across any command run. The TestPMDShell class could track
> whether verbose mode is on or not, and if True, run additional logic
> to scan for verbose output and add it to a data structure for access
> every time a command is run. Then users, from the perspective of
> writing a test suite, could do something like 'output in
> testpmd.verbose_output', where verbose_output is an instance data
> structure of the TestPMDShell. This might be overcomplicated to
> implement, but it was an idea I had that might make using verbose mode
> more streamlined. What are your thoughts?

I like the sound of this idea a lot actually since it would remove the
chance of the output just completely being thrown away. In my own test
suite I managed to dance around this by strategically placing my
testpmd commands, but this could save people some headache in the
future. I feel like this wouldn't be something overly complicated to
implement either, all we would have to do is extend the send_command
method in the TestpmdShell class and check a boolean for if verbose is
on, extract this output. If/how to clear this list would be something
to think about, but I would say that, in general, the idea of making
sure we don't lose information is something that I'm all for.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/1] dts: add text parser for testpmd verbose output
  2024-08-02 17:38           ` Jeremy Spewock
@ 2024-08-05 13:20             ` Nicholas Pratte
  0 siblings, 0 replies; 27+ messages in thread
From: Nicholas Pratte @ 2024-08-05 13:20 UTC (permalink / raw)
  To: Jeremy Spewock
  Cc: yoan.picchi, probb, paul.szczepanek, thomas,
	Honnappa.Nagarahalli, Luca.Vizzarro, juraj.linkes,
	wathsala.vithanage, dev

> I like the sound of this idea a lot actually since it would remove the
> chance of the output just completely being thrown away. In my own test
> suite I managed to dance around this by strategically placing my
> testpmd commands, but this could save people some headache in the
> future. I feel like this wouldn't be something overly complicated to
> implement either, all we would have to do is extend the send_command
> method in the TestpmdShell class and check a boolean for if verbose is
> on, extract this output. If/how to clear this list would be something
> to think about, but I would say that, in general, the idea of making
> sure we don't lose information is something that I'm all for.

That's a good point that you could just modify the send_command
method. In my head I was thinking that we'd have to modify each
individual method! Totally forget that all those testpmd methods I was
thinking about stem from send_command().

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 0/1] dts: testpmd verbose parser
  2024-07-29 20:39 [PATCH v1 0/1] dts: testpmd verbose parser jspewock
  2024-07-29 20:39 ` [PATCH v1 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-07-30 13:34 ` [PATCH v2 0/1] dts: testpmd verbose parser jspewock
@ 2024-08-08 20:36 ` jspewock
  2024-08-08 20:36   ` [PATCH v3 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-09-18 16:34 ` [PATCH v4 0/1] dts: testpmd verbose parser jspewock
  2024-09-18 17:05 ` [PATCH v5 0/1] dts: testpmd verbose parser jspewock
  4 siblings, 1 reply; 27+ messages in thread
From: jspewock @ 2024-08-08 20:36 UTC (permalink / raw)
  To: thomas, yoan.picchi, paul.szczepanek, Honnappa.Nagarahalli,
	probb, wathsala.vithanage, Luca.Vizzarro, juraj.linkes, npratte,
	alex.chapman
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

I have not yet explored the idea of caching verbose output from all
commands rather than simply parsing whatever output is passed into the
method, but I wanted to get what I have currently out in the meantime.

v3:
 * Add more OL flags to include everything listed in rte_mbuf_core.h
 * Address comments in the previous version regarding formatting and
   adding a new class for packet types in verbose output
 * Changed the structure of verbose output gathering so that rather
   a block of verbose output that contains a list of packets in the
   burst it is just simply a list of packet information. This is done
   by taking the header information from the start of the burst and
   caching it to add to all packet info.


Jeremy Spewock (1):
  dts: add text parser for testpmd verbose output

 dts/framework/parser.py                       |  30 ++
 dts/framework/remote_session/testpmd_shell.py | 405 +++++++++++++++++-
 dts/framework/utils.py                        |   1 +
 3 files changed, 434 insertions(+), 2 deletions(-)

-- 
2.45.2


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 1/1] dts: add text parser for testpmd verbose output
  2024-08-08 20:36 ` [PATCH v3 0/1] dts: testpmd verbose parser jspewock
@ 2024-08-08 20:36   ` jspewock
  2024-08-08 21:49     ` Jeremy Spewock
  2024-09-09 11:44     ` Juraj Linkeš
  0 siblings, 2 replies; 27+ messages in thread
From: jspewock @ 2024-08-08 20:36 UTC (permalink / raw)
  To: thomas, yoan.picchi, paul.szczepanek, Honnappa.Nagarahalli,
	probb, wathsala.vithanage, Luca.Vizzarro, juraj.linkes, npratte,
	alex.chapman
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

Multiple test suites from the old DTS framework rely on being able to
consume and interpret the verbose output of testpmd. The new framework
doesn't have an elegant way for handling the verbose output, but test
suites are starting to be written that rely on it. This patch creates a
TextParser class that can be used to extract the verbose information
from any testpmd output and also adjusts the `stop` method of the shell
to return all output that it collected.

Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
---
 dts/framework/parser.py                       |  30 ++
 dts/framework/remote_session/testpmd_shell.py | 405 +++++++++++++++++-
 dts/framework/utils.py                        |   1 +
 3 files changed, 434 insertions(+), 2 deletions(-)

diff --git a/dts/framework/parser.py b/dts/framework/parser.py
index 741dfff821..0b39025a48 100644
--- a/dts/framework/parser.py
+++ b/dts/framework/parser.py
@@ -160,6 +160,36 @@ def _find(text: str) -> Any:
 
         return ParserFn(TextParser_fn=_find)
 
+    @staticmethod
+    def find_all(
+        pattern: str | re.Pattern[str],
+        flags: re.RegexFlag = re.RegexFlag(0),
+    ) -> ParserFn:
+        """Makes a parser function that finds all of the regular expression matches in the text.
+
+        If there are no matches found in the text than None will be returned, otherwise a list
+        containing all matches will be returned. Patterns that contain multiple groups will pack
+        the matches for each group into a tuple.
+
+        Args:
+            pattern: The regular expression pattern.
+            flags: The regular expression flags. Ignored if the given pattern is already compiled.
+
+        Returns:
+            A :class:`ParserFn` that can be used as metadata for a dataclass field.
+        """
+        if isinstance(pattern, str):
+            pattern = re.compile(pattern, flags)
+
+        def _find_all(text: str) -> list[str] | None:
+            m = pattern.findall(text)
+            if len(m) == 0:
+                return None
+
+            return m
+
+        return ParserFn(TextParser_fn=_find_all)
+
     @staticmethod
     def find_int(
         pattern: str | re.Pattern[str],
diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
index 43e9f56517..7d0b5a374c 100644
--- a/dts/framework/remote_session/testpmd_shell.py
+++ b/dts/framework/remote_session/testpmd_shell.py
@@ -31,7 +31,7 @@
 from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
 from framework.testbed_model.sut_node import SutNode
-from framework.utils import StrEnum
+from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
 
 
 class TestPmdDevice:
@@ -577,6 +577,377 @@ class TestPmdPortStats(TextParser):
     tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
 
 
+class OLFlag(Flag):
+    """Flag representing the Packet Offload Features Flags in DPDK.
+
+    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK.
+    """
+
+    # RX flags
+    #:
+    RTE_MBUF_F_RX_RSS_HASH = auto()
+
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = auto()
+    #:
+    RTE_MBUF_F_RX_L4_CKSUM_NONE = auto()
+
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = auto()
+    #:
+    RTE_MBUF_F_RX_IP_CKSUM_NONE = auto()
+
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_INVALID = auto()
+
+    #:
+    RTE_MBUF_F_RX_VLAN = auto()
+    #:
+    RTE_MBUF_F_RX_FDIR = auto()
+    #:
+    RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD = auto()
+    #:
+    RTE_MBUF_F_RX_VLAN_STRIPPED = auto()
+    #: RX IEEE1588 L2 Ethernet PT Packet.
+    RTE_MBUF_F_RX_IEEE1588_PTP = auto()
+    #: RX IEEE1588 L2/L4 timestamped packet.
+    RTE_MBUF_F_RX_IEEE1588_TMST = auto()
+    #: FD id reported if FDIR match.
+    RTE_MBUF_F_RX_FDIR_ID = auto()
+    #: Flexible bytes reported if FDIR match.
+    RTE_MBUF_F_RX_FDIR_FLX = auto()
+    #:
+    RTE_MBUF_F_RX_QINQ_STRIPPED = auto()
+    #:
+    RTE_MBUF_F_RX_LRO = auto()
+    #:
+    RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED = auto()
+    #:
+    RTE_MBUF_F_RX_QINQ = auto()
+
+    # TX flags
+    #:
+    RTE_MBUF_F_TX_OUTER_UDP_CKSUM = auto()
+    #:
+    RTE_MBUF_F_TX_UDP_SEG = auto()
+    #:
+    RTE_MBUF_F_TX_SEC_OFFLOAD = auto()
+    #:
+    RTE_MBUF_F_TX_MACSEC = auto()
+
+    #:
+    RTE_MBUF_F_TX_TUNNEL_VXLAN = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GRE = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_IPIP = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GENEVE = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_MPLSINUDP = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GTP = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_ESP = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_IP = auto()
+    #:
+    RTE_MBUF_F_TX_TUNNEL_UDP = auto()
+
+    #:
+    RTE_MBUF_F_TX_QINQ = auto()
+    #:
+    RTE_MBUF_F_TX_TCP_SEG = auto()
+    #: TX IEEE1588 packet to timestamp.
+    RTE_MBUF_F_TX_IEEE1588_TMST = auto()
+
+    #:
+    RTE_MBUF_F_TX_L4_NO_CKSUM = auto()
+    #:
+    RTE_MBUF_F_TX_TCP_CKSUM = auto()
+    #:
+    RTE_MBUF_F_TX_SCTP_CKSUM = auto()
+    #:
+    RTE_MBUF_F_TX_UDP_CKSUM = auto()
+    #:
+    RTE_MBUF_F_TX_L4_MASK = auto()
+    #:
+    RTE_MBUF_F_TX_IP_CKSUM = auto()
+    #:
+    RTE_MBUF_F_TX_OUTER_IP_CKSUM = auto()
+
+    #:
+    RTE_MBUF_F_TX_IPV4 = auto()
+    #:
+    RTE_MBUF_F_TX_IPV6 = auto()
+    #:
+    RTE_MBUF_F_TX_VLAN = auto()
+    #:
+    RTE_MBUF_F_TX_OUTER_IPV4 = auto()
+    #:
+    RTE_MBUF_F_TX_OUTER_IPV6 = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing ol_flag values.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in arr:
+            if name in cls.__members__:
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls) -> ParserFn:
+        """Makes a parser function.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+class RtePTypes(Flag):
+    """Flag representing possible packet types in DPDK verbose output."""
+
+    # L2
+    #:
+    L2_ETHER = auto()
+    #:
+    L2_ETHER_TIMESYNC = auto()
+    #:
+    L2_ETHER_ARP = auto()
+    #:
+    L2_ETHER_LLDP = auto()
+    #:
+    L2_ETHER_NSH = auto()
+    #:
+    L2_ETHER_VLAN = auto()
+    #:
+    L2_ETHER_QINQ = auto()
+    #:
+    L2_ETHER_PPPOE = auto()
+    #:
+    L2_ETHER_FCOE = auto()
+    #:
+    L2_ETHER_MPLS = auto()
+    #:
+    L2_UNKNOWN = auto()
+
+    # L3
+    #:
+    L3_IPV4 = auto()
+    #:
+    L3_IPV4_EXT = auto()
+    #:
+    L3_IPV6 = auto()
+    #:
+    L3_IPV4_EXT_UNKNOWN = auto()
+    #:
+    L3_IPV6_EXT = auto()
+    #:
+    L3_IPV6_EXT_UNKNOWN = auto()
+    #:
+    L3_UNKNOWN = auto()
+
+    # L4
+    #:
+    L4_TCP = auto()
+    #:
+    L4_UDP = auto()
+    #:
+    L4_FRAG = auto()
+    #:
+    L4_SCTP = auto()
+    #:
+    L4_ICMP = auto()
+    #:
+    L4_NONFRAG = auto()
+    #:
+    L4_IGMP = auto()
+    #:
+    L4_UNKNOWN = auto()
+
+    # Tunnel
+    #:
+    TUNNEL_IP = auto()
+    #:
+    TUNNEL_GRE = auto()
+    #:
+    TUNNEL_VXLAN = auto()
+    #:
+    TUNNEL_NVGRE = auto()
+    #:
+    TUNNEL_GENEVE = auto()
+    #:
+    TUNNEL_GRENAT = auto()
+    #:
+    TUNNEL_GTPC = auto()
+    #:
+    TUNNEL_GTPU = auto()
+    #:
+    TUNNEL_ESP = auto()
+    #:
+    TUNNEL_L2TP = auto()
+    #:
+    TUNNEL_VXLAN_GPE = auto()
+    #:
+    TUNNEL_MPLS_IN_UDP = auto()
+    #:
+    TUNNEL_MPLS_IN_GRE = auto()
+    #:
+    TUNNEL_UNKNOWN = auto()
+
+    # Inner L2
+    #:
+    INNER_L2_ETHER = auto()
+    #:
+    INNER_L2_ETHER_VLAN = auto()
+    #:
+    INNER_L2_ETHER_QINQ = auto()
+    #:
+    INNER_L2_UNKNOWN = auto()
+
+    # Inner L3
+    #:
+    INNER_L3_IPV4 = auto()
+    #:
+    INNER_L3_IPV4_EXT = auto()
+    #:
+    INNER_L3_IPV6 = auto()
+    #:
+    INNER_L3_IPV4_EXT_UNKNOWN = auto()
+    #:
+    INNER_L3_IPV6_EXT = auto()
+    #:
+    INNER_L3_IPV6_EXT_UNKNOWN = auto()
+    #:
+    INNER_L3_UNKNOWN = auto()
+
+    # Inner L4
+    #:
+    INNER_L4_TCP = auto()
+    #:
+    INNER_L4_UDP = auto()
+    #:
+    INNER_L4_FRAG = auto()
+    #:
+    INNER_L4_SCTP = auto()
+    #:
+    INNER_L4_ICMP = auto()
+    #:
+    INNER_L4_NONFRAG = auto()
+    #:
+    INNER_L4_UNKNOWN = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing ol_flag values.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in arr:
+            if name in cls.__members__:
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls, hw: bool) -> ParserFn:
+        """Makes a parser function.
+
+        Args:
+            hw: Whether to make a parser for hardware ptypes or software ptypes. If :data:`True`
+                hardware ptypes will be collected, otherwise software pytpes will.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(f"{'hw' if hw else 'sw'} ptype: ([^-]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+@dataclass
+class TestPmdVerbosePacket(TextParser):
+    """Packet information provided by verbose output in Testpmd.
+
+    This dataclass expects that packet information be prepended with the starting line of packet
+    bursts. Specifically, the line that reads "port X/queue Y: sent/received Z packets".
+    """
+
+    #: ID of the port that handled the packet.
+    port_id: int = field(metadata=TextParser.find_int(r"port (\d+)/queue \d+"))
+    #: ID of the queue that handled the packet.
+    queue_id: int = field(metadata=TextParser.find_int(r"port \d+/queue (\d+)"))
+    #: Whether the packet was received or sent by the queue/port.
+    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
+    #:
+    src_mac: str = field(metadata=TextParser.find(f"src=({REGEX_FOR_MAC_ADDRESS})"))
+    #:
+    dst_mac: str = field(metadata=TextParser.find(f"dst=({REGEX_FOR_MAC_ADDRESS})"))
+    #: Memory pool the packet was handled on.
+    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
+    #: Packet type in hex.
+    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
+    #:
+    length: int = field(metadata=TextParser.find_int(r"length=(\d+)"))
+    #: Number of segments in the packet.
+    nb_segs: int = field(metadata=TextParser.find_int(r"nb_segs=(\d+)"))
+    #: Hardware packet type.
+    hw_ptype: RtePTypes = field(metadata=RtePTypes.make_parser(hw=True))
+    #: Software packet type.
+    sw_ptype: RtePTypes = field(metadata=RtePTypes.make_parser(hw=False))
+    #:
+    l2_len: int = field(metadata=TextParser.find_int(r"l2_len=(\d+)"))
+    #:
+    ol_flags: OLFlag = field(metadata=OLFlag.make_parser())
+    #: RSS has of the packet in hex.
+    rss_hash: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS hash=(0x[a-fA-F\d]+)")
+    )
+    #: RSS queue that handled the packet in hex.
+    rss_queue: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS queue=(0x[a-fA-F\d]+)")
+    )
+    #:
+    l3_len: int | None = field(default=None, metadata=TextParser.find_int(r"l3_len=(\d+)"))
+    #:
+    l4_len: int | None = field(default=None, metadata=TextParser.find_int(r"l4_len=(\d+)"))
+
+
 class TestPmdShell(DPDKShell):
     """Testpmd interactive shell.
 
@@ -645,7 +1016,7 @@ def start(self, verify: bool = True) -> None:
                         "Not all ports came up after starting packet forwarding in testpmd."
                     )
 
-    def stop(self, verify: bool = True) -> None:
+    def stop(self, verify: bool = True) -> str:
         """Stop packet forwarding.
 
         Args:
@@ -656,6 +1027,9 @@ def stop(self, verify: bool = True) -> None:
         Raises:
             InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
                 forwarding results in an error.
+
+        Returns:
+            Output gathered from sending the stop command.
         """
         stop_cmd_output = self.send_command("stop")
         if verify:
@@ -665,6 +1039,7 @@ 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.")
+        return stop_cmd_output
 
     def get_devices(self) -> list[TestPmdDevice]:
         """Get a list of device names that are known to testpmd.
@@ -806,6 +1181,32 @@ def show_port_stats(self, port_id: int) -> TestPmdPortStats:
 
         return TestPmdPortStats.parse(output)
 
+    @staticmethod
+    def extract_verbose_output(output: str) -> list[TestPmdVerbosePacket]:
+        """Extract the verbose information present in given testpmd output.
+
+        This method extracts sections of verbose output that begin with the line
+        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
+
+        Args:
+            output: Testpmd output that contains verbose information
+
+        Returns:
+            List of parsed packet information gathered from verbose information in `output`.
+        """
+        out: list[TestPmdVerbosePacket] = []
+        prev_header: str = ""
+        iter = re.finditer(
+            r"(?P<HEADER>(?:port \d+/queue \d+: received \d packets)?)\s*"
+            r"(?P<PACKET>src=[\w\s=:-]+?ol_flags: [\w ]+)",
+            output,
+        )
+        for match in iter:
+            if match.group("HEADER"):
+                prev_header = match.group("HEADER")
+            out.append(TestPmdVerbosePacket.parse(f"{prev_header}\n{match.group('PACKET')}"))
+        return out
+
     def _close(self) -> None:
         """Overrides :meth:`~.interactive_shell.close`."""
         self.stop()
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 6b5d5a805f..9c64cf497f 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -27,6 +27,7 @@
 from .exception import ConfigurationError
 
 REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
+REGEX_FOR_MAC_ADDRESS: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
 
 
 def expand_range(range_str: str) -> list[int]:
-- 
2.45.2


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 1/1] dts: add text parser for testpmd verbose output
  2024-08-08 20:36   ` [PATCH v3 1/1] dts: add text parser for testpmd verbose output jspewock
@ 2024-08-08 21:49     ` Jeremy Spewock
  2024-08-12 17:32       ` Nicholas Pratte
  2024-09-09 11:44     ` Juraj Linkeš
  1 sibling, 1 reply; 27+ messages in thread
From: Jeremy Spewock @ 2024-08-08 21:49 UTC (permalink / raw)
  To: thomas, yoan.picchi, paul.szczepanek, Honnappa.Nagarahalli,
	probb, wathsala.vithanage, Luca.Vizzarro, juraj.linkes, npratte,
	alex.chapman
  Cc: dev

On Thu, Aug 8, 2024 at 4:36 PM <jspewock@iol.unh.edu> wrote:
>
> From: Jeremy Spewock <jspewock@iol.unh.edu>
>
> Multiple test suites from the old DTS framework rely on being able to
> consume and interpret the verbose output of testpmd. The new framework
> doesn't have an elegant way for handling the verbose output, but test
> suites are starting to be written that rely on it. This patch creates a
> TextParser class that can be used to extract the verbose information
> from any testpmd output and also adjusts the `stop` method of the shell
> to return all output that it collected.
>
> Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
> ---
>  dts/framework/parser.py                       |  30 ++
>  dts/framework/remote_session/testpmd_shell.py | 405 +++++++++++++++++-
>  dts/framework/utils.py                        |   1 +
>  3 files changed, 434 insertions(+), 2 deletions(-)
>
> diff --git a/dts/framework/parser.py b/dts/framework/parser.py
> index 741dfff821..0b39025a48 100644
> --- a/dts/framework/parser.py
> +++ b/dts/framework/parser.py
> @@ -160,6 +160,36 @@ def _find(text: str) -> Any:
>
>          return ParserFn(TextParser_fn=_find)
>
> +    @staticmethod
> +    def find_all(

I just realized that I forgot to take this method out since it is no
longer used in this patch now that I removed the idea of a block of
output that contains a burst of packets. The method could still be
useful in the future, but it isn't used anywhere now, so I can remove
it in the next version if no one sees a use for it. I'm also open to
leaving it there for the "just in case" it is needed in the future.
What do you all think?

> +        pattern: str | re.Pattern[str],
> +        flags: re.RegexFlag = re.RegexFlag(0),
> +    ) -> ParserFn:
> +        """Makes a parser function that finds all of the regular expression matches in the text.
> +
> +        If there are no matches found in the text than None will be returned, otherwise a list
> +        containing all matches will be returned. Patterns that contain multiple groups will pack
> +        the matches for each group into a tuple.
> +
> +        Args:
> +            pattern: The regular expression pattern.
> +            flags: The regular expression flags. Ignored if the given pattern is already compiled.
> +
> +        Returns:
> +            A :class:`ParserFn` that can be used as metadata for a dataclass field.
> +        """
> +        if isinstance(pattern, str):
> +            pattern = re.compile(pattern, flags)
> +
> +        def _find_all(text: str) -> list[str] | None:
> +            m = pattern.findall(text)
> +            if len(m) == 0:
> +                return None
> +
> +            return m
> +
> +        return ParserFn(TextParser_fn=_find_all)
> +
<snip>
> 2.45.2
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 1/1] dts: add text parser for testpmd verbose output
  2024-08-08 21:49     ` Jeremy Spewock
@ 2024-08-12 17:32       ` Nicholas Pratte
  0 siblings, 0 replies; 27+ messages in thread
From: Nicholas Pratte @ 2024-08-12 17:32 UTC (permalink / raw)
  To: Jeremy Spewock
  Cc: thomas, yoan.picchi, paul.szczepanek, Honnappa.Nagarahalli,
	probb, wathsala.vithanage, Luca.Vizzarro, juraj.linkes,
	alex.chapman, dev

Great work!

I think it'd be fine to just keep if you ask me, I could see this
being used in the future. I'm also looking at it from the perspective
of 'what if i would have to write this myself,' if it turns out that
we need it again for something later. It's easier to remove later if
it turns out we aren't using it, but it'd likely be more
time-consuming to remove it now and implement it again later,
considering that time has already been spent testing and building it.

On Thu, Aug 8, 2024 at 5:49 PM Jeremy Spewock <jspewock@iol.unh.edu> wrote:
>
> On Thu, Aug 8, 2024 at 4:36 PM <jspewock@iol.unh.edu> wrote:
> >
> > From: Jeremy Spewock <jspewock@iol.unh.edu>
> >
> > Multiple test suites from the old DTS framework rely on being able to
> > consume and interpret the verbose output of testpmd. The new framework
> > doesn't have an elegant way for handling the verbose output, but test
> > suites are starting to be written that rely on it. This patch creates a
> > TextParser class that can be used to extract the verbose information
> > from any testpmd output and also adjusts the `stop` method of the shell
> > to return all output that it collected.
> >
> > Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
> > ---
> >  dts/framework/parser.py                       |  30 ++
> >  dts/framework/remote_session/testpmd_shell.py | 405 +++++++++++++++++-
> >  dts/framework/utils.py                        |   1 +
> >  3 files changed, 434 insertions(+), 2 deletions(-)
> >
> > diff --git a/dts/framework/parser.py b/dts/framework/parser.py
> > index 741dfff821..0b39025a48 100644
> > --- a/dts/framework/parser.py
> > +++ b/dts/framework/parser.py
> > @@ -160,6 +160,36 @@ def _find(text: str) -> Any:
> >
> >          return ParserFn(TextParser_fn=_find)
> >
> > +    @staticmethod
> > +    def find_all(
>
> I just realized that I forgot to take this method out since it is no
> longer used in this patch now that I removed the idea of a block of
> output that contains a burst of packets. The method could still be
> useful in the future, but it isn't used anywhere now, so I can remove
> it in the next version if no one sees a use for it. I'm also open to
> leaving it there for the "just in case" it is needed in the future.
> What do you all think?
>
> > +        pattern: str | re.Pattern[str],
> > +        flags: re.RegexFlag = re.RegexFlag(0),
> > +    ) -> ParserFn:
> > +        """Makes a parser function that finds all of the regular expression matches in the text.
> > +
> > +        If there are no matches found in the text than None will be returned, otherwise a list
> > +        containing all matches will be returned. Patterns that contain multiple groups will pack
> > +        the matches for each group into a tuple.
> > +
> > +        Args:
> > +            pattern: The regular expression pattern.
> > +            flags: The regular expression flags. Ignored if the given pattern is already compiled.
> > +
> > +        Returns:
> > +            A :class:`ParserFn` that can be used as metadata for a dataclass field.
> > +        """
> > +        if isinstance(pattern, str):
> > +            pattern = re.compile(pattern, flags)
> > +
> > +        def _find_all(text: str) -> list[str] | None:
> > +            m = pattern.findall(text)
> > +            if len(m) == 0:
> > +                return None
> > +
> > +            return m
> > +
> > +        return ParserFn(TextParser_fn=_find_all)
> > +
> <snip>
> > 2.45.2
> >

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 1/1] dts: add text parser for testpmd verbose output
  2024-08-08 20:36   ` [PATCH v3 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-08-08 21:49     ` Jeremy Spewock
@ 2024-09-09 11:44     ` Juraj Linkeš
  2024-09-17 13:40       ` Jeremy Spewock
  1 sibling, 1 reply; 27+ messages in thread
From: Juraj Linkeš @ 2024-09-09 11:44 UTC (permalink / raw)
  To: jspewock, thomas, yoan.picchi, paul.szczepanek,
	Honnappa.Nagarahalli, probb, wathsala.vithanage, Luca.Vizzarro,
	npratte, alex.chapman
  Cc: dev


> diff --git a/dts/framework/parser.py b/dts/framework/parser.py
> index 741dfff821..0b39025a48 100644
> --- a/dts/framework/parser.py
> +++ b/dts/framework/parser.py
> @@ -160,6 +160,36 @@ def _find(text: str) -> Any:
>   
>           return ParserFn(TextParser_fn=_find)
>   
> +    @staticmethod
> +    def find_all(
> +        pattern: str | re.Pattern[str],
> +        flags: re.RegexFlag = re.RegexFlag(0),
> +    ) -> ParserFn:

I'd remove this if it's not used, the rule being let's not introduce 
unused code because it's not going to be maintained. We can always add 
it when needed.

> +        """Makes a parser function that finds all of the regular expression matches in the text.
> +
> +        If there are no matches found in the text than None will be returned, otherwise a list

then None, but maybe a comma would be better (found in the text, None 
will be returned)

> +        containing all matches will be returned. Patterns that contain multiple groups will pack
> +        the matches for each group into a tuple.
> +

> diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
> index 43e9f56517..7d0b5a374c 100644
> --- a/dts/framework/remote_session/testpmd_shell.py
> +++ b/dts/framework/remote_session/testpmd_shell.py
> @@ -31,7 +31,7 @@
>   from framework.settings import SETTINGS
>   from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
>   from framework.testbed_model.sut_node import SutNode
> -from framework.utils import StrEnum
> +from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
>   
>   
>   class TestPmdDevice:
> @@ -577,6 +577,377 @@ class TestPmdPortStats(TextParser):
>       tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
>   
>   
> +class OLFlag(Flag):

We should come up with a consistent naming scheme for the various 
offloads. In the capabilities patch, I've introduced 
RxOffloadCapability. I think we can use the full word Offload and we 
should also capture in the name what sort of offload it is. In this 
case, would PacketOffloadFlag be a good name?

> +    """Flag representing the Packet Offload Features Flags in DPDK.
> +
> +    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK.

I like the reference, let's also mention the name of the file 
rte_mbuf_core.h. Maybe we should add more references like these to other 
flags.

> +    """
> +
> +    # RX flags
> +    #:
> +    RTE_MBUF_F_RX_RSS_HASH = auto()
> +
> +    #:

I noticed the flags are not sorted the same way as in rte_mbuf_core.h. I 
think there's value in using the same flag values.

We could also add descriptions to the flag if there are some to be found 
in rte_mbuf_core.h.

> +
> +    # TX flags
> +    #:
> +    RTE_MBUF_F_TX_OUTER_UDP_CKSUM = auto()

Since there is a gap between RX and TX flags, you can just assign the 
actual value here (1 << 41) and the continue using auto().


> +    @classmethod
> +    def from_str_list(cls, arr: list[str]) -> Self:
> +        """Makes an instance from a list containing the flag members.
> +
> +        Args:
> +            arr: A list of strings containing ol_flag values.
> +
> +        Returns:
> +            A new instance of the flag.
> +        """
> +        flag = cls(0)
> +        for name in arr:
> +            if name in cls.__members__:

We could also do if cls[name] in cls. It's basically the same thing, but 
doesn't use the dunder method.

> +                flag |= cls[name]
> +        return flag
> +
> +    @classmethod
> +    def make_parser(cls) -> ParserFn:
> +        """Makes a parser function.
> +
> +        Returns:
> +            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
> +                parser function that makes an instance of this flag from text.
> +        """
> +        return TextParser.wrap(
> +            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
> +            cls.from_str_list,
> +        )
> +
> +
> +class RtePTypes(Flag):
> +    """Flag representing possible packet types in DPDK verbose output."""

Now this docstring doesn't reference from where these come from.

I found these in rte_mbuf_ptype.h, but from what I can tell, they're not 
actual flags, just regular numbers:
#define RTE_PTYPE_L2_ETHER                  0x00000001
#define RTE_PTYPE_L2_ETHER_TIMESYNC         0x00000002

etc., so we're basically converting that to flags. I think this is OK 
and we don't really need to concern ourselves with the actual values, 
just the order.


> +    @classmethod
> +    def from_str_list(cls, arr: list[str]) -> Self:
> +        """Makes an instance from a list containing the flag members.
> +
> +        Args:
> +            arr: A list of strings containing ol_flag values.

ol_flag looks like a copy-paste.

> +
> +        Returns:
> +            A new instance of the flag.
> +        """
> +        flag = cls(0)
> +        for name in arr:
> +            if name in cls.__members__:
> +                flag |= cls[name]
> +        return flag
> +
> +    @classmethod
> +    def make_parser(cls, hw: bool) -> ParserFn:
> +        """Makes a parser function.
> +
> +        Args:
> +            hw: Whether to make a parser for hardware ptypes or software ptypes. If :data:`True`

I think there should be a comma before hardware (on the next line).

> +                hardware ptypes will be collected, otherwise software pytpes will.
> +
> +        Returns:
> +            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
> +                parser function that makes an instance of this flag from text.
> +        """
> +        return TextParser.wrap(
> +            TextParser.wrap(TextParser.find(f"{'hw' if hw else 'sw'} ptype: ([^-]+)"), str.split),
> +            cls.from_str_list,
> +        )
> +
> +
> +@dataclass
> +class TestPmdVerbosePacket(TextParser):

> +    ol_flags: OLFlag = field(metadata=OLFlag.make_parser())
> +    #: RSS has of the packet in hex.

typo: hash


>   class TestPmdShell(DPDKShell):

> +    @staticmethod
> +    def extract_verbose_output(output: str) -> list[TestPmdVerbosePacket]:
> +        """Extract the verbose information present in given testpmd output.
> +
> +        This method extracts sections of verbose output that begin with the line
> +        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
> +
> +        Args:
> +            output: Testpmd output that contains verbose information
> +
> +        Returns:
> +            List of parsed packet information gathered from verbose information in `output`.
> +        """
> +        out: list[TestPmdVerbosePacket] = []
> +        prev_header: str = ""
> +        iter = re.finditer(
> +            r"(?P<HEADER>(?:port \d+/queue \d+: received \d packets)?)\s*"

Looks like sent packets won't be captured by this.


> diff --git a/dts/framework/utils.py b/dts/framework/utils.py
> index 6b5d5a805f..9c64cf497f 100644
> --- a/dts/framework/utils.py
> +++ b/dts/framework/utils.py
> @@ -27,6 +27,7 @@
>   from .exception import ConfigurationError
>   
>   REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
> +REGEX_FOR_MAC_ADDRESS: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"

Is this the only format that testpmd returns?



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 1/1] dts: add text parser for testpmd verbose output
  2024-09-09 11:44     ` Juraj Linkeš
@ 2024-09-17 13:40       ` Jeremy Spewock
  2024-09-18  8:09         ` Juraj Linkeš
  0 siblings, 1 reply; 27+ messages in thread
From: Jeremy Spewock @ 2024-09-17 13:40 UTC (permalink / raw)
  To: Juraj Linkeš
  Cc: thomas, yoan.picchi, paul.szczepanek, Honnappa.Nagarahalli,
	probb, wathsala.vithanage, Luca.Vizzarro, npratte, alex.chapman,
	dev

On Mon, Sep 9, 2024 at 7:44 AM Juraj Linkeš <juraj.linkes@pantheon.tech> wrote:
>
>
> > diff --git a/dts/framework/parser.py b/dts/framework/parser.py
> > index 741dfff821..0b39025a48 100644
> > --- a/dts/framework/parser.py
> > +++ b/dts/framework/parser.py
> > @@ -160,6 +160,36 @@ def _find(text: str) -> Any:
> >
> >           return ParserFn(TextParser_fn=_find)
> >
> > +    @staticmethod
> > +    def find_all(
> > +        pattern: str | re.Pattern[str],
> > +        flags: re.RegexFlag = re.RegexFlag(0),
> > +    ) -> ParserFn:
>
> I'd remove this if it's not used, the rule being let's not introduce
> unused code because it's not going to be maintained. We can always add
> it when needed.

Since submitting this I did actually find one use for it in the Rx/Tx
suite, but that one has yet to undergo review, so it could be the case
that people don't like that implementation.

>
> > +        """Makes a parser function that finds all of the regular expression matches in the text.
> > +
> > +        If there are no matches found in the text than None will be returned, otherwise a list
>
> then None, but maybe a comma would be better (found in the text, None
> will be returned)

Ack.

>
> > +        containing all matches will be returned. Patterns that contain multiple groups will pack
> > +        the matches for each group into a tuple.
> > +
>
> > diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
> > index 43e9f56517..7d0b5a374c 100644
> > --- a/dts/framework/remote_session/testpmd_shell.py
> > +++ b/dts/framework/remote_session/testpmd_shell.py
> > @@ -31,7 +31,7 @@
> >   from framework.settings import SETTINGS
> >   from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
> >   from framework.testbed_model.sut_node import SutNode
> > -from framework.utils import StrEnum
> > +from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
> >
> >
> >   class TestPmdDevice:
> > @@ -577,6 +577,377 @@ class TestPmdPortStats(TextParser):
> >       tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
> >
> >
> > +class OLFlag(Flag):
>
> We should come up with a consistent naming scheme for the various
> offloads. In the capabilities patch, I've introduced
> RxOffloadCapability. I think we can use the full word Offload and we
> should also capture in the name what sort of offload it is. In this
> case, would PacketOffloadFlag be a good name?

This is a good point, I was just naming according to what they called
it in the verbose output, but this probably should be more consistent.
I think that PacketOffloadFlag would make sense.

>
> > +    """Flag representing the Packet Offload Features Flags in DPDK.
> > +
> > +    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK.
>
> I like the reference, let's also mention the name of the file
> rte_mbuf_core.h. Maybe we should add more references like these to other
> flags.

Ack.

>
> > +    """
> > +
> > +    # RX flags
> > +    #:
> > +    RTE_MBUF_F_RX_RSS_HASH = auto()
> > +
> > +    #:
>
> I noticed the flags are not sorted the same way as in rte_mbuf_core.h. I
> think there's value in using the same flag values.
>
> We could also add descriptions to the flag if there are some to be found
> in rte_mbuf_core.h.

Yeah, I reordered some to try and keep consistent groupings between
things like checksums or what I thought made logical sense. I figured
that the order wouldn't matter all that much since the verbose output
just uses the flag names, but you're right, there could be some value
in keeping them the same. Especially if we try to write parsers for
the new verbose output modes that are being worked on in testpmd since
one of them is just a hexdump.

>
> > +
> > +    # TX flags
> > +    #:
> > +    RTE_MBUF_F_TX_OUTER_UDP_CKSUM = auto()
>
> Since there is a gap between RX and TX flags, you can just assign the
> actual value here (1 << 41) and the continue using auto().

Good point, I can make that change.

>
>
> > +    @classmethod
> > +    def from_str_list(cls, arr: list[str]) -> Self:
> > +        """Makes an instance from a list containing the flag members.
> > +
> > +        Args:
> > +            arr: A list of strings containing ol_flag values.
> > +
> > +        Returns:
> > +            A new instance of the flag.
> > +        """
> > +        flag = cls(0)
> > +        for name in arr:
> > +            if name in cls.__members__:
>
> We could also do if cls[name] in cls. It's basically the same thing, but
> doesn't use the dunder method.

Ack.

>
> > +                flag |= cls[name]
> > +        return flag
> > +
> > +    @classmethod
> > +    def make_parser(cls) -> ParserFn:
> > +        """Makes a parser function.
> > +
> > +        Returns:
> > +            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
> > +                parser function that makes an instance of this flag from text.
> > +        """
> > +        return TextParser.wrap(
> > +            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
> > +            cls.from_str_list,
> > +        )
> > +
> > +
> > +class RtePTypes(Flag):
> > +    """Flag representing possible packet types in DPDK verbose output."""
>
> Now this docstring doesn't reference from where these come from.
>
> I found these in rte_mbuf_ptype.h, but from what I can tell, they're not
> actual flags, just regular numbers:

Right, I did take them from there. Probably good to mention where
these come from as well.

> #define RTE_PTYPE_L2_ETHER                  0x00000001
> #define RTE_PTYPE_L2_ETHER_TIMESYNC         0x00000002
>
> etc., so we're basically converting that to flags. I think this is OK
> and we don't really need to concern ourselves with the actual values,
> just the order.
>

Ack.

>
> > +    @classmethod
> > +    def from_str_list(cls, arr: list[str]) -> Self:
> > +        """Makes an instance from a list containing the flag members.
> > +
> > +        Args:
> > +            arr: A list of strings containing ol_flag values.
>
> ol_flag looks like a copy-paste.

Oops, good catch!

>
> > +
> > +        Returns:
> > +            A new instance of the flag.
> > +        """
> > +        flag = cls(0)
> > +        for name in arr:
> > +            if name in cls.__members__:
> > +                flag |= cls[name]
> > +        return flag
> > +
> > +    @classmethod
> > +    def make_parser(cls, hw: bool) -> ParserFn:
> > +        """Makes a parser function.
> > +
> > +        Args:
> > +            hw: Whether to make a parser for hardware ptypes or software ptypes. If :data:`True`
>
> I think there should be a comma before hardware (on the next line).

Ack.

>
> > +                hardware ptypes will be collected, otherwise software pytpes will.
> > +
> > +        Returns:
> > +            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
> > +                parser function that makes an instance of this flag from text.
> > +        """
> > +        return TextParser.wrap(
> > +            TextParser.wrap(TextParser.find(f"{'hw' if hw else 'sw'} ptype: ([^-]+)"), str.split),
> > +            cls.from_str_list,
> > +        )
> > +
> > +
> > +@dataclass
> > +class TestPmdVerbosePacket(TextParser):
>
> > +    ol_flags: OLFlag = field(metadata=OLFlag.make_parser())
> > +    #: RSS has of the packet in hex.
>
> typo: hash

Ack.

>
>
> >   class TestPmdShell(DPDKShell):
>
> > +    @staticmethod
> > +    def extract_verbose_output(output: str) -> list[TestPmdVerbosePacket]:
> > +        """Extract the verbose information present in given testpmd output.
> > +
> > +        This method extracts sections of verbose output that begin with the line
> > +        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
> > +
> > +        Args:
> > +            output: Testpmd output that contains verbose information
> > +
> > +        Returns:
> > +            List of parsed packet information gathered from verbose information in `output`.
> > +        """
> > +        out: list[TestPmdVerbosePacket] = []
> > +        prev_header: str = ""
> > +        iter = re.finditer(
> > +            r"(?P<HEADER>(?:port \d+/queue \d+: received \d packets)?)\s*"
>
> Looks like sent packets won't be captured by this.

Right, I think I wrote this implementation for received first and then
made it more dynamic so it could support both but missed this. Good
catch!

>
>
> > diff --git a/dts/framework/utils.py b/dts/framework/utils.py
> > index 6b5d5a805f..9c64cf497f 100644
> > --- a/dts/framework/utils.py
> > +++ b/dts/framework/utils.py
> > @@ -27,6 +27,7 @@
> >   from .exception import ConfigurationError
> >
> >   REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
> > +REGEX_FOR_MAC_ADDRESS: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
>
> Is this the only format that testpmd returns?

I believe so, but because I'm not completely sure I can change this
regex to support other variants as well. The hyphen separated one is
easy enough that it might as well be included, the group of 4
separated by a dot might be a little more involved but I can probably
get it to work.

>
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 1/1] dts: add text parser for testpmd verbose output
  2024-09-17 13:40       ` Jeremy Spewock
@ 2024-09-18  8:09         ` Juraj Linkeš
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Linkeš @ 2024-09-18  8:09 UTC (permalink / raw)
  To: Jeremy Spewock
  Cc: thomas, yoan.picchi, paul.szczepanek, Honnappa.Nagarahalli,
	probb, wathsala.vithanage, Luca.Vizzarro, npratte, alex.chapman,
	dev



On 17. 9. 2024 15:40, Jeremy Spewock wrote:
> On Mon, Sep 9, 2024 at 7:44 AM Juraj Linkeš <juraj.linkes@pantheon.tech> wrote:
>>
>>
>>> diff --git a/dts/framework/parser.py b/dts/framework/parser.py
>>> index 741dfff821..0b39025a48 100644
>>> --- a/dts/framework/parser.py
>>> +++ b/dts/framework/parser.py
>>> @@ -160,6 +160,36 @@ def _find(text: str) -> Any:
>>>
>>>            return ParserFn(TextParser_fn=_find)
>>>
>>> +    @staticmethod
>>> +    def find_all(
>>> +        pattern: str | re.Pattern[str],
>>> +        flags: re.RegexFlag = re.RegexFlag(0),
>>> +    ) -> ParserFn:
>>
>> I'd remove this if it's not used, the rule being let's not introduce
>> unused code because it's not going to be maintained. We can always add
>> it when needed.
> 
> Since submitting this I did actually find one use for it in the Rx/Tx
> suite, but that one has yet to undergo review, so it could be the case
> that people don't like that implementation.
> 

Ok, we can (and probably should) add it in that test suite patchset if 
needed. The required infrastructure is in this patchset and additions to 
it can be added in individual test suites (if only that one uses that 
addition).

>>> diff --git a/dts/framework/utils.py b/dts/framework/utils.py
>>> index 6b5d5a805f..9c64cf497f 100644
>>> --- a/dts/framework/utils.py
>>> +++ b/dts/framework/utils.py
>>> @@ -27,6 +27,7 @@
>>>    from .exception import ConfigurationError
>>>
>>>    REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
>>> +REGEX_FOR_MAC_ADDRESS: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
>>
>> Is this the only format that testpmd returns?
> 
> I believe so, but because I'm not completely sure I can change this
> regex to support other variants as well. The hyphen separated one is
> easy enough that it might as well be included, the group of 4
> separated by a dot might be a little more involved but I can probably
> get it to work.
> 

Ok, might as well be safe, doesn't sound like a big change.

A small point is to make the regex as readable as possible - we could 
split it into multiple regexes (and try to match multiple times) or put 
the one big regex string on mutliple lines if possible (with each line 
being a separate variant or multiple closely related variants).

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v4 0/1] dts: testpmd verbose parser
  2024-07-29 20:39 [PATCH v1 0/1] dts: testpmd verbose parser jspewock
                   ` (2 preceding siblings ...)
  2024-08-08 20:36 ` [PATCH v3 0/1] dts: testpmd verbose parser jspewock
@ 2024-09-18 16:34 ` jspewock
  2024-09-18 16:34   ` [PATCH v4 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-09-18 17:05 ` [PATCH v5 0/1] dts: testpmd verbose parser jspewock
  4 siblings, 1 reply; 27+ messages in thread
From: jspewock @ 2024-09-18 16:34 UTC (permalink / raw)
  To: paul.szczepanek, yoan.picchi, npratte, Honnappa.Nagarahalli,
	thomas, probb, Luca.Vizzarro, alex.chapman, wathsala.vithanage,
	juraj.linkes
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

v4:
  * adjust name of OLFlags class to be PacketOffloadFlags
  * change values in PacketOffloadFlags to match the ones in
    rte_mbuf_core.h
  * added documentation from rte_mbuf_core.h to flag values
  * added documentation from rte_mbuf_ptype.h to the RtePTypes class
    and reference to the file where the definitions are taken from
  * fixed typos
  * removed find_all() from TextParser
  * added support for other types of MAC addresses
  * small formatting fixes

Jeremy Spewock (1):
  dts: add text parser for testpmd verbose output

 dts/framework/remote_session/testpmd_shell.py | 525 +++++++++++++++++-
 dts/framework/utils.py                        |   6 +
 2 files changed, 529 insertions(+), 2 deletions(-)

-- 
2.46.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v4 1/1] dts: add text parser for testpmd verbose output
  2024-09-18 16:34 ` [PATCH v4 0/1] dts: testpmd verbose parser jspewock
@ 2024-09-18 16:34   ` jspewock
  0 siblings, 0 replies; 27+ messages in thread
From: jspewock @ 2024-09-18 16:34 UTC (permalink / raw)
  To: paul.szczepanek, yoan.picchi, npratte, Honnappa.Nagarahalli,
	thomas, probb, Luca.Vizzarro, alex.chapman, wathsala.vithanage,
	juraj.linkes
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

Multiple test suites from the old DTS framework rely on being able to
consume and interpret the verbose output of testpmd. The new framework
doesn't have an elegant way for handling the verbose output, but test
suites are starting to be written that rely on it. This patch creates a
TextParser class that can be used to extract the verbose information
from any testpmd output and also adjusts the `stop` method of the shell
to return all output that it collected.

Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
---
 dts/framework/remote_session/testpmd_shell.py | 525 +++++++++++++++++-
 dts/framework/utils.py                        |   6 +
 2 files changed, 529 insertions(+), 2 deletions(-)

diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
index 43e9f56517..2d741802c7 100644
--- a/dts/framework/remote_session/testpmd_shell.py
+++ b/dts/framework/remote_session/testpmd_shell.py
@@ -31,7 +31,7 @@
 from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
 from framework.testbed_model.sut_node import SutNode
-from framework.utils import StrEnum
+from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
 
 
 class TestPmdDevice:
@@ -577,6 +577,497 @@ class TestPmdPortStats(TextParser):
     tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
 
 
+class PacketOffloadFlag(Flag):
+    """Flag representing the Packet Offload Features Flags in DPDK.
+
+    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK
+    located in lib/mbuf/rte_mbuf_core.h. It is expected that flag values in this class will match
+    the values they are set to in said DPDK library with one exception; all values must be unique.
+    For example, the definitions for unknown checksum flags in rte_mbuf_core.h are all set to
+    :data:`0`, but it is valuable to distinguish between them in this framework. For this reason
+    flags that are not unique in the DPDK library are set either to values within the
+    RTE_MBUF_F_FIRST_FREE-RTE_MBUF_F_LAST_FREE range for Rx or shifted 61+ bits for Tx.
+    """
+
+    # RX flags
+
+    #: The RX packet is a 802.1q VLAN packet, and the tci has been saved in mbuf->vlan_tci. If the
+    #: flag RTE_MBUF_F_RX_VLAN_STRIPPED is also present, the VLAN header has been stripped from
+    #: mbuf data, else it is still present.
+    RTE_MBUF_F_RX_VLAN = auto()
+
+    #: RX packet with RSS hash result.
+    RTE_MBUF_F_RX_RSS_HASH = auto()
+
+    #: RX packet with FDIR match indicate.
+    RTE_MBUF_F_RX_FDIR = auto()
+
+    #: This flag is set when the outermost IP header checksum is detected as wrong by the hardware.
+    RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD = 1 << 5
+
+    #: A vlan has been stripped by the hardware and its tci is saved in mbuf->vlan_tci. This can
+    #: only happen if vlan stripping is enabled in the RX configuration of the PMD. When
+    #: RTE_MBUF_F_RX_VLAN_STRIPPED is set, RTE_MBUF_F_RX_VLAN must also be set.
+    RTE_MBUF_F_RX_VLAN_STRIPPED = auto()
+
+    #: No information about the RX IP checksum.
+    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = 1 << 23
+    #: The IP checksum in the packet is wrong.
+    RTE_MBUF_F_RX_IP_CKSUM_BAD = 1 << 4
+    #: The IP checksum in the packet is valid.
+    RTE_MBUF_F_RX_IP_CKSUM_GOOD = 1 << 7
+    #: The IP checksum is not correct in the packet data, but the integrity of the IP header is
+    #: verified.
+    RTE_MBUF_F_RX_IP_CKSUM_NONE = RTE_MBUF_F_RX_IP_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_GOOD
+
+    #: No information about the RX L4 checksum.
+    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = 1 << 24
+    #: The L4 checksum in the packet is wrong.
+    RTE_MBUF_F_RX_L4_CKSUM_BAD = 1 << 3
+    #: The L4 checksum in the packet is valid.
+    RTE_MBUF_F_RX_L4_CKSUM_GOOD = 1 << 8
+    #: The L4 checksum is not correct in the packet data, but the integrity of the L4 data is
+    #: verified.
+    RTE_MBUF_F_RX_L4_CKSUM_NONE = RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD
+
+    #: RX IEEE1588 L2 Ethernet PT Packet.
+    RTE_MBUF_F_RX_IEEE1588_PTP = 1 << 9
+    #: RX IEEE1588 L2/L4 timestamped packet.
+    RTE_MBUF_F_RX_IEEE1588_TMST = 1 << 10
+
+    #: FD id reported if FDIR match.
+    RTE_MBUF_F_RX_FDIR_ID = 1 << 13
+    #: Flexible bytes reported if FDIR match.
+    RTE_MBUF_F_RX_FDIR_FLX = 1 << 14
+
+    #: If both RTE_MBUF_F_RX_QINQ_STRIPPED and RTE_MBUF_F_RX_VLAN_STRIPPED are set, the 2 VLANs
+    #: have been stripped by the hardware. If RTE_MBUF_F_RX_QINQ_STRIPPED is set and
+    #: RTE_MBUF_F_RX_VLAN_STRIPPED is unset, only the outer VLAN is removed from packet data.
+    RTE_MBUF_F_RX_QINQ_STRIPPED = auto()
+
+    #: When packets are coalesced by a hardware or virtual driver, this flag can be set in the RX
+    #: mbuf, meaning that the m->tso_segsz field is valid and is set to the segment size of
+    #: original packets.
+    RTE_MBUF_F_RX_LRO = auto()
+
+    #: Indicate that security offload processing was applied on the RX packet.
+    RTE_MBUF_F_RX_SEC_OFFLOAD = 1 << 18
+    #: Indicate that security offload processing failed on the RX packet.
+    RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED = auto()
+
+    #: The RX packet is a double VLAN. If this flag is set, RTE_MBUF_F_RX_VLAN must also be set. If
+    #: the flag RTE_MBUF_F_RX_QINQ_STRIPPED is also present, both VLANs headers have been stripped
+    #: from mbuf data, else they are still present.
+    RTE_MBUF_F_RX_QINQ = auto()
+
+    #: No info about the outer RX L4 checksum
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = 1 << 25
+    #: The outer L4 checksum in the packet is wrong
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = 1 << 21
+    #: The outer L4 checksum in the packet is valid
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = 1 << 22
+    #: Invalid outer L4 checksum state.
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_INVALID = (
+        RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD | RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD
+    )
+
+    # TX flags
+    #: Outer UDP checksum offload flag. This flag is used for enabling outer UDP checksum in PMD.
+    #: To use outer UDP checksum, the user either needs to enable the following in mbuf:
+    #:  a) Fill outer_l2_len and outer_l3_len in mbuf.
+    #:  b) Set the RTE_MBUF_F_TX_OUTER_UDP_CKSUM flag.
+    #:  c) Set the RTE_MBUF_F_TX_OUTER_IPV4 or RTE_MBUF_F_TX_OUTER_IPV6 flag.
+    #: Or configure RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM offload flag.
+    RTE_MBUF_F_TX_OUTER_UDP_CKSUM = 1 << 41
+
+    #: UDP Fragmentation Offload flag. This flag is used for enabling UDP fragmentation in SW or in
+    #: HW.
+    RTE_MBUF_F_TX_UDP_SEG = auto()
+
+    #: Request security offload processing on the TX packet. To use Tx security offload, the user
+    #: needs to fill l2_len in mbuf indicating L2 header size and where L3 header starts.
+    #: Similarly, l3_len should also be filled along with ol_flags reflecting current L3 type.
+    RTE_MBUF_F_TX_SEC_OFFLOAD = auto()
+
+    #: Offload the MACsec. This flag must be set by the application to enable this offload feature
+    #: for a packet to be transmitted.
+    RTE_MBUF_F_TX_MACSEC = auto()
+
+    """
+    Bits 45:48 used for the tunnel type. The tunnel type must be specified for TSO or checksum on
+    the inner part of tunnel packets. These flags can be used with RTE_MBUF_F_TX_TCP_SEG for TSO,
+    or RTE_MBUF_F_TX_xxx_CKSUM. The mbuf fields for inner and outer header lengths are required:
+    outer_l2_len, outer_l3_len, l2_len, l3_len, l4_len and tso_segsz for TSO.
+    """
+
+    #:
+    RTE_MBUF_F_TX_TUNNEL_VXLAN = 1 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GRE = 2 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_IPIP = 3 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GENEVE = 4 << 45
+    """ TX packet with MPLS-in-UDP RFC 7510 header. """
+    #:
+    RTE_MBUF_F_TX_TUNNEL_MPLSINUDP = 5 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE = 6 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GTP = 7 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_ESP = 8 << 45
+    #: Generic IP encapsulated tunnel type, used for TSO and checksum offload. This can be used for
+    #: tunnels which are not standards or listed above. It is preferred to use specific tunnel
+    #: flags like RTE_MBUF_F_TX_TUNNEL_GRE or RTE_MBUF_F_TX_TUNNEL_IPIP if possible. The ethdev
+    #: must be configured with RTE_ETH_TX_OFFLOAD_IP_TNL_TSO.  Outer and inner checksums are done
+    #: according to the existing flags like RTE_MBUF_F_TX_xxx_CKSUM. Specific tunnel headers that
+    #: contain payload length, sequence id or checksum are not expected to be updated.
+    RTE_MBUF_F_TX_TUNNEL_IP = 0xD << 45
+    #: Generic UDP encapsulated tunnel type, used for TSO and checksum offload. UDP tunnel type
+    #: implies outer IP layer. It can be used for tunnels which are not standards or listed above.
+    #: It is preferred to use specific tunnel flags like RTE_MBUF_F_TX_TUNNEL_VXLAN if possible.
+    #: The ethdev must be configured with RTE_ETH_TX_OFFLOAD_UDP_TNL_TSO. Outer and inner checksums
+    #: are done according to the existing flags like RTE_MBUF_F_TX_xxx_CKSUM. Specific tunnel
+    #: headers that contain payload length, sequence id or checksum are not expected to be updated.
+    RTE_MBUF_F_TX_TUNNEL_UDP = auto()
+
+    #: Double VLAN insertion (QinQ) request to driver, driver may offload the insertion based on
+    #: device capability. Mbuf 'vlan_tci' & 'vlan_tci_outer' must be valid when this flag is set.
+    RTE_MBUF_F_TX_QINQ = 1 << 49
+
+    #: TCP segmentation offload. To enable this offload feature for a packet to be transmitted on
+    #: hardware supporting TSO:
+    #:  - set the RTE_MBUF_F_TX_TCP_SEG flag in mbuf->ol_flags (this flag implies
+    #:    RTE_MBUF_F_TX_TCP_CKSUM)
+    #:  - set the flag RTE_MBUF_F_TX_IPV4 or RTE_MBUF_F_TX_IPV6
+    #:    * if it's IPv4, set the RTE_MBUF_F_TX_IP_CKSUM flag
+    #:  - fill the mbuf offload information: l2_len, l3_len, l4_len, tso_segsz
+    RTE_MBUF_F_TX_TCP_SEG = auto()
+    #: TX IEEE1588 packet to timestamp.
+    RTE_MBUF_F_TX_IEEE1588_TMST = auto()
+
+    """
+    Bits 52+53 used for L4 packet type with checksum enabled: 00: Reserved,
+    01: TCP checksum, 10: SCTP checksum, 11: UDP checksum. To use hardware
+    L4 checksum offload, the user needs to:
+     - fill l2_len and l3_len in mbuf
+     - set the flags RTE_MBUF_F_TX_TCP_CKSUM, RTE_MBUF_F_TX_SCTP_CKSUM or
+       RTE_MBUF_F_TX_UDP_CKSUM
+     - set the flag RTE_MBUF_F_TX_IPV4 or RTE_MBUF_F_TX_IPV6
+    """
+    #: Disable L4 cksum of TX pkt. Originally 0 in rte_mbuf_core.h but changed for uniqueness.
+    RTE_MBUF_F_TX_L4_NO_CKSUM = 1 << 61
+    #: TCP cksum of TX pkt. Computed by NIC.
+    RTE_MBUF_F_TX_TCP_CKSUM = 1 << 52
+    #: SCTP cksum of TX pkt. Computed by NIC.
+    RTE_MBUF_F_TX_SCTP_CKSUM = 2 << 52
+    #: UDP cksum of TX pkt. Computed by NIC.
+    RTE_MBUF_F_TX_UDP_CKSUM = 3 << 52
+
+    #: Offload the IP checksum in the hardware. The flag RTE_MBUF_F_TX_IPV4 should also be set by
+    #: the application, although a PMD will only check RTE_MBUF_F_TX_IP_CKSUM.
+    RTE_MBUF_F_TX_IP_CKSUM = 1 << 54
+
+    #: Packet is IPv4. This flag must be set when using any offload feature (TSO, L3 or L4
+    #: checksum) to tell the NIC that the packet is an IPv4 packet. If the packet is a tunneled
+    #: packet, this flag is related to the inner headers.
+    RTE_MBUF_F_TX_IPV4 = auto()
+    #: Packet is IPv6. This flag must be set when using an offload feature (TSO or L4 checksum) to
+    #: tell the NIC that the packet is an IPv6 packet. If the packet is a tunneled packet, this
+    #: flag is related to the inner headers.
+    RTE_MBUF_F_TX_IPV6 = auto()
+    #: VLAN tag insertion request to driver, driver may offload the insertion based on the device
+    #: capability. mbuf 'vlan_tci' field must be valid when this flag is set.
+    RTE_MBUF_F_TX_VLAN = auto()
+
+    #: Offload the IP checksum of an external header in the hardware. The flag
+    #: RTE_MBUF_F_TX_OUTER_IPV4 should also be set by the application, although a PMD will only
+    #: check RTE_MBUF_F_TX_OUTER_IP_CKSUM.
+    RTE_MBUF_F_TX_OUTER_IP_CKSUM = auto()
+    #: Packet outer header is IPv4. This flag must be set when using any outer offload feature (L3
+    #: or L4 checksum) to tell the NIC that the outer header of the tunneled packet is an IPv4
+    #: packet.
+    RTE_MBUF_F_TX_OUTER_IPV4 = auto()
+    #: Packet outer header is IPv6. This flag must be set when using any outer offload feature (L4
+    #: checksum) to tell the NIC that the outer header of the tunneled packet is an IPv6 packet.
+    RTE_MBUF_F_TX_OUTER_IPV6 = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing ol_flag values.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in arr:
+            if hasattr(cls, name):
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls) -> ParserFn:
+        """Makes a parser function.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+class RtePTypes(Flag):
+    """Flag representing possible packet types in DPDK verbose output.
+
+    Values in this class are derived from definitions in the RTE MBUF ptype library in DPDK located
+    in lib/mbuf/rte_mbuf_ptype.h. Specifically, the names of values in this class should match the
+    possible return options from the methods rte_get_ptype_*_name in rte_mbuf_ptype.c.
+    """
+
+    # L2
+    #: Ethernet packet type. This is used for outer packet for tunneling cases.
+    L2_ETHER = auto()
+    #: Ethernet packet type for time sync.
+    L2_ETHER_TIMESYNC = auto()
+    #: ARP (Address Resolution Protocol) packet type.
+    L2_ETHER_ARP = auto()
+    #: LLDP (Link Layer Discovery Protocol) packet type.
+    L2_ETHER_LLDP = auto()
+    #: NSH (Network Service Header) packet type.
+    L2_ETHER_NSH = auto()
+    #: VLAN packet type.
+    L2_ETHER_VLAN = auto()
+    #: QinQ packet type.
+    L2_ETHER_QINQ = auto()
+    #: PPPOE packet type.
+    L2_ETHER_PPPOE = auto()
+    #: FCoE packet type..
+    L2_ETHER_FCOE = auto()
+    #: MPLS packet type.
+    L2_ETHER_MPLS = auto()
+    #: No L2 packet information.
+    L2_UNKNOWN = auto()
+
+    # L3
+    #: IP (Internet Protocol) version 4 packet type. This is used for outer packet for tunneling
+    #: cases, and does not contain any header option.
+    L3_IPV4 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for outer packet for tunneling
+    #: cases, and contains header options.
+    L3_IPV4_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for outer packet for tunneling
+    #: cases, and does not contain any extension header.
+    L3_IPV6 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for outer packet for tunneling
+    #: cases, and may or maynot contain header options.
+    L3_IPV4_EXT_UNKNOWN = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for outer packet for tunneling
+    #: cases, and contains extension headers.
+    L3_IPV6_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for outer packet for tunneling
+    #: cases, and may or maynot contain extension headers.
+    L3_IPV6_EXT_UNKNOWN = auto()
+    #: No L3 packet information.
+    L3_UNKNOWN = auto()
+
+    # L4
+    #: TCP (Transmission Control Protocol) packet type. This is used for outer packet for tunneling
+    #: cases.
+    L4_TCP = auto()
+    #: UDP (User Datagram Protocol) packet type. This is used for outer packet for tunneling cases.
+    L4_UDP = auto()
+    #: Fragmented IP (Internet Protocol) packet type. This is used for outer packet for tunneling
+    #: cases and refers to those packets of any IP types which can be recognized as fragmented. A
+    #: fragmented packet cannot be recognized as any other L4 types (RTE_PTYPE_L4_TCP,
+    #: RTE_PTYPE_L4_UDP, RTE_PTYPE_L4_SCTP, RTE_PTYPE_L4_ICMP, RTE_PTYPE_L4_NONFRAG).
+    L4_FRAG = auto()
+    #: SCTP (Stream Control Transmission Protocol) packet type. This is used for outer packet for
+    #: tunneling cases.
+    L4_SCTP = auto()
+    #: ICMP (Internet Control Message Protocol) packet type. This is used for outer packet for
+    #: tunneling cases.
+    L4_ICMP = auto()
+    #: Non-fragmented IP (Internet Protocol) packet type. This is used for outer packet for
+    #: tunneling cases and refers to those packets of any IP types, that cannot be recognized as
+    #: any of the above L4 types (RTE_PTYPE_L4_TCP, RTE_PTYPE_L4_UDP, RTE_PTYPE_L4_FRAG,
+    #: RTE_PTYPE_L4_SCTP, RTE_PTYPE_L4_ICMP).
+    L4_NONFRAG = auto()
+    #: IGMP (Internet Group Management Protocol) packet type.
+    L4_IGMP = auto()
+    #: No L4 packet information.
+    L4_UNKNOWN = auto()
+
+    # Tunnel
+    #: IP (Internet Protocol) in IP (Internet Protocol) tunneling packet type.
+    TUNNEL_IP = auto()
+    #: GRE (Generic Routing Encapsulation) tunneling packet type.
+    TUNNEL_GRE = auto()
+    #: VXLAN (Virtual eXtensible Local Area Network) tunneling packet type.
+    TUNNEL_VXLAN = auto()
+    #: NVGRE (Network Virtualization using Generic Routing Encapsulation) tunneling packet type.
+    TUNNEL_NVGRE = auto()
+    #: GENEVE (Generic Network Virtualization Encapsulation) tunneling packet type.
+    TUNNEL_GENEVE = auto()
+    #: Tunneling packet type of Teredo, VXLAN (Virtual eXtensible Local Area Network) or GRE
+    #: (Generic Routing Encapsulation) could be recognized as this packet type, if they can not be
+    #: recognized independently as of hardware capability.
+    TUNNEL_GRENAT = auto()
+    #: GTP-C (GPRS Tunnelling Protocol) control tunneling packet type.
+    TUNNEL_GTPC = auto()
+    #: GTP-U (GPRS Tunnelling Protocol) user data tunneling packet type.
+    TUNNEL_GTPU = auto()
+    #: ESP (IP Encapsulating Security Payload) tunneling packet type.
+    TUNNEL_ESP = auto()
+    #: L2TP (Layer 2 Tunneling Protocol) tunneling packet type.
+    TUNNEL_L2TP = auto()
+    #: VXLAN-GPE (VXLAN Generic Protocol Extension) tunneling packet type.
+    TUNNEL_VXLAN_GPE = auto()
+    #: MPLS-in-UDP tunneling packet type (RFC 7510).
+    TUNNEL_MPLS_IN_UDP = auto()
+    #: MPLS-in-GRE tunneling packet type (RFC 4023).
+    TUNNEL_MPLS_IN_GRE = auto()
+    #: No tunnel information found on the packet.
+    TUNNEL_UNKNOWN = auto()
+
+    # Inner L2
+    #: Ethernet packet type. This is used for inner packet type only.
+    INNER_L2_ETHER = auto()
+    #: Ethernet packet type with VLAN (Virtual Local Area Network) tag.
+    INNER_L2_ETHER_VLAN = auto()
+    #: QinQ packet type.
+    INNER_L2_ETHER_QINQ = auto()
+    #: No inner L2 information found on the packet.
+    INNER_L2_UNKNOWN = auto()
+
+    # Inner L3
+    #: IP (Internet Protocol) version 4 packet type. This is used for inner packet only, and does
+    #: not contain any header option.
+    INNER_L3_IPV4 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for inner packet only, and
+    #: contains header options.
+    INNER_L3_IPV4_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for inner packet only, and does
+    #: not contain any extension header.
+    INNER_L3_IPV6 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for inner packet only, and may or
+    #: may not contain header options.
+    INNER_L3_IPV4_EXT_UNKNOWN = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for inner packet only, and
+    #: contains extension headers.
+    INNER_L3_IPV6_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for inner packet only, and may or
+    #: may not contain extension headers.
+    INNER_L3_IPV6_EXT_UNKNOWN = auto()
+    #: No inner L3 information found on the packet.
+    INNER_L3_UNKNOWN = auto()
+
+    # Inner L4
+    #: TCP (Transmission Control Protocol) packet type. This is used for inner packet only.
+    INNER_L4_TCP = auto()
+    #: UDP (User Datagram Protocol) packet type. This is used for inner packet only.
+    INNER_L4_UDP = auto()
+    #: Fragmented IP (Internet Protocol) packet type. This is used for inner packet only, and may
+    #: or maynot have a layer 4 packet.
+    INNER_L4_FRAG = auto()
+    #: SCTP (Stream Control Transmission Protocol) packet type. This is used for inner packet only.
+    INNER_L4_SCTP = auto()
+    #: ICMP (Internet Control Message Protocol) packet type. This is used for inner packet only.
+    INNER_L4_ICMP = auto()
+    #: Non-fragmented IP (Internet Protocol) packet type. It is used for inner packet only, and may
+    #: or may not have other unknown layer 4 packet types.
+    INNER_L4_NONFRAG = auto()
+    #: No inner L4 information found on the packet.
+    INNER_L4_UNKNOWN = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing packet types.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in arr:
+            if hasattr(cls, name):
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls, hw: bool) -> ParserFn:
+        """Makes a parser function.
+
+        Args:
+            hw: Whether to make a parser for hardware ptypes or software ptypes. If :data:`True`,
+                hardware ptypes will be collected, otherwise software pytpes will.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(f"{'hw' if hw else 'sw'} ptype: ([^-]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+@dataclass
+class TestPmdVerbosePacket(TextParser):
+    """Packet information provided by verbose output in Testpmd.
+
+    This dataclass expects that packet information be prepended with the starting line of packet
+    bursts. Specifically, the line that reads "port X/queue Y: sent/received Z packets".
+    """
+
+    #: ID of the port that handled the packet.
+    port_id: int = field(metadata=TextParser.find_int(r"port (\d+)/queue \d+"))
+    #: ID of the queue that handled the packet.
+    queue_id: int = field(metadata=TextParser.find_int(r"port \d+/queue (\d+)"))
+    #: Whether the packet was received or sent by the queue/port.
+    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
+    #:
+    src_mac: str = field(metadata=TextParser.find(f"src=({REGEX_FOR_MAC_ADDRESS})"))
+    #:
+    dst_mac: str = field(metadata=TextParser.find(f"dst=({REGEX_FOR_MAC_ADDRESS})"))
+    #: Memory pool the packet was handled on.
+    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
+    #: Packet type in hex.
+    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
+    #:
+    length: int = field(metadata=TextParser.find_int(r"length=(\d+)"))
+    #: Number of segments in the packet.
+    nb_segs: int = field(metadata=TextParser.find_int(r"nb_segs=(\d+)"))
+    #: Hardware packet type.
+    hw_ptype: RtePTypes = field(metadata=RtePTypes.make_parser(hw=True))
+    #: Software packet type.
+    sw_ptype: RtePTypes = field(metadata=RtePTypes.make_parser(hw=False))
+    #:
+    l2_len: int = field(metadata=TextParser.find_int(r"l2_len=(\d+)"))
+    #:
+    ol_flags: PacketOffloadFlag = field(metadata=PacketOffloadFlag.make_parser())
+    #: RSS hash of the packet in hex.
+    rss_hash: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS hash=(0x[a-fA-F\d]+)")
+    )
+    #: RSS queue that handled the packet in hex.
+    rss_queue: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS queue=(0x[a-fA-F\d]+)")
+    )
+    #:
+    l3_len: int | None = field(default=None, metadata=TextParser.find_int(r"l3_len=(\d+)"))
+    #:
+    l4_len: int | None = field(default=None, metadata=TextParser.find_int(r"l4_len=(\d+)"))
+
+
 class TestPmdShell(DPDKShell):
     """Testpmd interactive shell.
 
@@ -645,7 +1136,7 @@ def start(self, verify: bool = True) -> None:
                         "Not all ports came up after starting packet forwarding in testpmd."
                     )
 
-    def stop(self, verify: bool = True) -> None:
+    def stop(self, verify: bool = True) -> str:
         """Stop packet forwarding.
 
         Args:
@@ -656,6 +1147,9 @@ def stop(self, verify: bool = True) -> None:
         Raises:
             InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
                 forwarding results in an error.
+
+        Returns:
+            Output gathered from sending the stop command.
         """
         stop_cmd_output = self.send_command("stop")
         if verify:
@@ -665,6 +1159,7 @@ 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.")
+        return stop_cmd_output
 
     def get_devices(self) -> list[TestPmdDevice]:
         """Get a list of device names that are known to testpmd.
@@ -806,6 +1301,32 @@ def show_port_stats(self, port_id: int) -> TestPmdPortStats:
 
         return TestPmdPortStats.parse(output)
 
+    @staticmethod
+    def extract_verbose_output(output: str) -> list[TestPmdVerbosePacket]:
+        """Extract the verbose information present in given testpmd output.
+
+        This method extracts sections of verbose output that begin with the line
+        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
+
+        Args:
+            output: Testpmd output that contains verbose information
+
+        Returns:
+            List of parsed packet information gathered from verbose information in `output`.
+        """
+        out: list[TestPmdVerbosePacket] = []
+        prev_header: str = ""
+        iter = re.finditer(
+            r"(?P<HEADER>(?:port \d+/queue \d+: (?:received|sent) \d+ packets)?)\s*"
+            r"(?P<PACKET>src=[\w\s=:-]+?ol_flags: [\w ]+)",
+            output,
+        )
+        for match in iter:
+            if match.group("HEADER"):
+                prev_header = match.group("HEADER")
+            out.append(TestPmdVerbosePacket.parse(f"{prev_header}\n{match.group('PACKET')}"))
+        return out
+
     def _close(self) -> None:
         """Overrides :meth:`~.interactive_shell.close`."""
         self.stop()
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 6b5d5a805f..c040d1cb37 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -27,6 +27,12 @@
 from .exception import ConfigurationError
 
 REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
+_REGEX_FOR_COLON_SEP_MAC: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
+_REGEX_FOR_HYPEN_SEP_MAC: str = r"(?:[\da-fA-F]{2}-){5,7}[\da-fA-F]{2}"
+_REGEX_FOR_DOT_SEP_MAC: str = r"(?:[\da-fA-F]{4}.){2}[\da-fA-F]{4}"
+REGEX_FOR_MAC_ADDRESS: str = (
+    rf"{_REGEX_FOR_COLON_SEP_MAC}|{_REGEX_FOR_HYPEN_SEP_MAC}|{_REGEX_FOR_DOT_SEP_MAC}"
+)
 
 
 def expand_range(range_str: str) -> list[int]:
-- 
2.46.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v5 0/1] dts: testpmd verbose parser
  2024-07-29 20:39 [PATCH v1 0/1] dts: testpmd verbose parser jspewock
                   ` (3 preceding siblings ...)
  2024-09-18 16:34 ` [PATCH v4 0/1] dts: testpmd verbose parser jspewock
@ 2024-09-18 17:05 ` jspewock
  2024-09-18 17:05   ` [PATCH v5 1/1] dts: add text parser for testpmd verbose output jspewock
  4 siblings, 1 reply; 27+ messages in thread
From: jspewock @ 2024-09-18 17:05 UTC (permalink / raw)
  To: alex.chapman, paul.szczepanek, Luca.Vizzarro,
	Honnappa.Nagarahalli, wathsala.vithanage, probb, npratte,
	juraj.linkes, thomas, yoan.picchi
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

v5:
 * fix typo

Jeremy Spewock (1):
  dts: add text parser for testpmd verbose output

 dts/framework/remote_session/testpmd_shell.py | 525 +++++++++++++++++-
 dts/framework/utils.py                        |   6 +
 2 files changed, 529 insertions(+), 2 deletions(-)

-- 
2.46.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v5 1/1] dts: add text parser for testpmd verbose output
  2024-09-18 17:05 ` [PATCH v5 0/1] dts: testpmd verbose parser jspewock
@ 2024-09-18 17:05   ` jspewock
  2024-09-19  9:02     ` Juraj Linkeš
  2024-09-19 12:35     ` Juraj Linkeš
  0 siblings, 2 replies; 27+ messages in thread
From: jspewock @ 2024-09-18 17:05 UTC (permalink / raw)
  To: alex.chapman, paul.szczepanek, Luca.Vizzarro,
	Honnappa.Nagarahalli, wathsala.vithanage, probb, npratte,
	juraj.linkes, thomas, yoan.picchi
  Cc: dev, Jeremy Spewock

From: Jeremy Spewock <jspewock@iol.unh.edu>

Multiple test suites from the old DTS framework rely on being able to
consume and interpret the verbose output of testpmd. The new framework
doesn't have an elegant way for handling the verbose output, but test
suites are starting to be written that rely on it. This patch creates a
TextParser class that can be used to extract the verbose information
from any testpmd output and also adjusts the `stop` method of the shell
to return all output that it collected.

Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
---
 dts/framework/remote_session/testpmd_shell.py | 525 +++++++++++++++++-
 dts/framework/utils.py                        |   6 +
 2 files changed, 529 insertions(+), 2 deletions(-)

diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
index 43e9f56517..2d741802c7 100644
--- a/dts/framework/remote_session/testpmd_shell.py
+++ b/dts/framework/remote_session/testpmd_shell.py
@@ -31,7 +31,7 @@
 from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
 from framework.testbed_model.sut_node import SutNode
-from framework.utils import StrEnum
+from framework.utils import REGEX_FOR_MAC_ADDRESS, StrEnum
 
 
 class TestPmdDevice:
@@ -577,6 +577,497 @@ class TestPmdPortStats(TextParser):
     tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
 
 
+class PacketOffloadFlag(Flag):
+    """Flag representing the Packet Offload Features Flags in DPDK.
+
+    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK
+    located in lib/mbuf/rte_mbuf_core.h. It is expected that flag values in this class will match
+    the values they are set to in said DPDK library with one exception; all values must be unique.
+    For example, the definitions for unknown checksum flags in rte_mbuf_core.h are all set to
+    :data:`0`, but it is valuable to distinguish between them in this framework. For this reason
+    flags that are not unique in the DPDK library are set either to values within the
+    RTE_MBUF_F_FIRST_FREE-RTE_MBUF_F_LAST_FREE range for Rx or shifted 61+ bits for Tx.
+    """
+
+    # RX flags
+
+    #: The RX packet is a 802.1q VLAN packet, and the tci has been saved in mbuf->vlan_tci. If the
+    #: flag RTE_MBUF_F_RX_VLAN_STRIPPED is also present, the VLAN header has been stripped from
+    #: mbuf data, else it is still present.
+    RTE_MBUF_F_RX_VLAN = auto()
+
+    #: RX packet with RSS hash result.
+    RTE_MBUF_F_RX_RSS_HASH = auto()
+
+    #: RX packet with FDIR match indicate.
+    RTE_MBUF_F_RX_FDIR = auto()
+
+    #: This flag is set when the outermost IP header checksum is detected as wrong by the hardware.
+    RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD = 1 << 5
+
+    #: A vlan has been stripped by the hardware and its tci is saved in mbuf->vlan_tci. This can
+    #: only happen if vlan stripping is enabled in the RX configuration of the PMD. When
+    #: RTE_MBUF_F_RX_VLAN_STRIPPED is set, RTE_MBUF_F_RX_VLAN must also be set.
+    RTE_MBUF_F_RX_VLAN_STRIPPED = auto()
+
+    #: No information about the RX IP checksum.
+    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = 1 << 23
+    #: The IP checksum in the packet is wrong.
+    RTE_MBUF_F_RX_IP_CKSUM_BAD = 1 << 4
+    #: The IP checksum in the packet is valid.
+    RTE_MBUF_F_RX_IP_CKSUM_GOOD = 1 << 7
+    #: The IP checksum is not correct in the packet data, but the integrity of the IP header is
+    #: verified.
+    RTE_MBUF_F_RX_IP_CKSUM_NONE = RTE_MBUF_F_RX_IP_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_GOOD
+
+    #: No information about the RX L4 checksum.
+    RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN = 1 << 24
+    #: The L4 checksum in the packet is wrong.
+    RTE_MBUF_F_RX_L4_CKSUM_BAD = 1 << 3
+    #: The L4 checksum in the packet is valid.
+    RTE_MBUF_F_RX_L4_CKSUM_GOOD = 1 << 8
+    #: The L4 checksum is not correct in the packet data, but the integrity of the L4 data is
+    #: verified.
+    RTE_MBUF_F_RX_L4_CKSUM_NONE = RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD
+
+    #: RX IEEE1588 L2 Ethernet PT Packet.
+    RTE_MBUF_F_RX_IEEE1588_PTP = 1 << 9
+    #: RX IEEE1588 L2/L4 timestamped packet.
+    RTE_MBUF_F_RX_IEEE1588_TMST = 1 << 10
+
+    #: FD id reported if FDIR match.
+    RTE_MBUF_F_RX_FDIR_ID = 1 << 13
+    #: Flexible bytes reported if FDIR match.
+    RTE_MBUF_F_RX_FDIR_FLX = 1 << 14
+
+    #: If both RTE_MBUF_F_RX_QINQ_STRIPPED and RTE_MBUF_F_RX_VLAN_STRIPPED are set, the 2 VLANs
+    #: have been stripped by the hardware. If RTE_MBUF_F_RX_QINQ_STRIPPED is set and
+    #: RTE_MBUF_F_RX_VLAN_STRIPPED is unset, only the outer VLAN is removed from packet data.
+    RTE_MBUF_F_RX_QINQ_STRIPPED = auto()
+
+    #: When packets are coalesced by a hardware or virtual driver, this flag can be set in the RX
+    #: mbuf, meaning that the m->tso_segsz field is valid and is set to the segment size of
+    #: original packets.
+    RTE_MBUF_F_RX_LRO = auto()
+
+    #: Indicate that security offload processing was applied on the RX packet.
+    RTE_MBUF_F_RX_SEC_OFFLOAD = 1 << 18
+    #: Indicate that security offload processing failed on the RX packet.
+    RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED = auto()
+
+    #: The RX packet is a double VLAN. If this flag is set, RTE_MBUF_F_RX_VLAN must also be set. If
+    #: the flag RTE_MBUF_F_RX_QINQ_STRIPPED is also present, both VLANs headers have been stripped
+    #: from mbuf data, else they are still present.
+    RTE_MBUF_F_RX_QINQ = auto()
+
+    #: No info about the outer RX L4 checksum
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_UNKNOWN = 1 << 25
+    #: The outer L4 checksum in the packet is wrong
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD = 1 << 21
+    #: The outer L4 checksum in the packet is valid
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD = 1 << 22
+    #: Invalid outer L4 checksum state.
+    RTE_MBUF_F_RX_OUTER_L4_CKSUM_INVALID = (
+        RTE_MBUF_F_RX_OUTER_L4_CKSUM_BAD | RTE_MBUF_F_RX_OUTER_L4_CKSUM_GOOD
+    )
+
+    # TX flags
+    #: Outer UDP checksum offload flag. This flag is used for enabling outer UDP checksum in PMD.
+    #: To use outer UDP checksum, the user either needs to enable the following in mbuf:
+    #:  a) Fill outer_l2_len and outer_l3_len in mbuf.
+    #:  b) Set the RTE_MBUF_F_TX_OUTER_UDP_CKSUM flag.
+    #:  c) Set the RTE_MBUF_F_TX_OUTER_IPV4 or RTE_MBUF_F_TX_OUTER_IPV6 flag.
+    #: Or configure RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM offload flag.
+    RTE_MBUF_F_TX_OUTER_UDP_CKSUM = 1 << 41
+
+    #: UDP Fragmentation Offload flag. This flag is used for enabling UDP fragmentation in SW or in
+    #: HW.
+    RTE_MBUF_F_TX_UDP_SEG = auto()
+
+    #: Request security offload processing on the TX packet. To use Tx security offload, the user
+    #: needs to fill l2_len in mbuf indicating L2 header size and where L3 header starts.
+    #: Similarly, l3_len should also be filled along with ol_flags reflecting current L3 type.
+    RTE_MBUF_F_TX_SEC_OFFLOAD = auto()
+
+    #: Offload the MACsec. This flag must be set by the application to enable this offload feature
+    #: for a packet to be transmitted.
+    RTE_MBUF_F_TX_MACSEC = auto()
+
+    """
+    Bits 45:48 used for the tunnel type. The tunnel type must be specified for TSO or checksum on
+    the inner part of tunnel packets. These flags can be used with RTE_MBUF_F_TX_TCP_SEG for TSO,
+    or RTE_MBUF_F_TX_xxx_CKSUM. The mbuf fields for inner and outer header lengths are required:
+    outer_l2_len, outer_l3_len, l2_len, l3_len, l4_len and tso_segsz for TSO.
+    """
+
+    #:
+    RTE_MBUF_F_TX_TUNNEL_VXLAN = 1 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GRE = 2 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_IPIP = 3 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GENEVE = 4 << 45
+    """ TX packet with MPLS-in-UDP RFC 7510 header. """
+    #:
+    RTE_MBUF_F_TX_TUNNEL_MPLSINUDP = 5 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE = 6 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_GTP = 7 << 45
+    #:
+    RTE_MBUF_F_TX_TUNNEL_ESP = 8 << 45
+    #: Generic IP encapsulated tunnel type, used for TSO and checksum offload. This can be used for
+    #: tunnels which are not standards or listed above. It is preferred to use specific tunnel
+    #: flags like RTE_MBUF_F_TX_TUNNEL_GRE or RTE_MBUF_F_TX_TUNNEL_IPIP if possible. The ethdev
+    #: must be configured with RTE_ETH_TX_OFFLOAD_IP_TNL_TSO.  Outer and inner checksums are done
+    #: according to the existing flags like RTE_MBUF_F_TX_xxx_CKSUM. Specific tunnel headers that
+    #: contain payload length, sequence id or checksum are not expected to be updated.
+    RTE_MBUF_F_TX_TUNNEL_IP = 0xD << 45
+    #: Generic UDP encapsulated tunnel type, used for TSO and checksum offload. UDP tunnel type
+    #: implies outer IP layer. It can be used for tunnels which are not standards or listed above.
+    #: It is preferred to use specific tunnel flags like RTE_MBUF_F_TX_TUNNEL_VXLAN if possible.
+    #: The ethdev must be configured with RTE_ETH_TX_OFFLOAD_UDP_TNL_TSO. Outer and inner checksums
+    #: are done according to the existing flags like RTE_MBUF_F_TX_xxx_CKSUM. Specific tunnel
+    #: headers that contain payload length, sequence id or checksum are not expected to be updated.
+    RTE_MBUF_F_TX_TUNNEL_UDP = auto()
+
+    #: Double VLAN insertion (QinQ) request to driver, driver may offload the insertion based on
+    #: device capability. Mbuf 'vlan_tci' & 'vlan_tci_outer' must be valid when this flag is set.
+    RTE_MBUF_F_TX_QINQ = 1 << 49
+
+    #: TCP segmentation offload. To enable this offload feature for a packet to be transmitted on
+    #: hardware supporting TSO:
+    #:  - set the RTE_MBUF_F_TX_TCP_SEG flag in mbuf->ol_flags (this flag implies
+    #:    RTE_MBUF_F_TX_TCP_CKSUM)
+    #:  - set the flag RTE_MBUF_F_TX_IPV4 or RTE_MBUF_F_TX_IPV6
+    #:    * if it's IPv4, set the RTE_MBUF_F_TX_IP_CKSUM flag
+    #:  - fill the mbuf offload information: l2_len, l3_len, l4_len, tso_segsz
+    RTE_MBUF_F_TX_TCP_SEG = auto()
+    #: TX IEEE1588 packet to timestamp.
+    RTE_MBUF_F_TX_IEEE1588_TMST = auto()
+
+    """
+    Bits 52+53 used for L4 packet type with checksum enabled: 00: Reserved,
+    01: TCP checksum, 10: SCTP checksum, 11: UDP checksum. To use hardware
+    L4 checksum offload, the user needs to:
+     - fill l2_len and l3_len in mbuf
+     - set the flags RTE_MBUF_F_TX_TCP_CKSUM, RTE_MBUF_F_TX_SCTP_CKSUM or
+       RTE_MBUF_F_TX_UDP_CKSUM
+     - set the flag RTE_MBUF_F_TX_IPV4 or RTE_MBUF_F_TX_IPV6
+    """
+    #: Disable L4 cksum of TX pkt. Originally 0 in rte_mbuf_core.h but changed for uniqueness.
+    RTE_MBUF_F_TX_L4_NO_CKSUM = 1 << 61
+    #: TCP cksum of TX pkt. Computed by NIC.
+    RTE_MBUF_F_TX_TCP_CKSUM = 1 << 52
+    #: SCTP cksum of TX pkt. Computed by NIC.
+    RTE_MBUF_F_TX_SCTP_CKSUM = 2 << 52
+    #: UDP cksum of TX pkt. Computed by NIC.
+    RTE_MBUF_F_TX_UDP_CKSUM = 3 << 52
+
+    #: Offload the IP checksum in the hardware. The flag RTE_MBUF_F_TX_IPV4 should also be set by
+    #: the application, although a PMD will only check RTE_MBUF_F_TX_IP_CKSUM.
+    RTE_MBUF_F_TX_IP_CKSUM = 1 << 54
+
+    #: Packet is IPv4. This flag must be set when using any offload feature (TSO, L3 or L4
+    #: checksum) to tell the NIC that the packet is an IPv4 packet. If the packet is a tunneled
+    #: packet, this flag is related to the inner headers.
+    RTE_MBUF_F_TX_IPV4 = auto()
+    #: Packet is IPv6. This flag must be set when using an offload feature (TSO or L4 checksum) to
+    #: tell the NIC that the packet is an IPv6 packet. If the packet is a tunneled packet, this
+    #: flag is related to the inner headers.
+    RTE_MBUF_F_TX_IPV6 = auto()
+    #: VLAN tag insertion request to driver, driver may offload the insertion based on the device
+    #: capability. mbuf 'vlan_tci' field must be valid when this flag is set.
+    RTE_MBUF_F_TX_VLAN = auto()
+
+    #: Offload the IP checksum of an external header in the hardware. The flag
+    #: RTE_MBUF_F_TX_OUTER_IPV4 should also be set by the application, although a PMD will only
+    #: check RTE_MBUF_F_TX_OUTER_IP_CKSUM.
+    RTE_MBUF_F_TX_OUTER_IP_CKSUM = auto()
+    #: Packet outer header is IPv4. This flag must be set when using any outer offload feature (L3
+    #: or L4 checksum) to tell the NIC that the outer header of the tunneled packet is an IPv4
+    #: packet.
+    RTE_MBUF_F_TX_OUTER_IPV4 = auto()
+    #: Packet outer header is IPv6. This flag must be set when using any outer offload feature (L4
+    #: checksum) to tell the NIC that the outer header of the tunneled packet is an IPv6 packet.
+    RTE_MBUF_F_TX_OUTER_IPV6 = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing ol_flag values.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in arr:
+            if hasattr(cls, name):
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls) -> ParserFn:
+        """Makes a parser function.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+class RtePTypes(Flag):
+    """Flag representing possible packet types in DPDK verbose output.
+
+    Values in this class are derived from definitions in the RTE MBUF ptype library in DPDK located
+    in lib/mbuf/rte_mbuf_ptype.h. Specifically, the names of values in this class should match the
+    possible return options from the methods rte_get_ptype_*_name in rte_mbuf_ptype.c.
+    """
+
+    # L2
+    #: Ethernet packet type. This is used for outer packet for tunneling cases.
+    L2_ETHER = auto()
+    #: Ethernet packet type for time sync.
+    L2_ETHER_TIMESYNC = auto()
+    #: ARP (Address Resolution Protocol) packet type.
+    L2_ETHER_ARP = auto()
+    #: LLDP (Link Layer Discovery Protocol) packet type.
+    L2_ETHER_LLDP = auto()
+    #: NSH (Network Service Header) packet type.
+    L2_ETHER_NSH = auto()
+    #: VLAN packet type.
+    L2_ETHER_VLAN = auto()
+    #: QinQ packet type.
+    L2_ETHER_QINQ = auto()
+    #: PPPOE packet type.
+    L2_ETHER_PPPOE = auto()
+    #: FCoE packet type..
+    L2_ETHER_FCOE = auto()
+    #: MPLS packet type.
+    L2_ETHER_MPLS = auto()
+    #: No L2 packet information.
+    L2_UNKNOWN = auto()
+
+    # L3
+    #: IP (Internet Protocol) version 4 packet type. This is used for outer packet for tunneling
+    #: cases, and does not contain any header option.
+    L3_IPV4 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for outer packet for tunneling
+    #: cases, and contains header options.
+    L3_IPV4_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for outer packet for tunneling
+    #: cases, and does not contain any extension header.
+    L3_IPV6 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for outer packet for tunneling
+    #: cases, and may or maynot contain header options.
+    L3_IPV4_EXT_UNKNOWN = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for outer packet for tunneling
+    #: cases, and contains extension headers.
+    L3_IPV6_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for outer packet for tunneling
+    #: cases, and may or maynot contain extension headers.
+    L3_IPV6_EXT_UNKNOWN = auto()
+    #: No L3 packet information.
+    L3_UNKNOWN = auto()
+
+    # L4
+    #: TCP (Transmission Control Protocol) packet type. This is used for outer packet for tunneling
+    #: cases.
+    L4_TCP = auto()
+    #: UDP (User Datagram Protocol) packet type. This is used for outer packet for tunneling cases.
+    L4_UDP = auto()
+    #: Fragmented IP (Internet Protocol) packet type. This is used for outer packet for tunneling
+    #: cases and refers to those packets of any IP types which can be recognized as fragmented. A
+    #: fragmented packet cannot be recognized as any other L4 types (RTE_PTYPE_L4_TCP,
+    #: RTE_PTYPE_L4_UDP, RTE_PTYPE_L4_SCTP, RTE_PTYPE_L4_ICMP, RTE_PTYPE_L4_NONFRAG).
+    L4_FRAG = auto()
+    #: SCTP (Stream Control Transmission Protocol) packet type. This is used for outer packet for
+    #: tunneling cases.
+    L4_SCTP = auto()
+    #: ICMP (Internet Control Message Protocol) packet type. This is used for outer packet for
+    #: tunneling cases.
+    L4_ICMP = auto()
+    #: Non-fragmented IP (Internet Protocol) packet type. This is used for outer packet for
+    #: tunneling cases and refers to those packets of any IP types, that cannot be recognized as
+    #: any of the above L4 types (RTE_PTYPE_L4_TCP, RTE_PTYPE_L4_UDP, RTE_PTYPE_L4_FRAG,
+    #: RTE_PTYPE_L4_SCTP, RTE_PTYPE_L4_ICMP).
+    L4_NONFRAG = auto()
+    #: IGMP (Internet Group Management Protocol) packet type.
+    L4_IGMP = auto()
+    #: No L4 packet information.
+    L4_UNKNOWN = auto()
+
+    # Tunnel
+    #: IP (Internet Protocol) in IP (Internet Protocol) tunneling packet type.
+    TUNNEL_IP = auto()
+    #: GRE (Generic Routing Encapsulation) tunneling packet type.
+    TUNNEL_GRE = auto()
+    #: VXLAN (Virtual eXtensible Local Area Network) tunneling packet type.
+    TUNNEL_VXLAN = auto()
+    #: NVGRE (Network Virtualization using Generic Routing Encapsulation) tunneling packet type.
+    TUNNEL_NVGRE = auto()
+    #: GENEVE (Generic Network Virtualization Encapsulation) tunneling packet type.
+    TUNNEL_GENEVE = auto()
+    #: Tunneling packet type of Teredo, VXLAN (Virtual eXtensible Local Area Network) or GRE
+    #: (Generic Routing Encapsulation) could be recognized as this packet type, if they can not be
+    #: recognized independently as of hardware capability.
+    TUNNEL_GRENAT = auto()
+    #: GTP-C (GPRS Tunnelling Protocol) control tunneling packet type.
+    TUNNEL_GTPC = auto()
+    #: GTP-U (GPRS Tunnelling Protocol) user data tunneling packet type.
+    TUNNEL_GTPU = auto()
+    #: ESP (IP Encapsulating Security Payload) tunneling packet type.
+    TUNNEL_ESP = auto()
+    #: L2TP (Layer 2 Tunneling Protocol) tunneling packet type.
+    TUNNEL_L2TP = auto()
+    #: VXLAN-GPE (VXLAN Generic Protocol Extension) tunneling packet type.
+    TUNNEL_VXLAN_GPE = auto()
+    #: MPLS-in-UDP tunneling packet type (RFC 7510).
+    TUNNEL_MPLS_IN_UDP = auto()
+    #: MPLS-in-GRE tunneling packet type (RFC 4023).
+    TUNNEL_MPLS_IN_GRE = auto()
+    #: No tunnel information found on the packet.
+    TUNNEL_UNKNOWN = auto()
+
+    # Inner L2
+    #: Ethernet packet type. This is used for inner packet type only.
+    INNER_L2_ETHER = auto()
+    #: Ethernet packet type with VLAN (Virtual Local Area Network) tag.
+    INNER_L2_ETHER_VLAN = auto()
+    #: QinQ packet type.
+    INNER_L2_ETHER_QINQ = auto()
+    #: No inner L2 information found on the packet.
+    INNER_L2_UNKNOWN = auto()
+
+    # Inner L3
+    #: IP (Internet Protocol) version 4 packet type. This is used for inner packet only, and does
+    #: not contain any header option.
+    INNER_L3_IPV4 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for inner packet only, and
+    #: contains header options.
+    INNER_L3_IPV4_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for inner packet only, and does
+    #: not contain any extension header.
+    INNER_L3_IPV6 = auto()
+    #: IP (Internet Protocol) version 4 packet type. This is used for inner packet only, and may or
+    #: may not contain header options.
+    INNER_L3_IPV4_EXT_UNKNOWN = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for inner packet only, and
+    #: contains extension headers.
+    INNER_L3_IPV6_EXT = auto()
+    #: IP (Internet Protocol) version 6 packet type. This is used for inner packet only, and may or
+    #: may not contain extension headers.
+    INNER_L3_IPV6_EXT_UNKNOWN = auto()
+    #: No inner L3 information found on the packet.
+    INNER_L3_UNKNOWN = auto()
+
+    # Inner L4
+    #: TCP (Transmission Control Protocol) packet type. This is used for inner packet only.
+    INNER_L4_TCP = auto()
+    #: UDP (User Datagram Protocol) packet type. This is used for inner packet only.
+    INNER_L4_UDP = auto()
+    #: Fragmented IP (Internet Protocol) packet type. This is used for inner packet only, and may
+    #: or maynot have a layer 4 packet.
+    INNER_L4_FRAG = auto()
+    #: SCTP (Stream Control Transmission Protocol) packet type. This is used for inner packet only.
+    INNER_L4_SCTP = auto()
+    #: ICMP (Internet Control Message Protocol) packet type. This is used for inner packet only.
+    INNER_L4_ICMP = auto()
+    #: Non-fragmented IP (Internet Protocol) packet type. It is used for inner packet only, and may
+    #: or may not have other unknown layer 4 packet types.
+    INNER_L4_NONFRAG = auto()
+    #: No inner L4 information found on the packet.
+    INNER_L4_UNKNOWN = auto()
+
+    @classmethod
+    def from_str_list(cls, arr: list[str]) -> Self:
+        """Makes an instance from a list containing the flag members.
+
+        Args:
+            arr: A list of strings containing packet types.
+
+        Returns:
+            A new instance of the flag.
+        """
+        flag = cls(0)
+        for name in arr:
+            if hasattr(cls, name):
+                flag |= cls[name]
+        return flag
+
+    @classmethod
+    def make_parser(cls, hw: bool) -> ParserFn:
+        """Makes a parser function.
+
+        Args:
+            hw: Whether to make a parser for hardware ptypes or software ptypes. If :data:`True`,
+                hardware ptypes will be collected, otherwise software pytpes will.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        return TextParser.wrap(
+            TextParser.wrap(TextParser.find(f"{'hw' if hw else 'sw'} ptype: ([^-]+)"), str.split),
+            cls.from_str_list,
+        )
+
+
+@dataclass
+class TestPmdVerbosePacket(TextParser):
+    """Packet information provided by verbose output in Testpmd.
+
+    This dataclass expects that packet information be prepended with the starting line of packet
+    bursts. Specifically, the line that reads "port X/queue Y: sent/received Z packets".
+    """
+
+    #: ID of the port that handled the packet.
+    port_id: int = field(metadata=TextParser.find_int(r"port (\d+)/queue \d+"))
+    #: ID of the queue that handled the packet.
+    queue_id: int = field(metadata=TextParser.find_int(r"port \d+/queue (\d+)"))
+    #: Whether the packet was received or sent by the queue/port.
+    was_received: bool = field(metadata=TextParser.find(r"received \d+ packets"))
+    #:
+    src_mac: str = field(metadata=TextParser.find(f"src=({REGEX_FOR_MAC_ADDRESS})"))
+    #:
+    dst_mac: str = field(metadata=TextParser.find(f"dst=({REGEX_FOR_MAC_ADDRESS})"))
+    #: Memory pool the packet was handled on.
+    pool: str = field(metadata=TextParser.find(r"pool=(\S+)"))
+    #: Packet type in hex.
+    p_type: int = field(metadata=TextParser.find_int(r"type=(0x[a-fA-F\d]+)"))
+    #:
+    length: int = field(metadata=TextParser.find_int(r"length=(\d+)"))
+    #: Number of segments in the packet.
+    nb_segs: int = field(metadata=TextParser.find_int(r"nb_segs=(\d+)"))
+    #: Hardware packet type.
+    hw_ptype: RtePTypes = field(metadata=RtePTypes.make_parser(hw=True))
+    #: Software packet type.
+    sw_ptype: RtePTypes = field(metadata=RtePTypes.make_parser(hw=False))
+    #:
+    l2_len: int = field(metadata=TextParser.find_int(r"l2_len=(\d+)"))
+    #:
+    ol_flags: PacketOffloadFlag = field(metadata=PacketOffloadFlag.make_parser())
+    #: RSS hash of the packet in hex.
+    rss_hash: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS hash=(0x[a-fA-F\d]+)")
+    )
+    #: RSS queue that handled the packet in hex.
+    rss_queue: int | None = field(
+        default=None, metadata=TextParser.find_int(r"RSS queue=(0x[a-fA-F\d]+)")
+    )
+    #:
+    l3_len: int | None = field(default=None, metadata=TextParser.find_int(r"l3_len=(\d+)"))
+    #:
+    l4_len: int | None = field(default=None, metadata=TextParser.find_int(r"l4_len=(\d+)"))
+
+
 class TestPmdShell(DPDKShell):
     """Testpmd interactive shell.
 
@@ -645,7 +1136,7 @@ def start(self, verify: bool = True) -> None:
                         "Not all ports came up after starting packet forwarding in testpmd."
                     )
 
-    def stop(self, verify: bool = True) -> None:
+    def stop(self, verify: bool = True) -> str:
         """Stop packet forwarding.
 
         Args:
@@ -656,6 +1147,9 @@ def stop(self, verify: bool = True) -> None:
         Raises:
             InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
                 forwarding results in an error.
+
+        Returns:
+            Output gathered from sending the stop command.
         """
         stop_cmd_output = self.send_command("stop")
         if verify:
@@ -665,6 +1159,7 @@ 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.")
+        return stop_cmd_output
 
     def get_devices(self) -> list[TestPmdDevice]:
         """Get a list of device names that are known to testpmd.
@@ -806,6 +1301,32 @@ def show_port_stats(self, port_id: int) -> TestPmdPortStats:
 
         return TestPmdPortStats.parse(output)
 
+    @staticmethod
+    def extract_verbose_output(output: str) -> list[TestPmdVerbosePacket]:
+        """Extract the verbose information present in given testpmd output.
+
+        This method extracts sections of verbose output that begin with the line
+        "port X/queue Y: sent/received Z packets" and end with the ol_flags of a packet.
+
+        Args:
+            output: Testpmd output that contains verbose information
+
+        Returns:
+            List of parsed packet information gathered from verbose information in `output`.
+        """
+        out: list[TestPmdVerbosePacket] = []
+        prev_header: str = ""
+        iter = re.finditer(
+            r"(?P<HEADER>(?:port \d+/queue \d+: (?:received|sent) \d+ packets)?)\s*"
+            r"(?P<PACKET>src=[\w\s=:-]+?ol_flags: [\w ]+)",
+            output,
+        )
+        for match in iter:
+            if match.group("HEADER"):
+                prev_header = match.group("HEADER")
+            out.append(TestPmdVerbosePacket.parse(f"{prev_header}\n{match.group('PACKET')}"))
+        return out
+
     def _close(self) -> None:
         """Overrides :meth:`~.interactive_shell.close`."""
         self.stop()
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 6b5d5a805f..c53cfdf31e 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -27,6 +27,12 @@
 from .exception import ConfigurationError
 
 REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
+_REGEX_FOR_COLON_SEP_MAC: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
+_REGEX_FOR_HYPHEN_SEP_MAC: str = r"(?:[\da-fA-F]{2}-){5,7}[\da-fA-F]{2}"
+_REGEX_FOR_DOT_SEP_MAC: str = r"(?:[\da-fA-F]{4}.){2}[\da-fA-F]{4}"
+REGEX_FOR_MAC_ADDRESS: str = (
+    rf"{_REGEX_FOR_COLON_SEP_MAC}|{_REGEX_FOR_HYPHEN_SEP_MAC}|{_REGEX_FOR_DOT_SEP_MAC}"
+)
 
 
 def expand_range(range_str: str) -> list[int]:
-- 
2.46.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v5 1/1] dts: add text parser for testpmd verbose output
  2024-09-18 17:05   ` [PATCH v5 1/1] dts: add text parser for testpmd verbose output jspewock
@ 2024-09-19  9:02     ` Juraj Linkeš
  2024-09-19 12:35     ` Juraj Linkeš
  1 sibling, 0 replies; 27+ messages in thread
From: Juraj Linkeš @ 2024-09-19  9:02 UTC (permalink / raw)
  To: jspewock, alex.chapman, paul.szczepanek, Luca.Vizzarro,
	Honnappa.Nagarahalli, wathsala.vithanage, probb, npratte, thomas,
	yoan.picchi
  Cc: dev

> diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py

> @@ -577,6 +577,497 @@ class TestPmdPortStats(TextParser):
>       tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
>   
>   
> +class PacketOffloadFlag(Flag):
> +    """Flag representing the Packet Offload Features Flags in DPDK.
> +
> +    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK
> +    located in lib/mbuf/rte_mbuf_core.h. It is expected that flag values in this class will match
> +    the values they are set to in said DPDK library with one exception; all values must be unique.
> +    For example, the definitions for unknown checksum flags in rte_mbuf_core.h are all set to
> +    :data:`0`, but it is valuable to distinguish between them in this framework. For this reason
> +    flags that are not unique in the DPDK library are set either to values within the
> +    RTE_MBUF_F_FIRST_FREE-RTE_MBUF_F_LAST_FREE range for Rx or shifted 61+ bits for Tx.
> +    """

> +    #: No information about the RX IP checksum.
> +    RTE_MBUF_F_RX_IP_CKSUM_UNKNOWN = 1 << 23

Good idea with the UKNOWN flag values.

> +    #: The IP checksum in the packet is wrong.
> +    RTE_MBUF_F_RX_IP_CKSUM_BAD = 1 << 4
> +    #: The IP checksum in the packet is valid.
> +    RTE_MBUF_F_RX_IP_CKSUM_GOOD = 1 << 7

I see you kept the order and just used the corresponding flag values. 
Makes sense.


> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_VXLAN = 1 << 45
> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_GRE = 2 << 45
> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_IPIP = 3 << 45
> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_GENEVE = 4 << 45
> +    """ TX packet with MPLS-in-UDP RFC 7510 header. """

This should be one line below after :#

> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_MPLSINUDP = 5 << 45
> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE = 6 << 45
> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_GTP = 7 << 45
> +    #:
> +    RTE_MBUF_F_TX_TUNNEL_ESP = 8 << 45

So the DPDK code mixes values withing flags? Would this work? We have to 
be careful with how we use this:
PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_VXLAN | 
PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_GRE == 
PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_IPIP
True

PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_VXLAN | 
PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_GRE is 
PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_IPIP
True

PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_VXLAN in 
PacketOffloadFlag.RTE_MBUF_F_TX_TUNNEL_IPIP
True

The combination of 1 | 2 == 3, even identity returns True and one flag 
is part of another. If we're looking at verbose_output.ol_flags and 
checking the RTE_MBUF_F_TX_TUNNEL_VXLAN flag, True would be returned for 
all flag that have the first bit set:
RTE_MBUF_F_TX_TUNNEL_VXLAN
RTE_MBUF_F_TX_TUNNEL_IPIP
RTE_MBUF_F_TX_TUNNEL_MPLSINUDP
RTE_MBUF_F_TX_TUNNEL_GTP

Do you know how this is handled in DPDK? Or how testpmd processes this 
to return the proper flag?

This mixing seems pretty wild to me (I guess this is to not waste space, 
since ULL is max 64 bits). We need to think this through thoroughly.


> +    #: TCP cksum of TX pkt. Computed by NIC.
> +    RTE_MBUF_F_TX_TCP_CKSUM = 1 << 52
> +    #: SCTP cksum of TX pkt. Computed by NIC.
> +    RTE_MBUF_F_TX_SCTP_CKSUM = 2 << 52
> +    #: UDP cksum of TX pkt. Computed by NIC.
> +    RTE_MBUF_F_TX_UDP_CKSUM = 3 << 52

This is the same thing as above.


> +    @classmethod
> +    def from_str_list(cls, arr: list[str]) -> Self:
> +        """Makes an instance from a list containing the flag members.
> +
> +        Args:
> +            arr: A list of strings containing ol_flag values.
> +
> +        Returns:
> +            A new instance of the flag.
> +        """
> +        flag = cls(0)
> +        for name in arr:
> +            if hasattr(cls, name):

So you used hasattr instead of cls[name] in cls. Is this to avoid the 
exception? I now realize that if we could ignore the exception then we 
won't need the condition.

The question is when the exception would be raised, or, in other words, 
what should we do when hasattr(cls, name) is False. If I understand this 
correctly, is it's False, then name is not among the flags and that 
means testpmd returned an unsupported flag, which shouldn't happen, but 
if it does in the future, we would be better off throwing an exception, 
or at very least, log a warning, so that we have an indication that we 
need to add support for a new flag.

> +                flag |= cls[name]
> +        return flag
> +
> +    @classmethod
> +    def make_parser(cls) -> ParserFn:
> +        """Makes a parser function.
> +
> +        Returns:
> +            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
> +                parser function that makes an instance of this flag from text.
> +        """
> +        return TextParser.wrap(
> +            TextParser.wrap(TextParser.find(r"ol_flags: ([^\n]+)"), str.split),
> +            cls.from_str_list,
> +        )

The RSSOffloadTypesFlag does the split in its from_list_string method. 
Do we want to do the same here?

Maybe could create a ParsableFlag (or Creatable? Or something else) 
superclass that would implement these from_* methods (from_list_string, 
from_str) and subclass it. Flags should be subclassable if they don't 
contain members.

The superclass would be useful so that we don't redefine the same method 
over and over and so that it's clear what's already available.


> @@ -656,6 +1147,9 @@ def stop(self, verify: bool = True) -> None:
>           Raises:
>               InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
>                   forwarding results in an error.
> +
> +        Returns:
> +            Output gathered from sending the stop command.

This not just from sending the stop command, but everything else that 
preceded (when collecting the verbose output), right?


> diff --git a/dts/framework/utils.py b/dts/framework/utils.py

> @@ -27,6 +27,12 @@
>   from .exception import ConfigurationError
>   
>   REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
> +_REGEX_FOR_COLON_SEP_MAC: str = r"(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}"
> +_REGEX_FOR_HYPHEN_SEP_MAC: str = r"(?:[\da-fA-F]{2}-){5,7}[\da-fA-F]{2}"

{5,7} should be just 5 repetitions. When could it be more?


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v5 1/1] dts: add text parser for testpmd verbose output
  2024-09-18 17:05   ` [PATCH v5 1/1] dts: add text parser for testpmd verbose output jspewock
  2024-09-19  9:02     ` Juraj Linkeš
@ 2024-09-19 12:35     ` Juraj Linkeš
  1 sibling, 0 replies; 27+ messages in thread
From: Juraj Linkeš @ 2024-09-19 12:35 UTC (permalink / raw)
  To: jspewock, alex.chapman, paul.szczepanek, Luca.Vizzarro,
	Honnappa.Nagarahalli, wathsala.vithanage, probb, npratte, thomas,
	yoan.picchi
  Cc: dev

> diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py

> @@ -577,6 +577,497 @@ class TestPmdPortStats(TextParser):
>       tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
>   
>   
> +class PacketOffloadFlag(Flag):
> +    """Flag representing the Packet Offload Features Flags in DPDK.
> +
> +    Values in this class are taken from the definitions in the RTE MBUF core library in DPDK
> +    located in lib/mbuf/rte_mbuf_core.h. It is expected that flag values in this class will match

Another minor improvement: we should put the file path into double 
backticks. Looking at DPDK docs, that's what they use for file paths, so 
``lib/mbuf/rte_mbuf_core.h``.

There's also a References section that supported by google docstrings. I 
don't know exactly what to put in it, but I'll try to use it in my 
patch. I imagine a list of files (or functions) would be useful - we 
could put the DPDK sources and testpmd functions that are relevant there.


^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2024-09-19 12:35 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-07-29 20:39 [PATCH v1 0/1] dts: testpmd verbose parser jspewock
2024-07-29 20:39 ` [PATCH v1 1/1] dts: add text parser for testpmd verbose output jspewock
2024-07-30 13:34 ` [PATCH v2 0/1] dts: testpmd verbose parser jspewock
2024-07-30 13:34   ` [PATCH v2 1/1] dts: add text parser for testpmd verbose output jspewock
2024-07-30 15:41     ` Nicholas Pratte
2024-07-30 21:30       ` Jeremy Spewock
2024-08-02 14:54         ` Nicholas Pratte
2024-08-02 17:38           ` Jeremy Spewock
2024-08-05 13:20             ` Nicholas Pratte
2024-07-30 21:33     ` Jeremy Spewock
2024-08-01  8:43       ` Luca Vizzarro
2024-08-02 13:40         ` Jeremy Spewock
2024-08-01  8:41     ` Luca Vizzarro
2024-08-02 13:35       ` Jeremy Spewock
2024-08-08 20:36 ` [PATCH v3 0/1] dts: testpmd verbose parser jspewock
2024-08-08 20:36   ` [PATCH v3 1/1] dts: add text parser for testpmd verbose output jspewock
2024-08-08 21:49     ` Jeremy Spewock
2024-08-12 17:32       ` Nicholas Pratte
2024-09-09 11:44     ` Juraj Linkeš
2024-09-17 13:40       ` Jeremy Spewock
2024-09-18  8:09         ` Juraj Linkeš
2024-09-18 16:34 ` [PATCH v4 0/1] dts: testpmd verbose parser jspewock
2024-09-18 16:34   ` [PATCH v4 1/1] dts: add text parser for testpmd verbose output jspewock
2024-09-18 17:05 ` [PATCH v5 0/1] dts: testpmd verbose parser jspewock
2024-09-18 17:05   ` [PATCH v5 1/1] dts: add text parser for testpmd verbose output jspewock
2024-09-19  9:02     ` Juraj Linkeš
2024-09-19 12:35     ` Juraj Linkeš

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).