3 # Copyright (c) 2020 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 Apache 2.
19 # Unless required by applicable law or agreed to in writing, software
20 # distributed under the License is distributed on an "AS IS" BASIS,
21 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 # See the License for the specific language governing permissions and
23 # limitations under the License.
25 """This module gets T-Rex advanced stateful (astf) traffic profile together
26 with other parameters, reads the profile and sends the traffic. At the end, it
27 parses for various counters.
36 0, u"/opt/trex-core-2.86/scripts/automation/trex_control_plane/interactive/"
38 from trex.astf.api import *
41 def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
42 """Return formatted, rounded latency.
44 :param lat_min: Min latency
45 :param lat_avg: Average latency
46 :param lat_max: Max latency
47 :param hdrh: Base64 encoded compressed HDRHistogram object.
52 :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
56 t_min = int(round(float(lat_min)))
60 t_avg = int(round(float(lat_avg)))
64 t_max = int(round(float(lat_max)))
68 return u"/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
82 """Send traffic and measure packet loss and latency.
85 - reads the given traffic profile with streams,
86 - connects to the T-rex astf client,
88 - removes all existing streams,
89 - adds streams from the traffic profile to the ports,
90 - clears the statistics from the client,
92 - waits for the defined time (or runs forever if async mode is defined),
93 - explicitly stops the traffic,
94 - reads and displays the statistics and
95 - disconnects from the client.
98 Contrary to stateless mode, ASTF profiles typically limit the number
99 of flows/transactions that can happen.
100 The caller is expected to set the duration parameter accordingly to
101 this limit and multiplier, including any overheads.
102 See *_traffic_duration output fields for TRex's measurement
103 of the real traffic duration (should be without any inactivity overheads).
104 If traffic has not ended by the final time, the traffic
105 is stopped explicitly, counters reflect the state just after the stop.
107 TODO: Support tests which focus only on some transaction phases,
108 e.g. TCP tests ignoring init and teardown separated by delays.
109 Currently, approximated time measures the whole traffic duration.
111 :param profile_file: A python module with T-rex traffic profile.
112 :param duration: Expected duration for all transactions to finish,
113 assuming only tolerable duration stretching happens.
114 This includes later start of later transactions
115 (according to TPS multiplier) and expected duration of each transaction.
116 Critically, this also includes any delay TRex shows when starting
117 traffic (but not the similar delay during stopping).
118 :param framesize: Frame size.
119 :param multiplier: Multiplier of profile CPS.
120 :param port_0: Port 0 on the traffic generator.
121 :param port_1: Port 1 on the traffic generator.
122 :param latency: With latency stats.
123 :param async_start: Start the traffic and exit.
124 :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
125 :type profile_file: str
126 :type duration: float
127 :type framesize: int or str
128 :type multiplier: int
132 :type async_start: bool
133 :type traffic_directions: int
146 approximated_duration = 0
150 # TODO: key-values pairs to the profile file
152 print(f"### Profile file:\n{profile_file}")
153 profile = ASTFProfile.load(profile_file, framesize=framesize)
155 print(f"Error while loading profile '{profile_file}'!")
160 client = ASTFClient()
163 # Acquire ports, stop the traffic, remove loaded traffic and clear
167 client.load_profile(profile)
170 if traffic_directions > 1:
173 # Clear the stats before injecting.
178 # Choose CPS and start traffic.
181 # Increase the input duration slightly,
182 # to ensure it does not end before sleep&stop below happens.
183 duration=duration + 0.1 if duration > 0 else duration,
185 latency_pps=int(multiplier) if latency else 0,
186 client_mask=2**len(ports)-1,
188 time_start = time.monotonic()
191 # For async stop, we need to export the current snapshot.
192 xsnap0 = client.ports[port_0].get_xstats().reference_stats
193 print(f"Xstats snapshot 0: {xsnap0!r}")
194 if traffic_directions > 1:
195 xsnap1 = client.ports[port_1].get_xstats().reference_stats
196 print(f"Xstats snapshot 1: {xsnap1!r}")
200 # Do not block yet, the existing transactions may take long time
201 # to finish. We need an action that is almost reset(),
202 # but without clearing stats.
203 client.stop(block=False)
204 client.stop_latency()
205 client.remove_rx_queue(client.get_all_ports())
206 # Now we can wait for the real traffic stop.
207 client.stop(block=True)
209 # Read the stats after the traffic stopped (or time up).
210 stats[time.monotonic() - time_start] = client.get_stats(
214 if client.get_warnings():
215 for warning in client.get_warnings():
218 # Now finish the complete reset.
221 print(u"##### Statistics #####")
222 print(json.dumps(stats, indent=4, separators=(u",", u": ")))
224 approximated_duration = list(sorted(stats.keys()))[-1]
225 stats = stats[sorted(stats.keys())[-1]]
226 lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
227 if traffic_directions > 1:
228 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
230 # TODO: Latency measurement not used at this phase. This part will
231 # be aligned in another commit.
232 # Stats index is not a port number, but "pgid".
234 lat_obj = stats[u"latency"][0][u"hist"]
235 # TODO: Latency histogram is dictionary in astf mode,
236 # needs additional processing
238 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
239 str(lat_obj[u"max_usec"]), u"-")
240 lat_a_hist = str(lat_obj[u"histogram"])
241 if traffic_directions > 1:
242 lat_obj = stats[u"latency"][1][u"hist"]
244 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
245 str(lat_obj[u"max_usec"]), u"-")
246 lat_b_hist = str(lat_obj[u"histogram"])
248 if traffic_directions > 1:
250 stats[port_0][u"opackets"] + stats[port_1][u"opackets"]
252 stats[port_0][u"ipackets"] + stats[port_1][u"ipackets"]
253 client_sent = stats[port_0][u"opackets"]
254 client_received = stats[port_0][u"ipackets"]
255 client_stats = stats[u"traffic"][u"client"]
256 server_stats = stats[u"traffic"][u"server"]
257 # Some zero counters are not sent
258 # Active and established flows UDP/TCP
260 c_act_flows = client_stats[u"m_active_flows"]
261 c_est_flows = client_stats[u"m_est_flows"]
262 c_traffic_duration = client_stats.get(u"m_traffic_duration", 0)
263 l7_data = f"client_active_flows={c_act_flows}; "
264 l7_data += f"client_established_flows={c_est_flows}; "
265 l7_data += f"client_traffic_duration={c_traffic_duration}; "
267 # Too many packets in NIC rx queue
268 c_err_rx_throttled = client_stats.get(u"err_rx_throttled", 0)
269 l7_data += f"client_err_rx_throttled={c_err_rx_throttled}; "
270 # Number of client side flows that were not opened
271 # due to flow-table overflow
272 c_err_nf_throttled = client_stats.get(u"err_c_nf_throttled", 0)
273 l7_data += f"client_err_nf_throttled={c_err_nf_throttled}; "
275 c_err_flow_overflow = client_stats.get(u"err_flow_overflow", 0)
276 l7_data += f"client_err_flow_overflow={c_err_flow_overflow}; "
278 s_act_flows = server_stats[u"m_active_flows"]
279 s_est_flows = server_stats[u"m_est_flows"]
280 s_traffic_duration = server_stats.get(u"m_traffic_duration", 0)
281 l7_data += f"server_active_flows={s_act_flows}; "
282 l7_data += f"server_established_flows={s_est_flows}; "
283 l7_data += f"server_traffic_duration={s_traffic_duration}; "
285 # Too many packets in NIC rx queue
286 s_err_rx_throttled = server_stats.get(u"err_rx_throttled", 0)
287 l7_data += f"client_err_rx_throttled={s_err_rx_throttled}; "
288 if u"udp" in profile_file:
290 # Established connections
291 c_udp_connects = client_stats.get(u"udps_connects", 0)
292 l7_data += f"client_udp_connects={c_udp_connects}; "
294 c_udp_closed = client_stats.get(u"udps_closed", 0)
295 l7_data += f"client_udp_closed={c_udp_closed}; "
297 c_udp_sndbyte = client_stats.get(u"udps_sndbyte", 0)
298 l7_data += f"client_udp_tx_bytes={c_udp_sndbyte}; "
300 c_udp_sndpkt = client_stats.get(u"udps_sndpkt", 0)
301 l7_data += f"client_udp_tx_packets={c_udp_sndpkt}; "
303 c_udp_rcvbyte = client_stats.get(u"udps_rcvbyte", 0)
304 l7_data += f"client_udp_rx_bytes={c_udp_rcvbyte}; "
306 c_udp_rcvpkt = client_stats.get(u"udps_rcvpkt", 0)
307 l7_data += f"client_udp_rx_packets={c_udp_rcvpkt}; "
309 c_udp_keepdrops = client_stats.get(u"udps_keepdrops", 0)
310 l7_data += f"client_udp_keep_drops={c_udp_keepdrops}; "
311 # Client without flow
312 c_err_cwf = client_stats.get(u"err_cwf", 0)
313 l7_data += f"client_err_cwf={c_err_cwf}; "
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 # Connection attempts
336 c_tcp_connattempt = client_stats.get(u"tcps_connattempt", 0)
337 l7_data += f"client_tcp_connattempt={c_tcp_connattempt}; "
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_received = 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"multiplier={multiplier!r}; "
387 f"total_received={total_received}; "
388 f"total_sent={total_sent}; "
389 f"frame_loss={lost_a + lost_b}; "
390 f"approximated_duration={approximated_duration}; "
391 f"latency_stream_0(usec)={lat_a}; "
392 f"latency_stream_1(usec)={lat_b}; "
393 f"latency_hist_stream_0={lat_a_hist}; "
394 f"latency_hist_stream_1={lat_b_hist}; "
395 f"client_sent={client_sent}; "
396 f"client_received={client_received}; "
402 """Main function for the traffic generator using T-rex.
404 It verifies the given command line arguments and runs "simple_burst"
407 parser = argparse.ArgumentParser()
409 u"-p", u"--profile", required=True, type=str,
410 help=u"Python traffic profile."
413 u"-d", u"--duration", required=True, type=float,
414 help=u"Duration of the whole traffic run, including overheads."
417 u"-s", u"--frame_size", required=True,
418 help=u"Size of a Frame without padding and IPG."
421 u"-m", u"--multiplier", required=True, type=float,
422 help=u"Multiplier of profile CPS."
425 u"--port_0", required=True, type=int,
426 help=u"Port 0 on the traffic generator."
429 u"--port_1", required=True, type=int,
430 help=u"Port 1 on the traffic generator."
433 u"--async_start", action=u"store_true", default=False,
434 help=u"Non-blocking call of the script."
437 u"--latency", action=u"store_true", default=False,
438 help=u"Add latency stream."
441 u"--traffic_directions", type=int, default=2,
442 help=u"Send bi- (2) or uni- (1) directional traffic."
445 args = parser.parse_args()
448 framesize = int(args.frame_size)
450 framesize = args.frame_size
453 profile_file=args.profile,
454 duration=args.duration,
456 multiplier=args.multiplier,
459 latency=args.latency,
460 async_start=args.async_start,
461 traffic_directions=args.traffic_directions,
465 if __name__ == u"__main__":