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()
186 # For async stop, we need to export the current snapshot.
187 xsnap0 = client.ports[port_0].get_xstats().reference_stats
188 print(f"Xstats snapshot 0: {xsnap0!r}")
189 if traffic_directions > 1:
190 xsnap1 = client.ports[port_1].get_xstats().reference_stats
191 print(f"Xstats snapshot 1: {xsnap1!r}")
193 # Do not block until done.
194 while client.is_traffic_active(ports=ports):
196 stats_sampling if stats_sampling < duration else duration
199 stats[time.monotonic()-time_start] = client.get_stats(
203 # Read the stats after the test
204 stats[time.monotonic()-time_start] = client.get_stats(
208 if client.get_warnings():
209 for warning in client.get_warnings():
214 print(u"##### Statistics #####")
215 print(json.dumps(stats, indent=4, separators=(u",", u": ")))
217 approximated_duration = list(sorted(stats.keys()))[-1]
218 stats = stats[sorted(stats.keys())[-1]]
219 lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
220 if traffic_directions > 1:
221 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
223 # TODO: Latency measurement not used at this phase. This part will
224 # be aligned in another commit.
225 # Stats index is not a port number, but "pgid".
227 lat_obj = stats[u"latency"][0][u"hist"]
228 # TODO: Latency histogram is dictionary in astf mode,
229 # needs additional processing
231 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
232 str(lat_obj[u"max_usec"]), u"-")
233 lat_a_hist = str(lat_obj[u"histogram"])
234 if traffic_directions > 1:
235 lat_obj = stats[u"latency"][1][u"hist"]
237 str(lat_obj[u"min_usec"]), str(lat_obj[u"s_avg"]),
238 str(lat_obj[u"max_usec"]), u"-")
239 lat_b_hist = str(lat_obj[u"histogram"])
241 if traffic_directions > 1:
243 stats[port_0][u"opackets"] + stats[port_1][u"opackets"]
245 stats[port_0][u"ipackets"] + stats[port_1][u"ipackets"]
246 client_stats = stats[u"traffic"][u"client"]
247 server_stats = stats[u"traffic"][u"server"]
248 # Active and established flows UDP/TCP
250 c_act_flows = client_stats[u"m_active_flows"]
251 c_est_flows = client_stats[u"m_est_flows"]
252 l7_data = f"client_active_flows={c_act_flows}, "
253 l7_data += f"client_established_flows={c_est_flows}, "
255 s_act_flows = server_stats[u"m_active_flows"]
256 s_est_flows = server_stats[u"m_est_flows"]
257 l7_data += f"server_active_flows={s_act_flows}, "
258 l7_data += f"server_established_flows={s_est_flows}, "
259 # Some zero counters are not sent
260 if u"udp" in profile_file:
262 # Established connections
263 c_udp_connects = client_stats.get(u"udps_connects", 0)
264 l7_data += f"client_udp_connects={c_udp_connects}, "
266 c_udp_closed = client_stats.get(u"udps_closed", 0)
267 l7_data += f"client_udp_closed={c_udp_closed}, "
269 # Accepted connections
270 s_udp_accepts = server_stats.get(u"udps_accepts", 0)
271 l7_data += f"server_udp_accepts={s_udp_accepts}, "
273 s_udp_closed = server_stats.get(u"udps_closed", 0)
274 l7_data += f"server_udp_closed={s_udp_closed}, "
275 elif u"tcp" in profile_file:
277 # Initiated connections
278 c_tcp_connatt = client_stats.get(u"tcps_connattempt", 0)
279 l7_data += f"client_tcp_connect_inits={c_tcp_connatt}, "
280 # Established connections
281 c_tcp_connects = client_stats.get(u"tcps_connects", 0)
282 l7_data += f"client_tcp_connects={c_tcp_connects}, "
284 c_tcp_closed = client_stats.get(u"tcps_closed", 0)
285 l7_data += f"client_tcp_closed={c_tcp_closed}, "
287 # Accepted connections
288 s_tcp_accepts = server_stats.get(u"tcps_accepts", 0)
289 l7_data += f"server_tcp_accepts={s_tcp_accepts}, "
290 # Established connections
291 s_tcp_connects = server_stats.get(u"tcps_connects", 0)
292 l7_data += f"server_tcp_connects={s_tcp_connects}, "
294 s_tcp_closed = server_stats.get(u"tcps_closed", 0)
295 l7_data += f"server_tcp_closed={s_tcp_closed}, "
297 total_sent = stats[port_0][u"opackets"]
298 total_rcvd = stats[port_1][u"ipackets"]
300 print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
301 if traffic_directions > 1:
302 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
305 print(u"T-Rex ASTF runtime error!", file=sys.stderr)
311 client.disconnect(stop_traffic=False, release_ports=True)
313 client.clear_profile()
316 f"cps={mult!r}, total_received={total_rcvd}, "
317 f"total_sent={total_sent}, frame_loss={lost_a + lost_b}, "
318 f"approximated_duration={approximated_duration}, "
319 f"latency_stream_0(usec)={lat_a}, "
320 f"latency_stream_1(usec)={lat_b}, "
321 f"latency_hist_stream_0={lat_a_hist}, "
322 f"latency_hist_stream_1={lat_b_hist}, "
328 """Main function for the traffic generator using T-rex.
330 It verifies the given command line arguments and runs "simple_burst"
333 parser = argparse.ArgumentParser()
335 u"-p", u"--profile", required=True, type=str,
336 help=u"Python traffic profile."
339 u"-d", u"--duration", required=True, type=float,
340 help=u"Duration of traffic run."
343 u"-s", u"--frame_size", required=True,
344 help=u"Size of a Frame without padding and IPG."
347 u"-m", u"--mult", required=True, type=int,
348 help=u"Multiplier of profile CPS."
351 u"-w", u"--warmup_time", type=float, default=5.0,
352 help=u"Traffic warm-up time in seconds, 0 = disable."
355 u"--port_0", required=True, type=int,
356 help=u"Port 0 on the traffic generator."
359 u"--port_1", required=True, type=int,
360 help=u"Port 1 on the traffic generator."
363 u"--async_start", action=u"store_true", default=False,
364 help=u"Non-blocking call of the script."
367 u"--latency", action=u"store_true", default=False,
368 help=u"Add latency stream."
371 u"--traffic_directions", type=int, default=2,
372 help=u"Send bi- (2) or uni- (1) directional traffic."
375 args = parser.parse_args()
378 framesize = int(args.frame_size)
380 framesize = args.frame_size
383 profile_file=args.profile, duration=args.duration, framesize=framesize,
384 mult=args.mult, warmup_time=args.warmup_time, port_0=args.port_0,
385 port_1=args.port_1, latency=args.latency, async_start=args.async_start,
386 traffic_directions=args.traffic_directions
390 if __name__ == u"__main__":