3 # Copyright (c) 2021 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 T-Rex advanced stateful (astf) traffic profile together
27 with other parameters, reads the profile and sends the traffic. At the end, it
28 parses for various counters.
37 0, u"/opt/trex-core-2.86/scripts/automation/trex_control_plane/interactive/"
39 from trex.astf.api import *
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 u"/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
83 """Send traffic and measure packet loss and latency.
86 - reads the given traffic profile with streams,
87 - connects to the T-rex astf client,
89 - removes all existing streams,
90 - adds streams from the traffic profile to the ports,
91 - clears the statistics from the client,
93 - waits for the defined time (or runs forever if async mode is defined),
94 - explicitly stops the traffic,
95 - reads and displays the statistics and
96 - disconnects from the client.
99 Contrary to stateless mode, ASTF profiles typically limit the number
100 of flows/transactions that can happen.
101 The caller is expected to set the duration parameter accordingly to
102 this limit and multiplier, including any overheads.
103 See *_traffic_duration output fields for TRex's measurement
104 of the real traffic duration (should be without any inactivity overheads).
105 If traffic has not ended by the final time, the traffic
106 is stopped explicitly, counters reflect the state just after the stop.
108 TODO: Support tests which focus only on some transaction phases,
109 e.g. TCP tests ignoring init and teardown separated by delays.
110 Currently, approximated time measures the whole traffic duration.
112 :param profile_file: A python module with T-rex traffic profile.
113 :param duration: Expected duration for all transactions to finish,
114 assuming only tolerable duration stretching happens.
115 This includes later start of later transactions
116 (according to TPS multiplier) and expected duration of each transaction.
117 Critically, this also includes any delay TRex shows when starting
118 traffic (but not the similar delay during stopping).
119 :param framesize: Frame size.
120 :param multiplier: Multiplier of profile CPS.
121 :param port_0: Port 0 on the traffic generator.
122 :param port_1: Port 1 on the traffic generator.
123 :param latency: With latency stats.
124 :param async_start: Start the traffic and exit.
125 :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
126 :type profile_file: str
127 :type duration: float
128 :type framesize: int or str
129 :type multiplier: int
133 :type async_start: bool
134 :type traffic_directions: int
147 approximated_duration = 0
151 # TODO: key-values pairs to the profile file
153 print(f"### Profile file:\n{profile_file}")
154 profile = ASTFProfile.load(profile_file, framesize=framesize)
156 print(f"Error while loading profile '{profile_file}'!")
161 client = ASTFClient()
164 # Acquire ports, stop the traffic, remove loaded traffic and clear
168 client.load_profile(profile)
171 if traffic_directions > 1:
174 # Clear the stats before injecting.
179 # Choose CPS and start traffic.
182 # Increase the input duration slightly,
183 # to ensure it does not end before sleep&stop below happens.
184 duration=duration + 0.1 if duration > 0 else duration,
186 latency_pps=int(multiplier) if latency else 0,
187 client_mask=2**len(ports)-1,
189 time_start = time.monotonic()
192 # For async stop, we need to export the current snapshot.
193 xsnap0 = client.ports[port_0].get_xstats().reference_stats
194 print(f"Xstats snapshot 0: {xsnap0!r}")
195 if traffic_directions > 1:
196 xsnap1 = client.ports[port_1].get_xstats().reference_stats
197 print(f"Xstats snapshot 1: {xsnap1!r}")
201 # Do not block yet, the existing transactions may take long time
202 # to finish. We need an action that is almost reset(),
203 # but without clearing stats.
204 client.stop(block=False)
205 client.stop_latency()
206 client.remove_rx_queue(client.get_all_ports())
207 # Now we can wait for the real traffic stop.
208 client.stop(block=True)
210 # Read the stats after the traffic stopped (or time up).
211 stats[time.monotonic() - time_start] = client.get_stats(
215 if client.get_warnings():
216 for warning in client.get_warnings():
219 # Now finish the complete reset.
222 print(u"##### Statistics #####")
223 print(json.dumps(stats, indent=4, separators=(u",", u": ")))
225 approximated_duration = list(sorted(stats.keys()))[-1]
226 stats = stats[sorted(stats.keys())[-1]]
227 lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
228 if traffic_directions > 1:
229 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
231 # TODO: Latency measurement not used at this phase. This part will
232 # be aligned in another commit.
233 # Stats index is not a port number, but "pgid".
235 lat_obj = stats[u"latency"][0][u"hist"]
236 # TODO: Latency histogram is dictionary in astf mode,
237 # needs additional processing
239 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
240 str(lat_obj[u"max_usec"]), u"-")
241 lat_a_hist = str(lat_obj[u"histogram"])
242 if traffic_directions > 1:
243 lat_obj = stats[u"latency"][1][u"hist"]
245 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
246 str(lat_obj[u"max_usec"]), u"-")
247 lat_b_hist = str(lat_obj[u"histogram"])
249 if traffic_directions > 1:
251 stats[port_0][u"opackets"] + stats[port_1][u"opackets"]
253 stats[port_0][u"ipackets"] + stats[port_1][u"ipackets"]
254 client_sent = stats[port_0][u"opackets"]
255 client_received = stats[port_0][u"ipackets"]
256 client_stats = stats[u"traffic"][u"client"]
257 server_stats = stats[u"traffic"][u"server"]
258 # Some zero counters are not sent
259 # Active and established flows UDP/TCP
261 c_act_flows = client_stats[u"m_active_flows"]
262 c_est_flows = client_stats[u"m_est_flows"]
263 c_traffic_duration = client_stats.get(u"m_traffic_duration", 0)
264 l7_data = f"client_active_flows={c_act_flows}; "
265 l7_data += f"client_established_flows={c_est_flows}; "
266 l7_data += f"client_traffic_duration={c_traffic_duration}; "
268 # Too many packets in NIC rx queue
269 c_err_rx_throttled = client_stats.get(u"err_rx_throttled", 0)
270 l7_data += f"client_err_rx_throttled={c_err_rx_throttled}; "
271 # Number of client side flows that were not opened
272 # due to flow-table overflow
273 c_err_nf_throttled = client_stats.get(u"err_c_nf_throttled", 0)
274 l7_data += f"client_err_nf_throttled={c_err_nf_throttled}; "
276 c_err_flow_overflow = client_stats.get(u"err_flow_overflow", 0)
277 l7_data += f"client_err_flow_overflow={c_err_flow_overflow}; "
279 s_act_flows = server_stats[u"m_active_flows"]
280 s_est_flows = server_stats[u"m_est_flows"]
281 s_traffic_duration = server_stats.get(u"m_traffic_duration", 0)
282 l7_data += f"server_active_flows={s_act_flows}; "
283 l7_data += f"server_established_flows={s_est_flows}; "
284 l7_data += f"server_traffic_duration={s_traffic_duration}; "
286 # Too many packets in NIC rx queue
287 s_err_rx_throttled = server_stats.get(u"err_rx_throttled", 0)
288 l7_data += f"client_err_rx_throttled={s_err_rx_throttled}; "
289 if u"udp" in profile_file:
291 # Established connections
292 c_udp_connects = client_stats.get(u"udps_connects", 0)
293 l7_data += f"client_udp_connects={c_udp_connects}; "
295 c_udp_closed = client_stats.get(u"udps_closed", 0)
296 l7_data += f"client_udp_closed={c_udp_closed}; "
298 c_udp_sndbyte = client_stats.get(u"udps_sndbyte", 0)
299 l7_data += f"client_udp_tx_bytes={c_udp_sndbyte}; "
301 c_udp_sndpkt = client_stats.get(u"udps_sndpkt", 0)
302 l7_data += f"client_udp_tx_packets={c_udp_sndpkt}; "
304 c_udp_rcvbyte = client_stats.get(u"udps_rcvbyte", 0)
305 l7_data += f"client_udp_rx_bytes={c_udp_rcvbyte}; "
307 c_udp_rcvpkt = client_stats.get(u"udps_rcvpkt", 0)
308 l7_data += f"client_udp_rx_packets={c_udp_rcvpkt}; "
310 c_udp_keepdrops = client_stats.get(u"udps_keepdrops", 0)
311 l7_data += f"client_udp_keep_drops={c_udp_keepdrops}; "
312 # Client without flow
313 c_err_cwf = client_stats.get(u"err_cwf", 0)
314 l7_data += f"client_err_cwf={c_err_cwf}; "
316 # Accepted connections
317 s_udp_accepts = server_stats.get(u"udps_accepts", 0)
318 l7_data += f"server_udp_accepts={s_udp_accepts}; "
320 s_udp_closed = server_stats.get(u"udps_closed", 0)
321 l7_data += f"server_udp_closed={s_udp_closed}; "
323 s_udp_sndbyte = server_stats.get(u"udps_sndbyte", 0)
324 l7_data += f"server_udp_tx_bytes={s_udp_sndbyte}; "
326 s_udp_sndpkt = server_stats.get(u"udps_sndpkt", 0)
327 l7_data += f"server_udp_tx_packets={s_udp_sndpkt}; "
329 s_udp_rcvbyte = server_stats.get(u"udps_rcvbyte", 0)
330 l7_data += f"server_udp_rx_bytes={s_udp_rcvbyte}; "
332 s_udp_rcvpkt = server_stats.get(u"udps_rcvpkt", 0)
333 l7_data += f"server_udp_rx_packets={s_udp_rcvpkt}; "
334 elif u"tcp" in profile_file:
336 # Connection attempts
337 c_tcp_connattempt = client_stats.get(u"tcps_connattempt", 0)
338 l7_data += f"client_tcp_connattempt={c_tcp_connattempt}; "
339 # Established connections
340 c_tcp_connects = client_stats.get(u"tcps_connects", 0)
341 l7_data += f"client_tcp_connects={c_tcp_connects}; "
343 c_tcp_closed = client_stats.get(u"tcps_closed", 0)
344 l7_data += f"client_tcp_closed={c_tcp_closed}; "
346 c_tcp_sndbyte = client_stats.get(u"tcps_sndbyte", 0)
347 l7_data += f"client_tcp_tx_bytes={c_tcp_sndbyte}; "
349 c_tcp_rcvbyte = client_stats.get(u"tcps_rcvbyte", 0)
350 l7_data += f"client_tcp_rx_bytes={c_tcp_rcvbyte}; "
352 # Accepted connections
353 s_tcp_accepts = server_stats.get(u"tcps_accepts", 0)
354 l7_data += f"server_tcp_accepts={s_tcp_accepts}; "
355 # Established connections
356 s_tcp_connects = server_stats.get(u"tcps_connects", 0)
357 l7_data += f"server_tcp_connects={s_tcp_connects}; "
359 s_tcp_closed = server_stats.get(u"tcps_closed", 0)
360 l7_data += f"server_tcp_closed={s_tcp_closed}; "
362 s_tcp_sndbyte = server_stats.get(u"tcps_sndbyte", 0)
363 l7_data += f"server_tcp_tx_bytes={s_tcp_sndbyte}; "
365 s_tcp_rcvbyte = server_stats.get(u"tcps_rcvbyte", 0)
366 l7_data += f"server_tcp_rx_bytes={s_tcp_rcvbyte}; "
368 total_sent = stats[port_0][u"opackets"]
369 total_received = stats[port_1][u"ipackets"]
371 print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
372 if traffic_directions > 1:
373 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
376 print(u"T-Rex ASTF runtime error!", file=sys.stderr)
382 client.disconnect(stop_traffic=False, release_ports=True)
384 client.clear_profile()
387 f"multiplier={multiplier!r}; "
388 f"total_received={total_received}; "
389 f"total_sent={total_sent}; "
390 f"frame_loss={lost_a + lost_b}; "
391 f"approximated_duration={approximated_duration}; "
392 f"latency_stream_0(usec)={lat_a}; "
393 f"latency_stream_1(usec)={lat_b}; "
394 f"latency_hist_stream_0={lat_a_hist}; "
395 f"latency_hist_stream_1={lat_b_hist}; "
396 f"client_sent={client_sent}; "
397 f"client_received={client_received}; "
403 """Main function for the traffic generator using T-rex.
405 It verifies the given command line arguments and runs "simple_burst"
408 parser = argparse.ArgumentParser()
410 u"-p", u"--profile", required=True, type=str,
411 help=u"Python traffic profile."
414 u"-d", u"--duration", required=True, type=float,
415 help=u"Duration of the whole traffic run, including overheads."
418 u"-s", u"--frame_size", required=True,
419 help=u"Size of a Frame without padding and IPG."
422 u"-m", u"--multiplier", required=True, type=float,
423 help=u"Multiplier of profile CPS."
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,
455 duration=args.duration,
457 multiplier=args.multiplier,
460 latency=args.latency,
461 async_start=args.async_start,
462 traffic_directions=args.traffic_directions,
466 if __name__ == u"__main__":