Performance: Fix l3fwd in 3node
[csit.git] / resources / 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         )
121         streams = profile.get_streams()
122     except STLError as err:
123         print(f"Error while loading profile '{profile_file}' {err!r}")
124         sys.exit(1)
125
126     try:
127         # Create the client:
128         client = STLClient()
129         # Connect to server:
130         client.connect()
131         # Prepare our ports (the machine has 0 <--> 1 with static route):
132         client.reset(ports=[port_0, port_1])
133         client.remove_all_streams(ports=[port_0, port_1])
134
135         if u"macsrc" in profile_file:
136             client.set_port_attr(ports=[port_0, port_1], promiscuous=True)
137         if isinstance(framesize, int):
138             client.add_streams(streams[0], ports=[port_0])
139             if traffic_directions > 1:
140                 client.add_streams(streams[1], ports=[port_1])
141         elif isinstance(framesize, str):
142             client.add_streams(streams[0:3], ports=[port_0])
143             if traffic_directions > 1:
144                 client.add_streams(streams[3:6], ports=[port_1])
145         if latency:
146             try:
147                 if isinstance(framesize, int):
148                     client.add_streams(streams[2], ports=[port_0])
149                     if traffic_directions > 1:
150                         client.add_streams(streams[3], ports=[port_1])
151                 elif isinstance(framesize, str):
152                     latency = False
153             except STLError:
154                 # Disable latency if NIC does not support requested stream type
155                 print(u"##### FAILED to add latency streams #####")
156                 latency = False
157         ports = [port_0]
158         if traffic_directions > 1:
159             ports.append(port_1)
160         # Warm-up phase:
161         if warmup_time > 0:
162             # Clear the stats before injecting:
163             client.clear_stats()
164
165             # Choose rate and start traffic:
166             client.start(ports=ports, mult=rate, duration=warmup_time,
167                          force=force)
168
169             # Block until done:
170             time_start = time.monotonic()
171             client.wait_on_traffic(ports=ports, timeout=warmup_time+30)
172             time_stop = time.monotonic()
173             approximated_duration = time_stop - time_start
174
175             if client.get_warnings():
176                 for warning in client.get_warnings():
177                     print(warning)
178
179             # Read the stats after the test:
180             stats = client.get_stats()
181
182             print(u"##### Warmup statistics #####")
183             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
184
185             lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
186             if traffic_directions > 1:
187                 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
188
189             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
190             if traffic_directions > 1:
191                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
192
193         # Clear the stats before injecting:
194         client.clear_stats()
195         lost_a = 0
196         lost_b = 0
197
198         # Choose rate and start traffic:
199         client.start(ports=ports, mult=rate, duration=duration)
200
201         if async_start:
202             # For async stop, we need to export the current snapshot.
203             xsnap0 = client.ports[0].get_xstats().reference_stats
204             print(f"Xstats snapshot 0: {xsnap0!r}")
205             if traffic_directions > 1:
206                 xsnap1 = client.ports[1].get_xstats().reference_stats
207                 print(f"Xstats snapshot 1: {xsnap1!r}")
208         else:
209             # Block until done:
210             time_start = time.monotonic()
211             client.wait_on_traffic(ports=ports, timeout=duration+30)
212             time_stop = time.monotonic()
213             approximated_duration = time_stop - time_start
214
215             if client.get_warnings():
216                 for warning in client.get_warnings():
217                     print(warning)
218
219             # Read the stats after the test
220             stats = client.get_stats()
221
222             print(u"##### Statistics #####")
223             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
224
225             lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
226             if traffic_directions > 1:
227                 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
228
229             # Stats index is not a port number, but "pgid".
230             if latency:
231                 lat_obj = stats[u"latency"][0][u"latency"]
232                 lat_a = fmt_latency(
233                     str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
234                     str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
235                 if traffic_directions > 1:
236                     lat_obj = stats[u"latency"][1][u"latency"]
237                     lat_b = fmt_latency(
238                         str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
239                         str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
240
241             if traffic_directions > 1:
242                 total_sent = stats[0][u"opackets"] + stats[1][u"opackets"]
243                 total_rcvd = stats[0][u"ipackets"] + stats[1][u"ipackets"]
244             else:
245                 total_sent = stats[port_0][u"opackets"]
246                 total_rcvd = stats[port_1][u"ipackets"]
247             try:
248                 approximated_rate = total_sent / approximated_duration
249             except ZeroDivisionError:
250                 pass
251
252             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
253             if traffic_directions > 1:
254                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
255
256     except STLError as ex_error:
257         print(ex_error, file=sys.stderr)
258         sys.exit(1)
259
260     finally:
261         if async_start:
262             if client:
263                 client.disconnect(stop_traffic=False, release_ports=True)
264         else:
265             if client:
266                 client.disconnect()
267             print(
268                 f"rate={rate!r}, totalReceived={total_rcvd}, "
269                 f"totalSent={total_sent}, frameLoss={lost_a + lost_b}, "
270                 f"targetDuration={duration!r}, "
271                 f"approximatedDuration={approximated_duration!r}, "
272                 f"approximatedRate={approximated_rate}, "
273                 f"latencyStream0(usec)={lat_a}, latencyStream1(usec)={lat_b}, "
274             )
275
276
277 def main():
278     """Main function for the traffic generator using T-rex.
279
280     It verifies the given command line arguments and runs "simple_burst"
281     function.
282     """
283     parser = argparse.ArgumentParser()
284     parser.add_argument(
285         u"-p", u"--profile", required=True, type=str,
286         help=u"Python traffic profile."
287     )
288     parser.add_argument(
289         u"-d", u"--duration", required=True, type=float,
290         help=u"Duration of traffic run."
291     )
292     parser.add_argument(
293         u"-s", u"--frame_size", required=True,
294         help=u"Size of a Frame without padding and IPG."
295     )
296     parser.add_argument(
297         u"-r", u"--rate", required=True,
298         help=u"Traffic rate with included units (%, pps)."
299     )
300     parser.add_argument(
301         u"-w", u"--warmup_time", type=float, default=5.0,
302         help=u"Traffic warm-up time in seconds, 0 = disable."
303     )
304     parser.add_argument(
305         u"--port_0", required=True, type=int,
306         help=u"Port 0 on the traffic generator."
307     )
308     parser.add_argument(
309         u"--port_1", required=True, type=int,
310         help=u"Port 1 on the traffic generator."
311     )
312     parser.add_argument(
313         u"--async_start", action=u"store_true", default=False,
314         help=u"Non-blocking call of the script."
315     )
316     parser.add_argument(
317         u"--latency", action=u"store_true", default=False,
318         help=u"Add latency stream."
319     )
320     parser.add_argument(
321         u"--traffic_directions", type=int, default=2,
322         help=u"Send bi- (2) or uni- (1) directional traffic."
323     )
324     parser.add_argument(
325         u"--force", action=u"store_true", default=False,
326         help=u"Force start regardless of ports state."
327     )
328
329     args = parser.parse_args()
330
331     try:
332         framesize = int(args.frame_size)
333     except ValueError:
334         framesize = args.frame_size
335
336     simple_burst(
337         profile_file=args.profile, duration=args.duration, framesize=framesize,
338         rate=args.rate, warmup_time=args.warmup_time, port_0=args.port_0,
339         port_1=args.port_1, latency=args.latency, async_start=args.async_start,
340         traffic_directions=args.traffic_directions, force=args.force
341     )
342
343
344 if __name__ == u"__main__":
345     main()