3 # Copyright (c) 2023 Cisco and/or its affiliates.
5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
7 # Licensed under the Apache License 2.0 or
8 # GNU General Public License v2.0 or later; you may not use this file
9 # except in compliance with one of these Licenses. You
10 # may obtain a copy of the Licenses at:
12 # http://www.apache.org/licenses/LICENSE-2.0
13 # https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
15 # Note: If this file is linked with Scapy, which is GPLv2+, your use of it
16 # must be under GPLv2+. If at any point in the future it is no longer linked
17 # with Scapy (or other GPLv2+ licensed software), you are free to choose
20 # Unless required by applicable law or agreed to in writing, software
21 # distributed under the License is distributed on an "AS IS" BASIS,
22 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 # See the License for the specific language governing permissions and
24 # limitations under the License.
26 """This module gets a traffic profile together with other parameters, reads
27 the profile and sends the traffic. At the end, it measures the packet loss and
37 0, "/opt/trex-core-3.03/scripts/automation/trex_control_plane/interactive/"
39 from trex.stl.api import STLClient, STLProfile, STLError
42 def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
43 """Return formatted, rounded latency.
45 :param lat_min: Min latency
46 :param lat_avg: Average latency
47 :param lat_max: Max latency
48 :param hdrh: Base64 encoded compressed HDRHistogram object.
53 :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
57 t_min = int(round(float(lat_min)))
61 t_avg = int(round(float(lat_avg)))
65 t_max = int(round(float(lat_max)))
69 return "/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
85 """Send traffic and measure packet loss and latency.
88 - reads the given traffic profile with streams,
89 - connects to the T-rex client,
91 - removes all existing streams,
92 - adds streams from the traffic profile to the ports,
93 - if the warm-up time is more than 0, sends the warm-up traffic, reads the
95 - clears the statistics from the client,
97 - waits for the defined time (or runs forever if async mode is defined),
99 - reads and displays the statistics and
100 - disconnects from the client.
102 :param profile_file: A python module with T-rex traffic profile.
103 :param framesize: Frame size.
104 :param duration: Duration of traffic run in seconds (-1=infinite).
105 :param rate: Traffic rate [percentage, pps, bps].
106 :param port_0: Port 0 on the traffic generator.
107 :param port_1: Port 1 on the traffic generator.
108 :param latency: With latency stats.
109 :param async_start: Start the traffic and exit.
110 :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
111 :param force: Force start regardless of ports state.
112 :param delay: Sleep overhead [s].
113 :type profile_file: str
114 :type framesize: int or str
115 :type duration: float
120 :type async_start: bool
121 :type traffic_directions: int
128 approximated_duration = 0.0
134 print(f"### Profile file:\n{profile_file}")
135 profile = STLProfile.load(
136 profile_file, direction=0, port_id=0, framesize=framesize,
139 streams = profile.get_streams()
141 print(f"Error while loading profile '{profile_file}'!")
149 # Prepare our ports (the machine has 0 <--> 1 with static route):
151 client.remove_all_streams()
153 if "macsrc" in profile_file:
154 client.set_port_attr(promiscuous=True)
155 if isinstance(framesize, int):
156 last_stream_a = int((len(streams) - 2) / 2)
157 last_stream_b = (last_stream_a * 2)
158 client.add_streams(streams[0:last_stream_a], ports=[port_0])
159 if traffic_directions > 1:
161 streams[last_stream_a:last_stream_b], ports=[port_1])
162 elif isinstance(framesize, str):
163 client.add_streams(streams[0:3], ports=[port_0])
164 if traffic_directions > 1:
165 client.add_streams(streams[3:6], ports=[port_1])
168 if isinstance(framesize, int):
169 client.add_streams(streams[last_stream_b], ports=[port_0])
170 if traffic_directions > 1:
172 streams[last_stream_b + 1], ports=[port_1])
173 elif isinstance(framesize, str):
176 # Disable latency if NIC does not support requested stream type
177 print("##### FAILED to add latency streams #####")
179 # Even for unidir, both ports are needed to see both rx and tx.
180 ports = [port_0, port_1]
182 # Clear the stats before injecting:
185 # Choose rate and start traffic:
187 ports=ports[:traffic_directions],
191 core_mask=STLClient.CORE_MASK_PIN,
195 # For async stop, we need to export the current snapshot.
196 for i in range(len(client.ports)):
197 xsnap = client.ports[i].get_xstats().reference_stats
198 print(f"Xstats snapshot {i}: {xsnap!r}")
200 time_start = time.monotonic()
201 # wait_on_traffic fails if duration stretches by 30 seconds or more.
202 # TRex has some overhead, wait some more.
203 time.sleep(duration + delay)
205 time_stop = time.monotonic()
206 approximated_duration = time_stop - time_start - delay
207 # Read the stats after the traffic stopped (or time up).
208 stats = client.get_stats()
209 if client.get_warnings():
210 for warning in client.get_warnings():
212 # Now finish the complete reset.
215 print("##### Statistics #####")
216 print(json.dumps(stats, indent=4, separators=(",", ": ")))
218 nr_ports = len(client.ports)
219 for i,j in zip(range(nr_ports)[0::2], range(nr_ports)[1::2]):
220 lost_r = stats[i]["opackets"] - stats[j]["ipackets"]
221 lost_l = stats[j]["opackets"] - stats[i]["ipackets"]
222 print(f"packets lost from {i} --> {j}: {lost_r} pkts")
223 print(f"packets lost from {j} --> {i}: {lost_l} pkts")
225 # Stats index is not a port number, but "pgid".
226 # We will take latency read from only first link.
228 lat_obj = stats["latency"][0]["latency"]
230 str(lat_obj["total_min"]), str(lat_obj["average"]),
231 str(lat_obj["total_max"]), str(lat_obj["hdrh"]))
232 # Do not bother with the other dir latency if unidir.
233 if traffic_directions > 1:
234 lat_obj = stats["latency"][1]["latency"]
236 str(lat_obj["total_min"]), str(lat_obj["average"]),
237 str(lat_obj["total_max"]), str(lat_obj["hdrh"]))
239 total_rcvd = stats["total"]["ipackets"]
240 total_sent = stats["total"]["opackets"]
243 print("T-Rex STL runtime error!", file=sys.stderr)
249 client.disconnect(stop_traffic=False, release_ports=True)
255 f"total_received={total_rcvd}; "
256 f"total_sent={total_sent}; "
257 f"frame_loss={total_sent - total_rcvd}; "
258 f"target_duration={duration!r}; "
259 f"approximated_duration={approximated_duration!r}; "
260 f"latency_stream_0(usec)={lat_a}; "
261 f"latency_stream_1(usec)={lat_b}; "
266 """Main function for the traffic generator using T-rex.
268 It verifies the given command line arguments and runs "simple_burst"
271 parser = argparse.ArgumentParser()
273 "-p", "--profile", required=True, type=str,
274 help="Python traffic profile."
277 "-d", "--duration", required=True, type=float,
278 help="Duration of traffic run."
281 "-s", "--frame_size", required=True,
282 help="Size of a Frame without padding and IPG."
285 "-r", "--rate", required=True,
286 help="Traffic rate with included units (pps)."
289 "--port_0", required=True, type=int,
290 help="Port 0 on the traffic generator."
293 "--port_1", required=True, type=int,
294 help="Port 1 on the traffic generator."
297 "--async_start", action="store_true", default=False,
298 help="Non-blocking call of the script."
301 "--latency", action="store_true", default=False,
302 help="Add latency stream."
305 "--traffic_directions", type=int, default=2,
306 help="Send bi- (2) or uni- (1) directional traffic."
309 "--force", action="store_true", default=False,
310 help="Force start regardless of ports state."
313 "--delay", required=True, type=float, default=0.0,
314 help="Delay assumed for traffic, sleep time is increased by this [s]."
317 args = parser.parse_args()
320 framesize = int(args.frame_size)
322 framesize = args.frame_size
325 profile_file=args.profile,
326 duration=args.duration,
331 latency=args.latency,
332 async_start=args.async_start,
333 traffic_directions=args.traffic_directions,
339 if __name__ == "__main__":