NAT44 EI tests
[csit.git] / GPL / tools / trex / trex_stateless_profile.py
1 #!/usr/bin/python3
2
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 """This module gets a traffic profile together with other parameters, reads
17 the profile and sends the traffic. At the end, it measures the packet loss and
18 latency.
19 """
20
21 import argparse
22 import json
23 import sys
24 import time
25
26 sys.path.insert(
27     0, u"/opt/trex-core-2.73/scripts/automation/trex_control_plane/interactive/"
28 )
29 from trex.stl.api import *
30
31
32 def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
33     """Return formatted, rounded latency.
34
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.
39     :type lat_min: str
40     :type lat_avg: str
41     :type lat_max: str
42     :type hdrh: str
43     :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
44     :rtype: str
45     """
46     try:
47         t_min = int(round(float(lat_min)))
48     except ValueError:
49         t_min = int(-1)
50     try:
51         t_avg = int(round(float(lat_avg)))
52     except ValueError:
53         t_avg = int(-1)
54     try:
55         t_max = int(round(float(lat_max)))
56     except ValueError:
57         t_max = int(-1)
58
59     return u"/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
60
61
62 def simple_burst(
63         profile_file, duration, framesize, rate, warmup_time, port_0, port_1,
64         latency, async_start=False, traffic_directions=2, force=False):
65     """Send traffic and measure packet loss and latency.
66
67     Procedure:
68      - reads the given traffic profile with streams,
69      - connects to the T-rex client,
70      - resets the ports,
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
74        statistics,
75      - clears the statistics from the client,
76      - starts the traffic,
77      - waits for the defined time (or runs forever if async mode is defined),
78      - stops the traffic,
79      - reads and displays the statistics and
80      - disconnects from the client.
81
82     :param profile_file: A python module with T-rex traffic profile.
83     :param framesize: Frame size.
84     :param duration: Duration of traffic run in seconds (-1=infinite).
85     :param rate: Traffic rate [percentage, pps, bps].
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     :param force: Force start regardless of ports state.
93     :type profile_file: str
94     :type framesize: int or str
95     :type duration: float
96     :type rate: str
97     :type warmup_time: float
98     :type port_0: int
99     :type port_1: int
100     :type latency: bool
101     :type async_start: bool
102     :type traffic_directions: int
103     :type force: bool
104     """
105     client = None
106     total_rcvd = 0
107     total_sent = 0
108     approximated_duration = 0
109     approximated_rate = 0
110     lost_a = 0
111     lost_b = 0
112     lat_a = u"-1/-1/-1/"
113     lat_b = u"-1/-1/-1/"
114
115     # Read the profile:
116     try:
117         print(f"### Profile file:\n{profile_file}")
118         profile = STLProfile.load(
119             profile_file, direction=0, port_id=0, framesize=framesize,
120             rate=rate
121         )
122         streams = profile.get_streams()
123     except STLError as err:
124         print(f"Error while loading profile '{profile_file}' {err!r}")
125         sys.exit(1)
126
127     try:
128         # Create the client:
129         client = STLClient()
130         # Connect to server:
131         client.connect()
132         # Prepare our ports (the machine has 0 <--> 1 with static route):
133         client.reset(ports=[port_0, port_1])
134         client.remove_all_streams(ports=[port_0, port_1])
135
136         if u"macsrc" in profile_file:
137             client.set_port_attr(ports=[port_0, port_1], promiscuous=True)
138         if isinstance(framesize, int):
139             last_stream_a = int((len(streams) - 2 ) / 2)
140             client.add_streams(streams[0:last_stream_a], ports=[port_0])
141             if traffic_directions > 1:
142                 last_stream_b = (last_stream_a * 2)
143                 client.add_streams(
144                     streams[last_stream_a:last_stream_b], ports=[port_1])
145             print(last_stream_a, last_stream_b, streams)
146         elif isinstance(framesize, str):
147             client.add_streams(streams[0:3], ports=[port_0])
148             if traffic_directions > 1:
149                 client.add_streams(streams[3:6], ports=[port_1])
150         if latency:
151             try:
152                 if isinstance(framesize, int):
153                     client.add_streams(streams[last_stream_b], ports=[port_0])
154                     if traffic_directions > 1:
155                         client.add_streams(
156                             streams[last_stream_b + 1], ports=[port_1])
157                 elif isinstance(framesize, str):
158                     latency = False
159             except STLError:
160                 # Disable latency if NIC does not support requested stream type
161                 print(u"##### FAILED to add latency streams #####")
162                 latency = False
163         ports = [port_0]
164         if traffic_directions > 1:
165             ports.append(port_1)
166         # Warm-up phase:
167         if warmup_time > 0:
168             # Clear the stats before injecting:
169             client.clear_stats()
170
171             # Choose rate and start traffic:
172             client.start(ports=ports, mult=rate, duration=warmup_time,
173                          force=force)
174
175             # Block until done:
176             time_start = time.monotonic()
177             client.wait_on_traffic(ports=ports, timeout=warmup_time+30)
178             time_stop = time.monotonic()
179             approximated_duration = time_stop - time_start
180
181             if client.get_warnings():
182                 for warning in client.get_warnings():
183                     print(warning)
184
185             # Read the stats after the test:
186             stats = client.get_stats()
187
188             print(u"##### Warmup statistics #####")
189             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
190
191             lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
192             if traffic_directions > 1:
193                 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
194
195             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
196             if traffic_directions > 1:
197                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
198
199         # Clear the stats before injecting:
200         client.clear_stats()
201         lost_a = 0
202         lost_b = 0
203
204         # Choose rate and start traffic:
205         client.start(ports=ports, mult=rate, duration=duration, force=force)
206
207         if async_start:
208             # For async stop, we need to export the current snapshot.
209             xsnap0 = client.ports[0].get_xstats().reference_stats
210             print(f"Xstats snapshot 0: {xsnap0!r}")
211             if traffic_directions > 1:
212                 xsnap1 = client.ports[1].get_xstats().reference_stats
213                 print(f"Xstats snapshot 1: {xsnap1!r}")
214         else:
215             # Block until done:
216             time_start = time.monotonic()
217             client.wait_on_traffic(ports=ports, timeout=duration+30)
218             time_stop = time.monotonic()
219             approximated_duration = time_stop - time_start
220
221             if client.get_warnings():
222                 for warning in client.get_warnings():
223                     print(warning)
224
225             # Read the stats after the test
226             stats = client.get_stats()
227
228             print(u"##### Statistics #####")
229             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
230
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"]
234
235             # Stats index is not a port number, but "pgid".
236             if latency:
237                 lat_obj = stats[u"latency"][0][u"latency"]
238                 lat_a = fmt_latency(
239                     str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
240                     str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
241                 if traffic_directions > 1:
242                     lat_obj = stats[u"latency"][1][u"latency"]
243                     lat_b = fmt_latency(
244                         str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
245                         str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
246
247             if traffic_directions > 1:
248                 total_sent = stats[0][u"opackets"] + stats[1][u"opackets"]
249                 total_rcvd = stats[0][u"ipackets"] + stats[1][u"ipackets"]
250             else:
251                 total_sent = stats[port_0][u"opackets"]
252                 total_rcvd = stats[port_1][u"ipackets"]
253             try:
254                 approximated_rate = total_sent / approximated_duration
255             except ZeroDivisionError:
256                 pass
257
258             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
259             if traffic_directions > 1:
260                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
261
262     except STLError as ex_error:
263         print(ex_error, file=sys.stderr)
264         sys.exit(1)
265
266     finally:
267         if async_start:
268             if client:
269                 client.disconnect(stop_traffic=False, release_ports=True)
270         else:
271             if client:
272                 client.disconnect()
273             print(
274                 f"rate={rate!r}, totalReceived={total_rcvd}, "
275                 f"totalSent={total_sent}, frameLoss={lost_a + lost_b}, "
276                 f"targetDuration={duration!r}, "
277                 f"approximatedDuration={approximated_duration!r}, "
278                 f"approximatedRate={approximated_rate}, "
279                 f"latencyStream0(usec)={lat_a}, latencyStream1(usec)={lat_b}, "
280             )
281
282
283 def main():
284     """Main function for the traffic generator using T-rex.
285
286     It verifies the given command line arguments and runs "simple_burst"
287     function.
288     """
289     parser = argparse.ArgumentParser()
290     parser.add_argument(
291         u"-p", u"--profile", required=True, type=str,
292         help=u"Python traffic profile."
293     )
294     parser.add_argument(
295         u"-d", u"--duration", required=True, type=float,
296         help=u"Duration of traffic run."
297     )
298     parser.add_argument(
299         u"-s", u"--frame_size", required=True,
300         help=u"Size of a Frame without padding and IPG."
301     )
302     parser.add_argument(
303         u"-r", u"--rate", required=True,
304         help=u"Traffic rate with included units (%, pps)."
305     )
306     parser.add_argument(
307         u"-w", u"--warmup_time", type=float, default=5.0,
308         help=u"Traffic warm-up time in seconds, 0 = disable."
309     )
310     parser.add_argument(
311         u"--port_0", required=True, type=int,
312         help=u"Port 0 on the traffic generator."
313     )
314     parser.add_argument(
315         u"--port_1", required=True, type=int,
316         help=u"Port 1 on the traffic generator."
317     )
318     parser.add_argument(
319         u"--async_start", action=u"store_true", default=False,
320         help=u"Non-blocking call of the script."
321     )
322     parser.add_argument(
323         u"--latency", action=u"store_true", default=False,
324         help=u"Add latency stream."
325     )
326     parser.add_argument(
327         u"--traffic_directions", type=int, default=2,
328         help=u"Send bi- (2) or uni- (1) directional traffic."
329     )
330     parser.add_argument(
331         u"--force", action=u"store_true", default=False,
332         help=u"Force start regardless of ports state."
333     )
334
335     args = parser.parse_args()
336
337     try:
338         framesize = int(args.frame_size)
339     except ValueError:
340         framesize = args.frame_size
341
342     simple_burst(
343         profile_file=args.profile, duration=args.duration, framesize=framesize,
344         rate=args.rate, warmup_time=args.warmup_time, port_0=args.port_0,
345         port_1=args.port_1, latency=args.latency, async_start=args.async_start,
346         traffic_directions=args.traffic_directions, force=args.force
347     )
348
349
350 if __name__ == u"__main__":
351     main()