3 # Copyright (c) 2020 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 """This module gets T-Rex advanced stateful (astf) traffic profile together
17 with other parameters, reads the profile and sends the traffic. At the end, it
18 measures the packet loss and latency.
27 0, u"/opt/trex-core-2.73/scripts/automation/trex_control_plane/interactive/"
29 from trex.astf.api import *
32 def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
33 """Return formatted, rounded latency.
35 :param lat_min: Min latency
36 :param lat_avg: Average latency
37 :param lat_max: Max latency
38 :param hdrh: Base64 encoded compressed HDRHistogram object.
43 :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
47 t_min = int(round(float(lat_min)))
51 t_avg = int(round(float(lat_avg)))
55 t_max = int(round(float(lat_max)))
59 return u"/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
63 profile_file, duration, framesize, mult, warmup_time, port_0, port_1,
64 latency, async_start=False, traffic_directions=2):
65 """Send traffic and measure packet loss and latency.
68 - reads the given traffic profile with streams,
69 - connects to the T-rex astf client,
71 - removes all existing streams,
72 - adds streams from the traffic profile to the ports,
73 - if the warm-up time is more than 0, sends the warm-up traffic, reads the
75 - clears the statistics from the client,
77 - waits for the defined time (or runs forever if async mode is defined),
79 - reads and displays the statistics and
80 - disconnects from the client.
82 :param profile_file: A python module with T-rex traffic profile.
83 :param duration: Duration of traffic run in seconds (-1=infinite).
84 :param framesize: Frame size.
85 :param mult: Multiplier of profile CPS.
86 :param warmup_time: Traffic warm-up time in seconds, 0 = disable.
87 :param port_0: Port 0 on the traffic generator.
88 :param port_1: Port 1 on the traffic generator.
89 :param latency: With latency stats.
90 :param async_start: Start the traffic and exit.
91 :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
92 :type profile_file: str
94 :type framesize: int or str
96 :type warmup_time: float
100 :type async_start: bool
101 :type traffic_directions: int
115 approximated_duration = 0
119 # TODO: key-values pairs to the profile file
121 print(f"### Profile file:\n{profile_file}")
122 profile = ASTFProfile.load(profile_file, framesize=framesize)
124 print(f"Error while loading profile '{profile_file}'!")
129 client = ASTFClient()
132 # Acquire ports, stop the traffic, remove loaded traffic and clear
136 client.load_profile(profile)
139 if traffic_directions > 1:
144 # Clear the stats before injecting.
146 # Choose CPS and start traffic.
147 client.start(mult=mult, duration=warmup_time)
148 time_start = time.monotonic()
150 # Read the stats after the warmup duration (no sampling needed).
151 time.sleep(warmup_time)
152 stats[time.monotonic()-time_start] = client.get_stats()
154 if client.get_warnings():
155 for warning in client.get_warnings():
160 print(u"##### Warmup Statistics #####")
161 print(json.dumps(stats, indent=4, separators=(u",", u": ")))
163 # TODO: check stats format
164 stats = stats[sorted(stats.keys())[-1]]
165 lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
166 if traffic_directions > 1:
167 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
169 print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
170 if traffic_directions > 1:
171 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
173 # Clear the stats before injecting.
178 # Choose CPS and start traffic.
180 mult=mult, duration=duration, nc=True,
181 latency_pps=mult if latency else 0, client_mask=2**len(ports)-1
183 time_start = time.monotonic()
184 # t-rex starts the packet flow with the delay
185 stats[time.monotonic()-time_start] = client.get_stats(ports=[port_0])
186 while stats[sorted(stats.keys())[-1]][port_0][u"opackets"] == 0:
189 stats[time.monotonic() - time_start] = \
190 client.get_stats(ports=[port_0])
192 trex_start_time = list(sorted(stats.keys()))[-1]
193 time_start += trex_start_time
197 # For async stop, we need to export the current snapshot.
198 xsnap0 = client.ports[port_0].get_xstats().reference_stats
199 print(f"Xstats snapshot 0: {xsnap0!r}")
200 if traffic_directions > 1:
201 xsnap1 = client.ports[port_1].get_xstats().reference_stats
202 print(f"Xstats snapshot 1: {xsnap1!r}")
205 stats_sampling if stats_sampling < duration else duration
207 # Do not block until done.
208 while client.is_traffic_active(ports=ports):
210 stats[time.monotonic()-time_start] = \
211 client.get_stats(ports=ports)
213 stats_sampling if stats_sampling < duration else duration
216 # Read the stats after the test
217 stats[time.monotonic()-time_start] = \
218 client.get_stats(ports=ports)
220 if client.get_warnings():
221 for warning in client.get_warnings():
226 print(u"##### Statistics #####")
227 print(json.dumps(stats, indent=4, separators=(u",", u": ")))
229 approximated_duration = list(sorted(stats.keys()))[-1]
230 stats = stats[sorted(stats.keys())[-1]]
231 lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
232 if traffic_directions > 1:
233 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
235 # TODO: Latency measurement not used at this phase. This part will
236 # be aligned in another commit.
237 # Stats index is not a port number, but "pgid".
239 lat_obj = stats[u"latency"][0][u"hist"]
240 # TODO: Latency histogram is dictionary in astf mode,
241 # needs additional processing
243 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
244 str(lat_obj[u"max_usec"]), u"-")
245 lat_a_hist = str(lat_obj[u"histogram"])
246 if traffic_directions > 1:
247 lat_obj = stats[u"latency"][1][u"hist"]
249 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
250 str(lat_obj[u"max_usec"]), u"-")
251 lat_b_hist = str(lat_obj[u"histogram"])
253 if traffic_directions > 1:
255 stats[port_0][u"opackets"] + stats[port_1][u"opackets"]
257 stats[port_0][u"ipackets"] + stats[port_1][u"ipackets"]
258 client_stats = stats[u"traffic"][u"client"]
259 server_stats = stats[u"traffic"][u"server"]
260 # Some zero counters are not sent
261 # Active and established flows UDP/TCP
263 c_act_flows = client_stats[u"m_active_flows"]
264 c_est_flows = client_stats[u"m_est_flows"]
265 c_traffic_duration = client_stats.get(u"m_traffic_duration", 0)
266 l7_data = f"client_active_flows={c_act_flows}, "
267 l7_data += f"client_established_flows={c_est_flows}, "
268 l7_data += f"client_traffic_duration={c_traffic_duration}, "
270 # Too many packets in NIC rx queue
271 c_err_rx_throttled = client_stats.get(u"err_rx_throttled", 0)
272 l7_data += f"client_err_rx_throttled={c_err_rx_throttled}, "
273 # Number of client side flows that were not opened
274 # due to flow-table overflow
275 c_err_nf_throttled = client_stats.get(u"err_c_nf_throttled", 0)
276 l7_data += f"client_err_nf_throttled={c_err_nf_throttled}, "
278 c_err_flow_overflow = client_stats.get(u"err_flow_overflow", 0)
279 l7_data += f"client_err_flow_overflow={c_err_flow_overflow}, "
281 s_act_flows = server_stats[u"m_active_flows"]
282 s_est_flows = server_stats[u"m_est_flows"]
283 s_traffic_duration = server_stats.get(u"m_traffic_duration", 0)
284 l7_data += f"server_active_flows={s_act_flows}, "
285 l7_data += f"server_established_flows={s_est_flows}, "
286 l7_data += f"server_traffic_duration={s_traffic_duration}, "
288 # Too many packets in NIC rx queue
289 s_err_rx_throttled = server_stats.get(u"err_rx_throttled", 0)
290 l7_data += f"client_err_rx_throttled={s_err_rx_throttled}, "
291 if u"udp" in profile_file:
293 # Established connections
294 c_udp_connects = client_stats.get(u"udps_connects", 0)
295 l7_data += f"client_udp_connects={c_udp_connects}, "
297 c_udp_closed = client_stats.get(u"udps_closed", 0)
298 l7_data += f"client_udp_closed={c_udp_closed}, "
300 c_udp_sndbyte = client_stats.get(u"udps_sndbyte", 0)
301 l7_data += f"client_udp_tx_bytes={c_udp_sndbyte}, "
303 c_udp_sndpkt = client_stats.get(u"udps_sndpkt", 0)
304 l7_data += f"client_udp_tx_packets={c_udp_sndpkt}, "
306 c_udp_rcvbyte = client_stats.get(u"udps_rcvbyte", 0)
307 l7_data += f"client_udp_rx_bytes={c_udp_rcvbyte}, "
309 c_udp_rcvpkt = client_stats.get(u"udps_rcvpkt", 0)
310 l7_data += f"client_udp_rx_packets={c_udp_rcvpkt}, "
312 c_udp_keepdrops = client_stats.get(u"udps_keepdrops", 0)
313 l7_data += f"client_udp_keep_drops={c_udp_keepdrops}, "
315 # Accepted connections
316 s_udp_accepts = server_stats.get(u"udps_accepts", 0)
317 l7_data += f"server_udp_accepts={s_udp_accepts}, "
319 s_udp_closed = server_stats.get(u"udps_closed", 0)
320 l7_data += f"server_udp_closed={s_udp_closed}, "
322 s_udp_sndbyte = server_stats.get(u"udps_sndbyte", 0)
323 l7_data += f"server_udp_tx_bytes={s_udp_sndbyte}, "
325 s_udp_sndpkt = server_stats.get(u"udps_sndpkt", 0)
326 l7_data += f"server_udp_tx_packets={s_udp_sndpkt}, "
328 s_udp_rcvbyte = server_stats.get(u"udps_rcvbyte", 0)
329 l7_data += f"server_udp_rx_bytes={s_udp_rcvbyte}, "
331 s_udp_rcvpkt = server_stats.get(u"udps_rcvpkt", 0)
332 l7_data += f"server_udp_rx_packets={s_udp_rcvpkt}, "
333 elif u"tcp" in profile_file:
335 # Initiated connections
336 c_tcp_connatt = client_stats.get(u"tcps_connattempt", 0)
337 l7_data += f"client_tcp_connect_inits={c_tcp_connatt}, "
338 # Established connections
339 c_tcp_connects = client_stats.get(u"tcps_connects", 0)
340 l7_data += f"client_tcp_connects={c_tcp_connects}, "
342 c_tcp_closed = client_stats.get(u"tcps_closed", 0)
343 l7_data += f"client_tcp_closed={c_tcp_closed}, "
345 c_tcp_sndbyte = client_stats.get(u"tcps_sndbyte", 0)
346 l7_data += f"client_tcp_tx_bytes={c_tcp_sndbyte}, "
348 c_tcp_rcvbyte = client_stats.get(u"tcps_rcvbyte", 0)
349 l7_data += f"client_tcp_rx_bytes={c_tcp_rcvbyte}, "
351 # Accepted connections
352 s_tcp_accepts = server_stats.get(u"tcps_accepts", 0)
353 l7_data += f"server_tcp_accepts={s_tcp_accepts}, "
354 # Established connections
355 s_tcp_connects = server_stats.get(u"tcps_connects", 0)
356 l7_data += f"server_tcp_connects={s_tcp_connects}, "
358 s_tcp_closed = server_stats.get(u"tcps_closed", 0)
359 l7_data += f"server_tcp_closed={s_tcp_closed}, "
361 s_tcp_sndbyte = server_stats.get(u"tcps_sndbyte", 0)
362 l7_data += f"server_tcp_tx_bytes={s_tcp_sndbyte}, "
364 s_tcp_rcvbyte = server_stats.get(u"tcps_rcvbyte", 0)
365 l7_data += f"server_tcp_rx_bytes={s_tcp_rcvbyte}, "
367 total_sent = stats[port_0][u"opackets"]
368 total_rcvd = stats[port_1][u"ipackets"]
370 print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
371 if traffic_directions > 1:
372 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
375 print(u"T-Rex ASTF runtime error!", file=sys.stderr)
381 client.disconnect(stop_traffic=False, release_ports=True)
383 client.clear_profile()
386 f"trex_start_time={trex_start_time}, "
387 f"cps={mult!r}, total_received={total_rcvd}, "
388 f"total_sent={total_sent}, frame_loss={lost_a + lost_b}, "
389 f"approximated_duration={approximated_duration}, "
390 f"latency_stream_0(usec)={lat_a}, "
391 f"latency_stream_1(usec)={lat_b}, "
392 f"latency_hist_stream_0={lat_a_hist}, "
393 f"latency_hist_stream_1={lat_b_hist}, "
399 """Main function for the traffic generator using T-rex.
401 It verifies the given command line arguments and runs "simple_burst"
404 parser = argparse.ArgumentParser()
406 u"-p", u"--profile", required=True, type=str,
407 help=u"Python traffic profile."
410 u"-d", u"--duration", required=True, type=float,
411 help=u"Duration of traffic run."
414 u"-s", u"--frame_size", required=True,
415 help=u"Size of a Frame without padding and IPG."
418 u"-m", u"--mult", required=True, type=int,
419 help=u"Multiplier of profile CPS."
422 u"-w", u"--warmup_time", type=float, default=5.0,
423 help=u"Traffic warm-up time in seconds, 0 = disable."
426 u"--port_0", required=True, type=int,
427 help=u"Port 0 on the traffic generator."
430 u"--port_1", required=True, type=int,
431 help=u"Port 1 on the traffic generator."
434 u"--async_start", action=u"store_true", default=False,
435 help=u"Non-blocking call of the script."
438 u"--latency", action=u"store_true", default=False,
439 help=u"Add latency stream."
442 u"--traffic_directions", type=int, default=2,
443 help=u"Send bi- (2) or uni- (1) directional traffic."
446 args = parser.parse_args()
449 framesize = int(args.frame_size)
451 framesize = args.frame_size
454 profile_file=args.profile, duration=args.duration, framesize=framesize,
455 mult=args.mult, warmup_time=args.warmup_time, port_0=args.port_0,
456 port_1=args.port_1, latency=args.latency, async_start=args.async_start,
457 traffic_directions=args.traffic_directions
461 if __name__ == u"__main__":