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 parses for various counters.
27 0, u"/opt/trex-core-2.82/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))
73 """Send traffic and measure packet loss and latency.
76 - reads the given traffic profile with streams,
77 - connects to the T-rex astf client,
79 - removes all existing streams,
80 - adds streams from the traffic profile to the ports,
81 - clears the statistics from the client,
83 - waits for the defined time (or runs forever if async mode is defined),
84 - explicitly stops the traffic,
85 - reads and displays the statistics and
86 - disconnects from the client.
89 Contrary to stateless mode, ASTF profiles typically limit the number
90 of flows/transactions that can happen.
91 The caller is expected to set the duration parameter accordingly to
92 this limit and multiplier, including any overheads.
93 See *_traffic_duration output fields for TRex's measurement
94 of the real traffic duration (should be without any inactivity overheads).
95 If traffic has not ended by the final time, the traffic
96 is stopped explicitly, counters reflect the state just after the stop.
98 TODO: Support tests which focus only on some transaction phases,
99 e.g. TCP tests ignoring init and teardown separated by delays.
100 Currently, approximated time measures the whole traffic duration.
102 :param profile_file: A python module with T-rex traffic profile.
103 :param duration: Expected duration for all transactions to finish,
104 assuming only tolerable duration stretching happens.
105 This includes later start of later transactions
106 (according to TPS multiplier) and expected duration of each transaction.
107 Critically, this also includes any delay TRex shows when starting
108 traffic (but not the similar delay during stopping).
109 :param framesize: Frame size.
110 :param multiplier: Multiplier of profile CPS.
111 :param port_0: Port 0 on the traffic generator.
112 :param port_1: Port 1 on the traffic generator.
113 :param latency: With latency stats.
114 :param async_start: Start the traffic and exit.
115 :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
116 :type profile_file: str
117 :type duration: float
118 :type framesize: int or str
119 :type multiplier: int
123 :type async_start: bool
124 :type traffic_directions: int
137 approximated_duration = 0
141 # TODO: key-values pairs to the profile file
143 print(f"### Profile file:\n{profile_file}")
144 profile = ASTFProfile.load(profile_file, framesize=framesize)
146 print(f"Error while loading profile '{profile_file}'!")
151 client = ASTFClient()
154 # Acquire ports, stop the traffic, remove loaded traffic and clear
158 client.load_profile(profile)
161 if traffic_directions > 1:
164 # Clear the stats before injecting.
169 # Choose CPS and start traffic.
172 # Increase the input duration slightly,
173 # to ensure it does not end before sleep&stop below happens.
174 duration=duration + 0.1 if duration > 0 else duration,
176 latency_pps=int(multiplier) if latency else 0,
177 client_mask=2**len(ports)-1,
179 time_start = time.monotonic()
182 # For async stop, we need to export the current snapshot.
183 xsnap0 = client.ports[port_0].get_xstats().reference_stats
184 print(f"Xstats snapshot 0: {xsnap0!r}")
185 if traffic_directions > 1:
186 xsnap1 = client.ports[port_1].get_xstats().reference_stats
187 print(f"Xstats snapshot 1: {xsnap1!r}")
191 # Do not block yet, the existing transactions may take long time
192 # to finish. We need an action that is almost reset(),
193 # but without clearing stats.
194 client.stop(block=False)
195 client.stop_latency()
196 client.remove_rx_queue(client.get_all_ports())
197 # Now we can wait for the real traffic stop.
198 client.stop(block=True)
200 # Read the stats after the traffic stopped (or time up).
201 stats[time.monotonic() - time_start] = client.get_stats(
205 if client.get_warnings():
206 for warning in client.get_warnings():
209 # Now finish the complete reset.
212 print(u"##### Statistics #####")
213 print(json.dumps(stats, indent=4, separators=(u",", u": ")))
215 approximated_duration = list(sorted(stats.keys()))[-1]
216 stats = stats[sorted(stats.keys())[-1]]
217 lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
218 if traffic_directions > 1:
219 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
221 # TODO: Latency measurement not used at this phase. This part will
222 # be aligned in another commit.
223 # Stats index is not a port number, but "pgid".
225 lat_obj = stats[u"latency"][0][u"hist"]
226 # TODO: Latency histogram is dictionary in astf mode,
227 # needs additional processing
229 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
230 str(lat_obj[u"max_usec"]), u"-")
231 lat_a_hist = str(lat_obj[u"histogram"])
232 if traffic_directions > 1:
233 lat_obj = stats[u"latency"][1][u"hist"]
235 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
236 str(lat_obj[u"max_usec"]), u"-")
237 lat_b_hist = str(lat_obj[u"histogram"])
239 if traffic_directions > 1:
241 stats[port_0][u"opackets"] + stats[port_1][u"opackets"]
243 stats[port_0][u"ipackets"] + stats[port_1][u"ipackets"]
244 client_sent = stats[port_0][u"opackets"]
245 client_received = stats[port_0][u"ipackets"]
246 client_stats = stats[u"traffic"][u"client"]
247 server_stats = stats[u"traffic"][u"server"]
248 # Some zero counters are not sent
249 # Active and established flows UDP/TCP
251 c_act_flows = client_stats[u"m_active_flows"]
252 c_est_flows = client_stats[u"m_est_flows"]
253 c_traffic_duration = client_stats.get(u"m_traffic_duration", 0)
254 l7_data = f"client_active_flows={c_act_flows}; "
255 l7_data += f"client_established_flows={c_est_flows}; "
256 l7_data += f"client_traffic_duration={c_traffic_duration}; "
258 # Too many packets in NIC rx queue
259 c_err_rx_throttled = client_stats.get(u"err_rx_throttled", 0)
260 l7_data += f"client_err_rx_throttled={c_err_rx_throttled}; "
261 # Number of client side flows that were not opened
262 # due to flow-table overflow
263 c_err_nf_throttled = client_stats.get(u"err_c_nf_throttled", 0)
264 l7_data += f"client_err_nf_throttled={c_err_nf_throttled}; "
266 c_err_flow_overflow = client_stats.get(u"err_flow_overflow", 0)
267 l7_data += f"client_err_flow_overflow={c_err_flow_overflow}; "
269 s_act_flows = server_stats[u"m_active_flows"]
270 s_est_flows = server_stats[u"m_est_flows"]
271 s_traffic_duration = server_stats.get(u"m_traffic_duration", 0)
272 l7_data += f"server_active_flows={s_act_flows}; "
273 l7_data += f"server_established_flows={s_est_flows}; "
274 l7_data += f"server_traffic_duration={s_traffic_duration}; "
276 # Too many packets in NIC rx queue
277 s_err_rx_throttled = server_stats.get(u"err_rx_throttled", 0)
278 l7_data += f"client_err_rx_throttled={s_err_rx_throttled}; "
279 if u"udp" in profile_file:
281 # Established connections
282 c_udp_connects = client_stats.get(u"udps_connects", 0)
283 l7_data += f"client_udp_connects={c_udp_connects}; "
285 c_udp_closed = client_stats.get(u"udps_closed", 0)
286 l7_data += f"client_udp_closed={c_udp_closed}; "
288 c_udp_sndbyte = client_stats.get(u"udps_sndbyte", 0)
289 l7_data += f"client_udp_tx_bytes={c_udp_sndbyte}; "
291 c_udp_sndpkt = client_stats.get(u"udps_sndpkt", 0)
292 l7_data += f"client_udp_tx_packets={c_udp_sndpkt}; "
294 c_udp_rcvbyte = client_stats.get(u"udps_rcvbyte", 0)
295 l7_data += f"client_udp_rx_bytes={c_udp_rcvbyte}; "
297 c_udp_rcvpkt = client_stats.get(u"udps_rcvpkt", 0)
298 l7_data += f"client_udp_rx_packets={c_udp_rcvpkt}; "
300 c_udp_keepdrops = client_stats.get(u"udps_keepdrops", 0)
301 l7_data += f"client_udp_keep_drops={c_udp_keepdrops}; "
302 # Client without flow
303 c_err_cwf = client_stats.get(u"err_cwf", 0)
304 l7_data += f"client_err_cwf={c_err_cwf}; "
306 # Accepted connections
307 s_udp_accepts = server_stats.get(u"udps_accepts", 0)
308 l7_data += f"server_udp_accepts={s_udp_accepts}; "
310 s_udp_closed = server_stats.get(u"udps_closed", 0)
311 l7_data += f"server_udp_closed={s_udp_closed}; "
313 s_udp_sndbyte = server_stats.get(u"udps_sndbyte", 0)
314 l7_data += f"server_udp_tx_bytes={s_udp_sndbyte}; "
316 s_udp_sndpkt = server_stats.get(u"udps_sndpkt", 0)
317 l7_data += f"server_udp_tx_packets={s_udp_sndpkt}; "
319 s_udp_rcvbyte = server_stats.get(u"udps_rcvbyte", 0)
320 l7_data += f"server_udp_rx_bytes={s_udp_rcvbyte}; "
322 s_udp_rcvpkt = server_stats.get(u"udps_rcvpkt", 0)
323 l7_data += f"server_udp_rx_packets={s_udp_rcvpkt}; "
324 elif u"tcp" in profile_file:
326 # Connection attempts
327 c_tcp_connattempt = client_stats.get(u"tcps_connattempt", 0)
328 l7_data += f"client_tcp_connattempt={c_tcp_connattempt}; "
329 # Established connections
330 c_tcp_connects = client_stats.get(u"tcps_connects", 0)
331 l7_data += f"client_tcp_connects={c_tcp_connects}; "
333 c_tcp_closed = client_stats.get(u"tcps_closed", 0)
334 l7_data += f"client_tcp_closed={c_tcp_closed}; "
336 c_tcp_sndbyte = client_stats.get(u"tcps_sndbyte", 0)
337 l7_data += f"client_tcp_tx_bytes={c_tcp_sndbyte}; "
339 c_tcp_rcvbyte = client_stats.get(u"tcps_rcvbyte", 0)
340 l7_data += f"client_tcp_rx_bytes={c_tcp_rcvbyte}; "
342 # Accepted connections
343 s_tcp_accepts = server_stats.get(u"tcps_accepts", 0)
344 l7_data += f"server_tcp_accepts={s_tcp_accepts}; "
345 # Established connections
346 s_tcp_connects = server_stats.get(u"tcps_connects", 0)
347 l7_data += f"server_tcp_connects={s_tcp_connects}; "
349 s_tcp_closed = server_stats.get(u"tcps_closed", 0)
350 l7_data += f"server_tcp_closed={s_tcp_closed}; "
352 s_tcp_sndbyte = server_stats.get(u"tcps_sndbyte", 0)
353 l7_data += f"server_tcp_tx_bytes={s_tcp_sndbyte}; "
355 s_tcp_rcvbyte = server_stats.get(u"tcps_rcvbyte", 0)
356 l7_data += f"server_tcp_rx_bytes={s_tcp_rcvbyte}; "
358 total_sent = stats[port_0][u"opackets"]
359 total_received = stats[port_1][u"ipackets"]
361 print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
362 if traffic_directions > 1:
363 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
366 print(u"T-Rex ASTF runtime error!", file=sys.stderr)
372 client.disconnect(stop_traffic=False, release_ports=True)
374 client.clear_profile()
377 f"multiplier={multiplier!r}; "
378 f"total_received={total_received}; "
379 f"total_sent={total_sent}; "
380 f"frame_loss={lost_a + lost_b}; "
381 f"approximated_duration={approximated_duration}; "
382 f"latency_stream_0(usec)={lat_a}; "
383 f"latency_stream_1(usec)={lat_b}; "
384 f"latency_hist_stream_0={lat_a_hist}; "
385 f"latency_hist_stream_1={lat_b_hist}; "
386 f"client_sent={client_sent}; "
387 f"client_received={client_received}; "
393 """Main function for the traffic generator using T-rex.
395 It verifies the given command line arguments and runs "simple_burst"
398 parser = argparse.ArgumentParser()
400 u"-p", u"--profile", required=True, type=str,
401 help=u"Python traffic profile."
404 u"-d", u"--duration", required=True, type=float,
405 help=u"Duration of the whole traffic run, including overheads."
408 u"-s", u"--frame_size", required=True,
409 help=u"Size of a Frame without padding and IPG."
412 u"-m", u"--multiplier", required=True, type=float,
413 help=u"Multiplier of profile CPS."
416 u"--port_0", required=True, type=int,
417 help=u"Port 0 on the traffic generator."
420 u"--port_1", required=True, type=int,
421 help=u"Port 1 on the traffic generator."
424 u"--async_start", action=u"store_true", default=False,
425 help=u"Non-blocking call of the script."
428 u"--latency", action=u"store_true", default=False,
429 help=u"Add latency stream."
432 u"--traffic_directions", type=int, default=2,
433 help=u"Send bi- (2) or uni- (1) directional traffic."
436 args = parser.parse_args()
439 framesize = int(args.frame_size)
441 framesize = args.frame_size
444 profile_file=args.profile,
445 duration=args.duration,
447 multiplier=args.multiplier,
450 latency=args.latency,
451 async_start=args.async_start,
452 traffic_directions=args.traffic_directions,
456 if __name__ == u"__main__":