[PATCH v3 1/2] dts: add pvp multi paths vhost performance testsuite

Luca Vizzarro Luca.Vizzarro at arm.com
Thu Oct 29 17:53:03 CET 2026


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 <knimoji at iol.unh.edu>
> ---
>   ...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
> +
> +
> + at 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"]}""",
> +            )


More information about the dev mailing list