Hi Koushik,

thank you for the wait!

On 20/10/2026 18:30, Koushik Bhargav Nimoji wrote:
This patch adds the pvp multi paths vhost single core forward
performance testsuite, which launches testpmd using the vhost
and virtio virtual devices with one and two cores respectively,
and tests the vhost single core throughput performance.

Signed-off-by: Koushik Bhargav Nimoji <[email protected]>
---
  ...pvp_multi_paths_vhost_single_core_perf.rst |   8 +
  dts/configurations/tests_config.example.yaml  |  12 +
  dts/framework/testbed_model/linux_session.py  |   4 +
  dts/framework/testbed_model/os_session.py     |   4 +
  ..._pvp_multi_paths_vhost_single_core_perf.py | 467 ++++++++++++++++++
  5 files changed, 495 insertions(+)
  create mode 100644 
doc/api/dts/tests.TestSuite_pvp_multi_paths_vhost_single_core_perf.rst
  create mode 100644 
dts/tests/TestSuite_pvp_multi_paths_vhost_single_core_perf.py

diff --git 
a/doc/api/dts/tests.TestSuite_pvp_multi_paths_vhost_single_core_perf.rst 
b/doc/api/dts/tests.TestSuite_pvp_multi_paths_vhost_single_core_perf.rst
new file mode 100644
index 0000000000..4fcc6b6c91
--- /dev/null
+++ b/doc/api/dts/tests.TestSuite_pvp_multi_paths_vhost_single_core_perf.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+
+pvp_multi_paths_vhost_single_core_perf Test Suite
+=================================================
+
+.. automodule:: tests.TestSuite_pvp_multi_paths_vhost_single_core_perf
+   :members:
+   :show-inheritance:
diff --git a/dts/configurations/tests_config.example.yaml 
b/dts/configurations/tests_config.example.yaml
index ce67385b0b..f765526442 100644
--- a/dts/configurations/tests_config.example.yaml
+++ b/dts/configurations/tests_config.example.yaml
@@ -18,6 +18,18 @@ hello_world:
  #       num_descriptors: 1024
  #       expected_mpps: 1.0
  #   delta_tolerance: 0.05
+# pvp_multi_paths_vhost_single_core_forward_perf:

Is this meant to be:

  pvp_multi_paths_vhost_single_core_perf

+#   test_parameters: # Add frame size / descriptor count combinations as needed
+#     - frame_size: 64
+#       num_descriptors: 512
+#       expected_mpps: 1.0 # Set millions of packets per second according to 
your devices expected throughput for this given frame size / descriptor count
+#     - frame_size: 64
+#       num_descriptors: 1024
+#       expected_mpps: 1.0
+#     - frame_size: 512
+#       num_descriptors: 1024
+#       expected_mpps: 1.0
+#   delta_tolerance: 0.05
  # pmd_rss:
  #   NUM_QUEUES: 4
  #   ACTUAL_KEY_SIZE: 52
diff --git a/dts/framework/testbed_model/linux_session.py 
b/dts/framework/testbed_model/linux_session.py
index 8cc0d09d01..b5cc0a78a9 100644
--- a/dts/framework/testbed_model/linux_session.py
+++ b/dts/framework/testbed_model/linux_session.py
@@ -460,3 +460,7 @@ def configure_ipv4_forwarding(self, enable: bool) -> None:
          """Overrides 
:meth:`~.os_session.OSSession.configure_ipv4_forwarding`."""
          state = 1 if enable else 0
          self.send_command(f"sysctl -w net.ipv4.ip_forward={state}", 
privileged=True)
+
+    def clean_up_vhost(self) -> None:
+        """Overrides :meth:`~.os_session.OSSession.clean_up_vhost`."""
+        self.send_command("rm -rf /tmp/vhost-net*", privileged=True)

I am not particularly pleased with the idea of deleting every vhost-net*, just thinking in case this would disrupt any other application. Would there be any better way of doing this?

diff --git a/dts/framework/testbed_model/os_session.py 
b/dts/framework/testbed_model/os_session.py
index 3f588acd44..a107bfdf1d 100644
--- a/dts/framework/testbed_model/os_session.py
+++ b/dts/framework/testbed_model/os_session.py
@@ -719,3 +719,7 @@ def get_pci_addr_of_crypto_vfs(self, pf_port: Port) -> 
list[str]:
              A list containing all of the PCI addresses of the VFs on the 
port. If the port has no
              VFs then the list will be empty.
          """
+
+    @abstractmethod
+    def clean_up_vhost(self) -> None:
+        """Deletes stale vhost-user Unix domain socket files."""
diff --git a/dts/tests/TestSuite_pvp_multi_paths_vhost_single_core_perf.py 
b/dts/tests/TestSuite_pvp_multi_paths_vhost_single_core_perf.py
new file mode 100644
index 0000000000..bc220b34b9
--- /dev/null
+++ b/dts/tests/TestSuite_pvp_multi_paths_vhost_single_core_perf.py
@@ -0,0 +1,467 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 University of New Hampshire
+
+"""Vhost-user single-core forwarding performance test suite.
+
+This suite measures the packet forwarding performance of DPDK's vhost-user PMD 
running on a
+single CPU core in a Physical-Virtual-Physical (PVP) topology. Performance is 
evaluated across
+multiple virtio-user backend options, including packed versus split ring 
formats, in-order packet
+processing, mergeable buffers, and SIMD vectorization. Test cases are 
parameterized using
+combinations of frame sizes, descriptor ring sizes, and target MPPS baselines 
specified in the
+test configuration. High-speed traffic is injected from an external generator 
through SUT physical
+interfaces, routed through Vhost and Virtio loops, and returned to the 
generator to measure
+aggregate MPPS.
+"""
+
+from typing import cast
+
+from scapy.layers.inet import IP
+from scapy.layers.l2 import Ether
+from scapy.packet import Raw
+
+from api.capabilities import (
+    LinkTopology,
+    requires_link_topology,
+)
+from api.packet import assess_performance_by_packet
+from api.params.eal import VirtualDevice
+from api.params.types import LogicalCoreList, RSSSetting, TestPmdParamsDict
+from api.test import get_current_test_case_name, verify, write_performance_json
+from api.test_suite import BaseConfig, TestSuite, perf_test
+from api.testpmd import TestPmd
+from api.testpmd.config import (
+    Params,
+    PortTopology,
+    RXRingParams,
+    SimpleForwardingModes,
+    TXRingParams,
+)
+
+
+class Config(BaseConfig):
+    """Performance test metrics."""
+
+    test_parameters: list[dict[str, int | float]] = [

dict[str, int | float] is too permissive.
/If you require the keys to be these ones then you need to explicitly specify them otherwise the parser will happily validate junk. Just a TypedDict will do the job here:

class TestParameter(TypedDict):
        frame_size: int
        ...

+        {"frame_size": 64, "num_descriptors": 1024, "expected_mpps": 1.00},
+        {"frame_size": 128, "num_descriptors": 1024, "expected_mpps": 1.00},
+        {"frame_size": 256, "num_descriptors": 1024, "expected_mpps": 1.00},
+        {"frame_size": 512, "num_descriptors": 1024, "expected_mpps": 1.00},
+        {"frame_size": 1024, "num_descriptors": 1024, "expected_mpps": 1.00},
+        {"frame_size": 1518, "num_descriptors": 1024, "expected_mpps": 1.00},
+    ]
+    delta_tolerance: float = 0.05
+
+
+@requires_link_topology(LinkTopology.TWO_LINKS)
+class TestPvpMultiPathsVhostSingleCorePerf(TestSuite):
+    """pvp multi paths vhost single core performance test suite."""
+
+    config: Config
+
+    def set_up_suite(self) -> None:
+        """Assign test parameters."""
+        self.test_parameters = self.config.test_parameters
+        self.delta_tolerance = self.config.delta_tolerance
+
+    def set_up_test_case(self) -> None:
+        """Delete stale vhost-user Unix domain socket files."""
+        self._ctx.sut_node.main_session.clean_up_vhost()

A test suite should not access the context from _ctx. This is more like meant to be __ctx, but due to some old rules we agreed to only stick to _ctx. Basically anything private or protected shouldn't be used in test suites. TestSuite provides all helpers etc just for the actual test suite.

Finally, ideally main_session shouldn't be accessed by test suites either but interfaces that serve the test suites. So the API, as the internal specification is bound to change we need to have a consistent API.

+    def _create_and_transmit(
+        self,
+        ring_format: int,
+        in_order: int,
+        buffers: int,
+        vectorized: int,
+        extra_args: TestPmdParamsDict,
+    ) -> None:
+        """Create testpmd instances with specified params and send traffic.
+
+        Args:
+            ring_format: selects the virtio queue descriptor layout.
+            in_order: toggles in-order packet processing.
+            buffers: enables mergeable rx buffers.
+            vectorized: enables SIMD vectorized rx/tx data paths.
+            extra_args: extra/generic TestPmd arguments.
+        """
+        sut_dpdk_driver = self._ctx.sut_node.config.ports[0].os_driver_for_dpdk
+        vhost_user_vdev = 
VirtualDevice("net_vhost0,iface=/tmp/vhost-net,queues=1")
+        virtio_user_vdev = VirtualDevice(
+            "net_virtio_user0,"
+            "mac=00:11:22:33:44:10,"
+            "path=/tmp/vhost-net,"
+            "queues=1,"
+            f"packed_vq={ring_format},"
+            f"mrg_rxbuf={buffers},"
+            f"in_order={in_order},"
+            f"vectorized={vectorized}"
+        )
+
+        for params in self.test_parameters:
+            frame_size = params["frame_size"]
+            num_descriptors = params["num_descriptors"]
+
+            default_args: TestPmdParamsDict = {
+                "tx_ring": TXRingParams(descriptors=int(num_descriptors)),
+                "rx_ring": RXRingParams(descriptors=int(num_descriptors)),
+            }
+
+            if sut_dpdk_driver == "mlx5_core":
+                default_args["burst"] = 64
+                default_args["mbcache"] = 512
+            elif sut_dpdk_driver == "i40e":
+                default_args["rx_queues"] = 1
+                default_args["tx_queues"] = 1

This could be a problematic bit as we tried our best in the past to make test suites as driver-agnostic as possible. Would these undermine that ability? Perhaps a comment is needed here.

+
+            vhost_params: TestPmdParamsDict = {
+                "prefix": "vhost",
+                "allowed_ports": [],
+                "memory_channels": 4,
+                "port_topology": PortTopology.chained,
+                "vdevs": [vhost_user_vdev],
+                "lcore_list": LogicalCoreList([1, 2]),
+                "nb_cores": 1,
+                **default_args,
+            }
+
+            virtio_params: TestPmdParamsDict = {
+                "prefix": "virtio",
+                "no_pci": True,
+                "memory_channels": 4,
+                "allowed_ports": [],
+                "vdevs": [virtio_user_vdev],
+                "lcore_list": LogicalCoreList([3, 4, 5]),
+                "nb_cores": 2,
+                **extra_args,
+                **default_args,
+            }
+
+            with (
+                TestPmd(**vhost_params) as vhost,
+                TestPmd(**virtio_params) as virtio,
+            ):
+                vhost.set_forward_mode(SimpleForwardingModes.mac)
+                virtio.set_forward_mode(SimpleForwardingModes.io)
+                vhost.set_portlist([0, 2, 1])
+
+                assert params["expected_mpps"] > 0, "Invalid expected mpps, must be 
greater than 0."
+                params["measured_mpps"] = round(
+                    self._transmit(vhost, virtio, int(frame_size), 
repetitions=5), 3
+                )
+                params["performance_delta"] = round(
+                    (float(params["measured_mpps"]) - 
float(params["expected_mpps"]))
+                    / float(params["expected_mpps"]),
+                    3,
+                )
+                params["pass"] = float(params["performance_delta"]) >= 
-self.delta_tolerance
+
+        self._produce_stats_table(self.test_parameters)
+        for params in self.test_parameters:
+            verify(
+                params["pass"] is True,
+                f"""Packets forwarded is less than {(1 - self.delta_tolerance) 
* 100}%
+                of the expected baseline.
+                Measured MPPS = {params["measured_mpps"]}
+                Expected MPPS = {params["expected_mpps"]}""",
+            )

Reply via email to