From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id AD05246766; Fri, 16 May 2025 22:19:27 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id D871C40664; Fri, 16 May 2025 22:19:09 +0200 (CEST) Received: from mail-qk1-f169.google.com (mail-qk1-f169.google.com [209.85.222.169]) by mails.dpdk.org (Postfix) with ESMTP id 8997B40674 for ; Fri, 16 May 2025 22:19:08 +0200 (CEST) Received: by mail-qk1-f169.google.com with SMTP id af79cd13be357-7c5445cb72dso28767985a.3 for ; Fri, 16 May 2025 13:19:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1747426748; x=1748031548; darn=dpdk.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=XXIL+sUbFJhZtUxAzazg7rV79z+b9gvgQ/7gptiJEKg=; b=gzcB6qYNiYJMmt2Ko0sHsSA8vTMJbq0mIyiUo01ia9thn0wyn5yAwvc9T3FB23w3bR luEfXbMYKoS0Clrd51w1TA0pjevrCj1lq1Vi9KSbj4km34822Ugi7ba40Q5rhj6XVvOQ zMM4mzFaNFtNopMCNIycAO7Q2CxgkR1Iv72dc= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747426748; x=1748031548; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=XXIL+sUbFJhZtUxAzazg7rV79z+b9gvgQ/7gptiJEKg=; b=umnOv2mkovvVUa+hfKRGE19yBSeCxtbS9V/1MrCTJgMn8EPSqjNlWPja+JKXul5/Ky hojS2X2yDutnS0GAk36Yt/pKa/+XTpivaFrhqxbf0Tg6aS6UvdTlnyrm/f52Rw8mCrkf OBYhev+0RNiw2kw3WU6W12DPLyi/TA4mcKF0pXBog42iPpkN2yVgiB9/jwPl7vpoJf9C +RchH2/2J0ruzD6Z/fqiP1ydmr9R2UmwC16g3WmivBOwj9kCjIAEd2NEfopSevo1MIdx KSZ4O2vQgdLUilxjyJoS07dd0Ei4GZTXEwuC7QfGHqObgKBku9FzBc2cLbXTLXb73Vfu P+2w== X-Gm-Message-State: AOJu0YwJTHFC3mmMddOco5bESB0UpvoKZqQ+uM8qfA43WzYdr0LwixBS 6rHHbnTOUxRxdOT1OMusr1SbH6H4a0IJnvEsXdMHXx+R0Zi7E6Zn+HigbeZrc5CUm0A= X-Gm-Gg: ASbGnctdulZQYJyLbVEFvLTIj2B2G1Ac9UcEJbMu8Zq7VmxwPoMM04KEWuZNTM3EXkG BDl8B2G+l8vtH8zjzC30yh9yclv251KCcS/00TLVZeux+1ReCJhUcyvrfXz/D2KBdVUY3yuv07U kolcDVFalKQ/AoWVHS8u9SPg3BBZaHGWbqqKbOcpsd5NEiui0So9fXzWQSIfo5x6wA+esUceL0Q GFJb6MVXcZozHdplnr/B0u+evJBQ9Npk5wRbS6muPwCrpY0Eea6JIn7WnP2okFxm19L5Y9ZLTdW Y/zyJVu1uzy2Q00P4CZDZnoWrhggLsUjSWlCePR6ZxYw9b9WzVAPFTg4qZGAKbE= X-Google-Smtp-Source: AGHT+IFksUfHuEoEtO4h5J9G/kqtKCa1Y07iYvpIoG4q4Duoexc2CVSTC0XTrrLBuKGXknLHwhjN6w== X-Received: by 2002:a05:620a:608d:b0:7c3:d798:e8ae with SMTP id af79cd13be357-7cd4671a909mr323721785a.2.1747426747719; Fri, 16 May 2025 13:19:07 -0700 (PDT) Received: from localhost.unh.edu ([2606:4100:3880:1271:e2f8:4ec3:8bf3:864c]) by smtp.gmail.com with ESMTPSA id af79cd13be357-7cd467d93d5sm159673685a.31.2025.05.16.13.19.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 16 May 2025 13:19:07 -0700 (PDT) From: Nicholas Pratte To: stephen@networkplumber.org, dmarx@iol.unh.edu, luca.vizzarro@arm.com, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com, Honnappa.Nagarahalli@arm.com, thomas@monjalon.net, thomas.wilks@arm.com, probb@iol.unh.edu Cc: dev@dpdk.org, Nicholas Pratte Subject: [RFC v2 5/6] dts: add trex traffic generator to dts framework Date: Fri, 16 May 2025 16:18:33 -0400 Message-ID: <20250516201834.626206-6-npratte@iol.unh.edu> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20250516201834.626206-1-npratte@iol.unh.edu> References: <20250423194011.1447679-1-npratte@iol.unh.edu> <20250516201834.626206-1-npratte@iol.unh.edu> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Implement the TREX traffic generator for use in the DTS framework. The provided implementation leverages TREX's stateless API automation library, via use of a Python shell. As such, limitation to specific TREX versions may be be needed. The DTS context has been modified to include a performance traffic generator in addition to a functional traffic generator. Bugzilla ID: 1697 Signed-off-by: Nicholas Pratte --- dts/framework/config/test_run.py | 20 +- dts/framework/context.py | 11 +- dts/framework/test_run.py | 27 +- dts/framework/test_suite.py | 6 +- .../traffic_generator/__init__.py | 22 +- .../testbed_model/traffic_generator/trex.py | 292 ++++++++++++++++++ 6 files changed, 359 insertions(+), 19 deletions(-) create mode 100644 dts/framework/testbed_model/traffic_generator/trex.py diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py index b6e4099eeb..3e09005338 100644 --- a/dts/framework/config/test_run.py +++ b/dts/framework/config/test_run.py @@ -396,6 +396,8 @@ class TrafficGeneratorType(str, Enum): #: SCAPY = "SCAPY" + #: + TREX = "TREX" class TrafficGeneratorConfig(FrozenModel): @@ -404,6 +406,8 @@ class TrafficGeneratorConfig(FrozenModel): #: The traffic generator type the child class is required to define to be distinguished among #: others. type: TrafficGeneratorType + remote_path: PurePath + config: PurePath class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig): @@ -412,8 +416,16 @@ class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig): type: Literal[TrafficGeneratorType.SCAPY] +class TrexTrafficGeneratorConfig(TrafficGeneratorConfig): + """TREX traffic generator specific configuration.""" + + type: Literal[TrafficGeneratorType.TREX] + + #: A union type discriminating traffic generators by the `type` field. -TrafficGeneratorConfigTypes = Annotated[ScapyTrafficGeneratorConfig, Field(discriminator="type")] +TrafficGeneratorConfigTypes = Annotated[ + TrexTrafficGeneratorConfig, ScapyTrafficGeneratorConfig, Field(discriminator="type") +] #: Comma-separated list of logical cores to use. An empty string or ```any``` means use all lcores. LogicalCores = Annotated[ @@ -461,8 +473,10 @@ class TestRunConfiguration(FrozenModel): #: The DPDK configuration used to test. dpdk: DPDKConfiguration - #: The traffic generator configuration used to test. - traffic_generator: TrafficGeneratorConfigTypes + #: The traffic generator configuration used for functional tests. + func_traffic_generator: TrafficGeneratorConfig + #: The traffic generator configuration used for performance tests. + perf_traffic_generator: TrafficGeneratorConfig #: Whether to run performance tests. perf: bool #: Whether to run functional tests. diff --git a/dts/framework/context.py b/dts/framework/context.py index 4360bc8699..fb7ded2a10 100644 --- a/dts/framework/context.py +++ b/dts/framework/context.py @@ -16,7 +16,12 @@ if TYPE_CHECKING: from framework.remote_session.dpdk import DPDKBuildEnvironment, DPDKRuntimeEnvironment - from framework.testbed_model.traffic_generator.traffic_generator import TrafficGenerator + from framework.testbed_model.traffic_generator.capturing_traffic_generator import ( + CapturingTrafficGenerator, + ) + from framework.testbed_model.traffic_generator.performance_traffic_generator import ( + PerformanceTrafficGenerator, + ) P = ParamSpec("P") @@ -69,7 +74,9 @@ class Context: topology: Topology dpdk_build: "DPDKBuildEnvironment" dpdk: "DPDKRuntimeEnvironment" - tg: "TrafficGenerator" + tg_dpdk: "DPDKRuntimeEnvironment" + func_tg: "CapturingTrafficGenerator" + perf_tg: "PerformanceTrafficGenerator" local: LocalContext = field(default_factory=LocalContext) shell_pool: ShellPool = field(default_factory=ShellPool) diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py index 0fdc57ea9c..be1476081a 100644 --- a/dts/framework/test_run.py +++ b/dts/framework/test_run.py @@ -107,7 +107,7 @@ from types import MethodType from typing import ClassVar, Protocol, Union -from framework.config.test_run import TestRunConfiguration +from framework.config.test_run import TestRunConfiguration, TrafficGeneratorType from framework.context import Context, init_ctx from framework.exception import ( InternalError, @@ -204,10 +204,22 @@ def __init__( dpdk_build_env = DPDKBuildEnvironment(config.dpdk.build, sut_node) dpdk_runtime_env = DPDKRuntimeEnvironment(config.dpdk, sut_node, dpdk_build_env) - traffic_generator = create_traffic_generator(config.traffic_generator, tg_node) + # There is definitely a better way to do this. + tg_dpdk_runtime_env = None + if config.perf: + tg_dpdk_runtime_env = DPDKRuntimeEnvironment(config.dpdk, tg_node, None) + func_traffic_generator = create_traffic_generator(config.func_traffic_generator, tg_node) + perf_traffic_generator = create_traffic_generator(config.perf_traffic_generator, tg_node) self.ctx = Context( - sut_node, tg_node, topology, dpdk_build_env, dpdk_runtime_env, traffic_generator + sut_node, + tg_node, + topology, + dpdk_build_env, + dpdk_runtime_env, + tg_dpdk_runtime_env, + func_traffic_generator, + perf_traffic_generator, ) self.result = result self.selected_tests = list(self.config.filter_tests(tests_config)) @@ -345,7 +357,9 @@ def next(self) -> State | None: test_run.ctx.sut_node.setup() test_run.ctx.tg_node.setup() test_run.ctx.dpdk.setup(test_run.ctx.topology.sut_ports) - test_run.ctx.tg.setup(test_run.ctx.topology.tg_ports, test_run.ctx.topology.tg_port_ingress) + test_run.ctx.tg_dpdk.setup(test_run.ctx.topology.tg_ports) + test_run.ctx.func_tg.setup(test_run.ctx.topology.tg_ports) + test_run.ctx.perf_tg.setup(test_run.ctx.topology.tg_ports) self.result.ports = test_run.ctx.topology.sut_ports + test_run.ctx.topology.tg_ports self.result.sut_info = test_run.ctx.sut_node.node_info @@ -430,8 +444,9 @@ def description(self) -> str: def next(self) -> State | None: """Next state.""" - self.test_run.ctx.shell_pool.terminate_current_pool() - self.test_run.ctx.tg.teardown(self.test_run.ctx.topology.tg_ports) + self.test_run.ctx.tg_dpdk.teardown(self.test_run.ctx.topology.tg_ports) + self.test_run.ctx.func_tg.teardown(self.test_run.ctx.topology.tg_ports) + self.test_run.ctx.perf_tg.teardown(self.test_run.ctx.topology.tg_ports) self.test_run.ctx.dpdk.teardown(self.test_run.ctx.topology.sut_ports) self.test_run.ctx.tg_node.teardown() self.test_run.ctx.sut_node.teardown() diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py index e07c327b77..507df508cb 100644 --- a/dts/framework/test_suite.py +++ b/dts/framework/test_suite.py @@ -254,11 +254,11 @@ def send_packets_and_capture( A list of received packets. """ assert isinstance( - self._ctx.tg, CapturingTrafficGenerator + self._ctx.func_tg, CapturingTrafficGenerator ), "Cannot capture with a non-capturing traffic generator" # TODO: implement @requires for types of traffic generator packets = self._adjust_addresses(packets) - return self._ctx.tg.send_packets_and_capture( + return self._ctx.func_tg.send_packets_and_capture( packets, self._ctx.topology.tg_port_egress, self._ctx.topology.tg_port_ingress, @@ -276,7 +276,7 @@ def send_packets( packets: Packets to send. """ packets = self._adjust_addresses(packets) - self._ctx.tg.send_packets(packets, self._ctx.topology.tg_port_egress) + self._ctx.func_tg.send_packets(packets, self._ctx.topology.tg_port_egress) def get_expected_packets( self, diff --git a/dts/framework/testbed_model/traffic_generator/__init__.py b/dts/framework/testbed_model/traffic_generator/__init__.py index 2a259a6e6c..53125995cd 100644 --- a/dts/framework/testbed_model/traffic_generator/__init__.py +++ b/dts/framework/testbed_model/traffic_generator/__init__.py @@ -14,17 +14,27 @@ and a capturing traffic generator is required. """ -from framework.config.test_run import ScapyTrafficGeneratorConfig, TrafficGeneratorConfig +from framework.config.test_run import ( + ScapyTrafficGeneratorConfig as ScapyTrafficGeneratorConfig, +) +from framework.config.test_run import ( + TrafficGeneratorConfig, + TrafficGeneratorType, +) +from framework.config.test_run import ( + TrexTrafficGeneratorConfig as TrexTrafficGeneratorConfig, +) from framework.exception import ConfigurationError from framework.testbed_model.node import Node -from .capturing_traffic_generator import CapturingTrafficGenerator from .scapy import ScapyTrafficGenerator +from .traffic_generator import TrafficGenerator +from .trex import TrexTrafficGenerator def create_traffic_generator( traffic_generator_config: TrafficGeneratorConfig, node: Node -) -> CapturingTrafficGenerator: +) -> TrafficGenerator: """The factory function for creating traffic generator objects from the test run configuration. Args: @@ -37,8 +47,10 @@ def create_traffic_generator( Raises: ConfigurationError: If an unknown traffic generator has been setup. """ - match traffic_generator_config: - case ScapyTrafficGeneratorConfig(): + match traffic_generator_config.type: + case TrafficGeneratorType.SCAPY: return ScapyTrafficGenerator(node, traffic_generator_config, privileged=True) + case TrafficGeneratorType.TREX: + return TrexTrafficGenerator(node, traffic_generator_config, privileged=True) case _: raise ConfigurationError(f"Unknown traffic generator: {traffic_generator_config.type}") diff --git a/dts/framework/testbed_model/traffic_generator/trex.py b/dts/framework/testbed_model/traffic_generator/trex.py new file mode 100644 index 0000000000..b517333ab0 --- /dev/null +++ b/dts/framework/testbed_model/traffic_generator/trex.py @@ -0,0 +1,292 @@ +"""Implementation for TREX performance traffic generator.""" + +import time +from dataclasses import dataclass +from enum import Flag, auto +from typing import Callable, ClassVar + +from invoke.runners import Promise +from scapy.packet import Packet + +from framework.config.node import NodeConfiguration +from framework.config.test_run import TrafficGeneratorConfig +from framework.exception import SSHTimeoutError +from framework.remote_session.python_shell import PythonShell +from framework.remote_session.ssh_session import SSHSession +from framework.testbed_model.linux_session import LinuxSession +from framework.testbed_model.node import Node, create_session +from framework.testbed_model.traffic_generator.performance_traffic_generator import ( + PerformanceTrafficGenerator, + PerformanceTrafficStats, +) + + +@dataclass +class TrexPerPortStats: + """Performance statistics on a per port basis. + + Attributes: + opackets: Number of packets sent. + obytes: Number of egress bytes sent. + tx_bps: Maximum bits per second transmitted. + tx_pps: Number of transmitted packets sent. + """ + + tx_bps: float + tx_pps: float + + +@dataclass +class TrexPerformanceStats(PerformanceTrafficStats): + """Data structure to store performance statistics for a given test run. + + Attributes: + packet: The packet that was sent in the test run. + frame_size: The total length of the frame. (L2 downward) + tx_expected_bps: The expected bits per second on a given NIC. + tx_expected_cps: ... + tx_expected_pps: The expected packets per second of a given NIC. + tx_pps: The recorded maximum packets per second of the tested NIC. + tx_cps: The recorded maximum cps of the tested NIC + tx_bps: The recorded maximum bits per second of the tested NIC. + obytes: Total bytes output during test run. + port_stats: A list of :class:`TrexPerPortStats` provided by TREX. + """ + + packet: Packet + frame_size: int + + tx_expected_bps: float + tx_expected_cps: float + tx_expected_pps: float + + tx_pps: float + tx_cps: float + tx_bps: float + + port_stats: list[TrexPerPortStats] | None + + +class TrexStatelessTXModes(Flag): + """Flags indicating TREX instance's current trasmission mode.""" + + CONTINUOUS = auto() + SINGLE_BURST = auto() + MULTI_BURST = auto() + + +class TrexTrafficGenerator(PythonShell, PerformanceTrafficGenerator): + """TREX traffic generator. + + This implementation leverages the stateless API library provided in the TREX installation. + + Attributes: + stl_client_name: The name of the stateless client used in the stateless API. + packet_stream_name: The name of the stateless packet stream used in the stateless API. + timeout_duration: Internal timeout for connection to the TREX server. + """ + + _os_session: LinuxSession + _server_remote_session: SSHSession + _trex_server_process: Promise + + _tg_config: TrafficGeneratorConfig + _node_config: NodeConfiguration + + _python_indentation: ClassVar[str] = " " * 4 + + stl_client_name: ClassVar[str] = "client" + packet_stream_name: ClassVar[str] = "stream" + + _streaming_mode: TrexStatelessTXModes = TrexStatelessTXModes.CONTINUOUS + + timeout_duration: int + + def __init__( + self, tg_node: Node, config: TrafficGeneratorConfig, timeout_duration: int = 5, **kwargs + ) -> None: + """Initialize the TREX server. + + Initializes needed OS sessions for the creation of the TREX server process. + + Attributes: + tg_node: TG node the TREX instance is operating on. + config: Traffic generator config provided for TREX instance. + timeout_duration: Internal timeout for connection to the TREX server. + """ + super().__init__(node=tg_node, config=config, tg_node=tg_node, **kwargs) + self._node_config = tg_node.config + self._tg_config = config + self.timeout_duration = timeout_duration + + # Create TREX server session. + self._tg_node._other_sessions.append( + create_session(self._tg_node.config, "TREX Server.", self._logger) + ) + self._os_session = self._tg_node._other_sessions[0] + self._server_remote_session = self._os_session.remote_session + + def setup(self, ports): + """Initialize and start a TREX server process. + + Binds TG ports to vfio-pci and starts the trex process. + + Attributes: + ports: Related ports utilized in TG instance. + """ + super().setup(ports) + # Start TREX server process. + try: + self._logger.info("Starting TREX server process: sending 45 second sleep.") + server_command = [ + f"cd {self._tg_config.remote_path}; {self._tg_config.remote_path}/t-rex-64", + f"--cfg {self._tg_config.config} -i" + ] + privileged_command = self._os_session._get_privileged_command(" ".join(server_command)) + self._trex_server_process = self._server_remote_session._send_async_command( + privileged_command, timeout=None, env=None + ) + self._logger.info(f"Sending: '{privileged_command}") + time.sleep(45) + except SSHTimeoutError as e: + self._logger.exception("Failed to start TREX server process.", e) + + # Start Python shell. + self.start_application() + self.send_command("import os") + # Parent directory: /opt/v3.03/automation/trex_control_plane/interactive + self.send_command( + f"os.chdir('{self._tg_config.remote_path}/automation/trex_control_plane/interactive')" + ) + + # Import stateless API components. + imports = [ + "import trex", + "import trex.stl", + "import trex.stl.trex_stl_client", + "import trex.stl.trex_stl_streams", + "import trex.stl.trex_stl_packet_builder_scapy", + "from scapy.layers.l2 import Ether", + "from scapy.layers.inet import IP", + "from scapy.packet import Raw", + ] + self.send_command("\n".join(imports)) + + stateless_client = [ + f"{self.stl_client_name} = trex.stl.trex_stl_client.STLClient(", + f"username='{self._node_config.user}',", + "server='127.0.0.1',", + f"sync_timeout={self.timeout_duration}", + ")", + ] + self.send_command(f"\n{self._python_indentation}".join(stateless_client)) + self.send_command(f"{self.stl_client_name}.connect()") + + def teardown(self, ports) -> None: + """Teardown the TREX server and stateless implementation. + + close the TREX server process, and stop the Python shell. + + Attributes: + ports: Associated ports used by the TREX instance. + """ + super().teardown(ports) + self.send_command(f"{self.stl_client_name}.disconnect()") + self._os_session.send_command(f"pkill 't-rex-64'", privileged=True) + self._trex_server_process.join() + self.close() + + def _calculate_traffic_stats( + self, packet: Packet, duration: float, callback: Callable[[Packet, float], str] + ) -> PerformanceTrafficStats: + """Calculate the traffic statistics, using provided TG output. + + Takes in the statistics output provided by the stateless API implementation, and collects + them into a performance statistics data structure. + + Attributes: + packet: The packet being used for the performance test. + duration: The duration of the test. + callback: The callback function used to generate the traffic. + """ + # Convert to a dictionary. + stats_output = eval(callback(packet, duration)) + global_output = stats_output.get("global", "ERROR - DATA NOT FOUND") + + print(type(stats_output)) + print(stats_output) + + print(type(global_output)) + print(global_output) + return TrexPerformanceStats( + len(packet), + packet, + stats_output.get("tx_expected_bps", "ERROR - DATA NOT FOUND"), + stats_output.get("tx_expected_cps", "ERROR - DATA NOT FOUND"), + stats_output.get("tx_expected_pps", "ERROR - DATA NOT FOUND"), + stats_output.get("tx_pps", "ERROR - DATA NOT FOUND"), + stats_output.get("tx_cps", "ERROR - DATA NOT FOUND"), + stats_output.get("tx_bps", "ERROR - DATA NOT FOUND"), + stats_output.get("obytes", "ERROR - DATA NOT FOUND"), + None, + ) + + def set_streaming_mode(self, streaming_mode: TrexStatelessTXModes) -> None: + """Set the streaming mode of the TREX instance.""" + # Streaming modes are mutually exclusive. + self._streaming_mode = self._streaming_mode & streaming_mode + + def _generate_traffic(self, packet: Packet, duration: float) -> str: + """Generate traffic using provided packet. + + Uses the provided packet to generate traffic for the provided duration. + + Attributes: + packet: The packet being used for the performance test. + duration: The duration of the test being performed. + + Returns: + a string output of statistics provided by the traffic generator. + """ + """Implementation for :method:`generate_traffic_and_stats`.""" + streaming_mode = "" + if self._streaming_mode == TrexStatelessTXModes.CONTINUOUS: + streaming_mode = "STLTXCont" + elif self._streaming_mode == TrexStatelessTXModes.SINGLE_BURST: + streaming_mode = "STLTXSingleBurst" + elif self._streaming_mode == TrexStatelessTXModes.MULTI_BURST: + streaming_mode = "STLTXMultiBurst" + + packet_stream = [ + f"{self.packet_stream_name} = trex.stl.trex_stl_streams.STLStream(", + f"name='Test_{len(packet)}_bytes',", + f"packet=trex.stl.trex_stl_packet_builder_scapy.STLPktBuilder(pkt={packet.command()}),", + f"mode=trex.stl.trex_stl_streams.{streaming_mode}(),", + ")", + ] + self.send_command("\n".join(packet_stream)) + + # Prepare TREX console for next performance test. + procedure = [ + f"{self.stl_client_name}.connect()", + f"{self.stl_client_name}.reset(ports = [0, 1])", + f"{self.stl_client_name}.add_streams({self.packet_stream_name}, ports=[0, 1])", + f"{self.stl_client_name}.clear_stats()", + ")", + ] + self.send_command("\n".join(procedure)) + + start_test = [ + f"{self.stl_client_name}.start(ports=[0, 1], duration={duration})", + f"{self.stl_client_name}.wait_on_traffic(ports=[0, 1])", + ] + self._timeout = duration + self.send_command("\n".join(start_test), added_timeout=duration) + import time + + time.sleep(duration + 1) + + # Gather statistics output for parsing. + return self.send_command( + f"{self.stl_client_name}.get_stats(ports=[0, 1])", skip_first_line=True + ) -- 2.47.1