<div dir="ltr">Reviewed-by: Andrew Bailey <<a href="mailto:abailey@iol.unh.edu" target="_blank">abailey@iol.unh.edu</a>></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Wed, Nov 5, 2025 at 5:37 PM Patrick Robb <<a href="mailto:probb@iol.unh.edu">probb@iol.unh.edu</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">From: Nicholas Pratte <<a href="mailto:npratte@iol.unh.edu" target="_blank">npratte@iol.unh.edu</a>><br>
<br>
Rework TG class hierarchy to include performance traffic generators.<br>
As such, methods specific to capturing traffic have been moved to the<br>
CapturingTrafficGenerator subclass.<br>
<br>
Bugzilla ID: 1697<br>
Signed-off-by: Nicholas Pratte <<a href="mailto:npratte@iol.unh.edu" target="_blank">npratte@iol.unh.edu</a>><br>
Signed-off-by: Patrick Robb <<a href="mailto:probb@iol.unh.edu" target="_blank">probb@iol.unh.edu</a>><br>
Reviewed-by: Dean Marx <<a href="mailto:dmarx@iol.unh.edu" target="_blank">dmarx@iol.unh.edu</a>><br>
Reviewed-by: Andrew Bailey <<a href="mailto:abailey@iol.unh.edu" target="_blank">abailey@iol.unh.edu</a>><br>
---<br>
.../capturing_traffic_generator.py | 34 +++++++++++<br>
.../performance_traffic_generator.py | 56 +++++++++++++++++++<br>
.../traffic_generator/traffic_generator.py | 38 -------------<br>
3 files changed, 90 insertions(+), 38 deletions(-)<br>
create mode 100644 dts/framework/testbed_model/traffic_generator/performance_traffic_generator.py<br>
<br>
diff --git a/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py<br>
index ec0993e6b7..734a66d1f3 100644<br>
--- a/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py<br>
+++ b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py<br>
@@ -65,6 +65,40 @@ def is_capturing(self) -> bool:<br>
"""This traffic generator can capture traffic."""<br>
return True<br>
<br>
+ def send_packet(self, packet: Packet, port: Port) -> None:<br>
+ """Send `packet` and block until it is fully sent.<br>
+<br>
+ Send `packet` on `port`, then wait until `packet` is fully sent.<br>
+<br>
+ Args:<br>
+ packet: The packet to send.<br>
+ port: The egress port on the TG node.<br>
+ """<br>
+ self.send_packets([packet], port)<br>
+<br>
+ def send_packets(self, packets: list[Packet], port: Port) -> None:<br>
+ """Send `packets` and block until they are fully sent.<br>
+<br>
+ Send `packets` on `port`, then wait until `packets` are fully sent.<br>
+<br>
+ Args:<br>
+ packets: The packets to send.<br>
+ port: The egress port on the TG node.<br>
+ """<br>
+ self._<a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(f"Sending packet{'s' if len(packets) > 1 else ''}.")<br>
+ self._logger.debug(get_packet_summaries(packets))<br>
+ self._send_packets(packets, port)<br>
+<br>
+ @abstractmethod<br>
+ def _send_packets(self, packets: list[Packet], port: Port) -> None:<br>
+ """The implementation of :method:`send_packets`.<br>
+<br>
+ The subclasses must implement this method which sends `packets` on `port`.<br>
+ The method should block until all `packets` are fully sent.<br>
+<br>
+ What fully sent means is defined by the traffic generator.<br>
+ """<br>
+<br>
def send_packets_and_capture(<br>
self,<br>
packets: list[Packet],<br>
diff --git a/dts/framework/testbed_model/traffic_generator/performance_traffic_generator.py b/dts/framework/testbed_model/traffic_generator/performance_traffic_generator.py<br>
new file mode 100644<br>
index 0000000000..09abe9a66b<br>
--- /dev/null<br>
+++ b/dts/framework/testbed_model/traffic_generator/performance_traffic_generator.py<br>
@@ -0,0 +1,56 @@<br>
+"""Traffic generators for performance tests which can generate a high number of packets."""<br>
+<br>
+from abc import abstractmethod<br>
+from dataclasses import dataclass<br>
+<br>
+from scapy.packet import Packet<br>
+<br>
+from .traffic_generator import TrafficGenerator<br>
+<br>
+<br>
+@dataclass(slots=True)<br>
+class PerformanceTrafficStats:<br>
+ """Data structure to store performance statistics for a given test run.<br>
+<br>
+ Attributes:<br>
+ tx_pps: Recorded tx packets per second.<br>
+ tx_bps: Recorded tx bytes per second.<br>
+ rx_pps: Recorded rx packets per second.<br>
+ rx_bps: Recorded rx bytes per second.<br>
+ frame_size: The total length of the frame.<br>
+ """<br>
+<br>
+ tx_pps: float<br>
+ tx_bps: float<br>
+ rx_pps: float<br>
+ rx_bps: float<br>
+<br>
+ frame_size: int | None = None<br>
+<br>
+<br>
+class PerformanceTrafficGenerator(TrafficGenerator):<br>
+ """An abstract base class for all performance-oriented traffic generators.<br>
+<br>
+ Provides an intermediary interface for performance-based traffic generator.<br>
+ """<br>
+<br>
+ @abstractmethod<br>
+ def calculate_traffic_and_stats(<br>
+ self,<br>
+ packet: Packet,<br>
+ duration: float,<br>
+ send_mpps: int | None = None,<br>
+ ) -> PerformanceTrafficStats:<br>
+ """Send packet traffic and acquire associated statistics.<br>
+<br>
+ If `send_mpps` is provided, attempt to transmit traffic at the `send_mpps` rate.<br>
+ Otherwise, attempt to transmit at line rate.<br>
+<br>
+ Args:<br>
+ packet: The packet to send.<br>
+ duration: Performance test duration (in seconds).<br>
+ send_mpps: The millions packets per second send rate.<br>
+<br>
+ Returns:<br>
+ Performance statistics of the generated test.<br>
+ """<br>
diff --git a/dts/framework/testbed_model/traffic_generator/traffic_generator.py b/dts/framework/testbed_model/traffic_generator/traffic_generator.py<br>
index cac119c183..e5f246df7a 100644<br>
--- a/dts/framework/testbed_model/traffic_generator/traffic_generator.py<br>
+++ b/dts/framework/testbed_model/traffic_generator/traffic_generator.py<br>
@@ -11,14 +11,10 @@<br>
from abc import ABC, abstractmethod<br>
from typing import Any<br>
<br>
-from scapy.packet import Packet<br>
-<br>
from framework.config.test_run import TrafficGeneratorConfig<br>
from framework.logger import DTSLogger, get_dts_logger<br>
from framework.testbed_model.node import Node<br>
-from framework.testbed_model.port import Port<br>
from framework.testbed_model.topology import Topology<br>
-from framework.utils import get_packet_summaries<br>
<br>
<br>
class TrafficGenerator(ABC):<br>
@@ -57,40 +53,6 @@ def teardown(self) -> None:<br>
"""Teardown the traffic generator."""<br>
self.close()<br>
<br>
- def send_packet(self, packet: Packet, port: Port) -> None:<br>
- """Send `packet` and block until it is fully sent.<br>
-<br>
- Send `packet` on `port`, then wait until `packet` is fully sent.<br>
-<br>
- Args:<br>
- packet: The packet to send.<br>
- port: The egress port on the TG node.<br>
- """<br>
- self.send_packets([packet], port)<br>
-<br>
- def send_packets(self, packets: list[Packet], port: Port) -> None:<br>
- """Send `packets` and block until they are fully sent.<br>
-<br>
- Send `packets` on `port`, then wait until `packets` are fully sent.<br>
-<br>
- Args:<br>
- packets: The packets to send.<br>
- port: The egress port on the TG node.<br>
- """<br>
- self._<a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(f"Sending packet{'s' if len(packets) > 1 else ''}.")<br>
- self._logger.debug(get_packet_summaries(packets))<br>
- self._send_packets(packets, port)<br>
-<br>
- @abstractmethod<br>
- def _send_packets(self, packets: list[Packet], port: Port) -> None:<br>
- """The implementation of :method:`send_packets`.<br>
-<br>
- The subclasses must implement this method which sends `packets` on `port`.<br>
- The method should block until all `packets` are fully sent.<br>
-<br>
- What fully sent means is defined by the traffic generator.<br>
- """<br>
-<br>
@property<br>
def is_capturing(self) -> bool:<br>
"""This traffic generator can't capture traffic."""<br>
-- <br>
2.49.0<br>
<br>
</blockquote></div>