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