License: Wrap GPL block to 80 characters
[csit.git] / GPL / tools / trex / trex_stl_profile.py
1 #!/usr/bin/python3
2
3 # Copyright (c) 2021 Cisco and/or its affiliates.
4 #
5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
6 #
7 # Licensed under the Apache License 2.0 or
8 # GNU General Public License v2.0 or later;  you may not use this file
9 # except in compliance with one of these Licenses. You
10 # may obtain a copy of the Licenses at:
11 #
12 #     http://www.apache.org/licenses/LICENSE-2.0
13 #     https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
14 #
15 # Note: If this file is linked with Scapy, which is GPLv2+, your use of it
16 # must be under GPLv2+.  If at any point in the future it is no longer linked
17 # with Scapy (or other GPLv2+ licensed software), you are free to choose
18 # Apache 2.
19 #
20 # Unless required by applicable law or agreed to in writing, software
21 # distributed under the License is distributed on an "AS IS" BASIS,
22 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 # See the License for the specific language governing permissions and
24 # limitations under the License.
25
26 """This module gets a traffic profile together with other parameters, reads
27 the profile and sends the traffic. At the end, it measures the packet loss and
28 latency.
29 """
30
31 import argparse
32 import json
33 import sys
34 import time
35
36 sys.path.insert(
37     0, u"/opt/trex-core-2.86/scripts/automation/trex_control_plane/interactive/"
38 )
39 from trex.stl.api import *
40
41
42 def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
43     """Return formatted, rounded latency.
44
45     :param lat_min: Min latency
46     :param lat_avg: Average latency
47     :param lat_max: Max latency
48     :param hdrh: Base64 encoded compressed HDRHistogram object.
49     :type lat_min: str
50     :type lat_avg: str
51     :type lat_max: str
52     :type hdrh: str
53     :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
54     :rtype: str
55     """
56     try:
57         t_min = int(round(float(lat_min)))
58     except ValueError:
59         t_min = int(-1)
60     try:
61         t_avg = int(round(float(lat_avg)))
62     except ValueError:
63         t_avg = int(-1)
64     try:
65         t_max = int(round(float(lat_max)))
66     except ValueError:
67         t_max = int(-1)
68
69     return u"/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
70
71
72 def simple_burst(
73         profile_file,
74         duration,
75         framesize,
76         rate,
77         port_0,
78         port_1,
79         latency,
80         async_start=False,
81         traffic_directions=2,
82         force=False,
83     ):
84     """Send traffic and measure packet loss and latency.
85
86     Procedure:
87      - reads the given traffic profile with streams,
88      - connects to the T-rex client,
89      - resets the ports,
90      - removes all existing streams,
91      - adds streams from the traffic profile to the ports,
92      - if the warm-up time is more than 0, sends the warm-up traffic, reads the
93        statistics,
94      - clears the statistics from the client,
95      - starts the traffic,
96      - waits for the defined time (or runs forever if async mode is defined),
97      - stops the traffic,
98      - reads and displays the statistics and
99      - disconnects from the client.
100
101     :param profile_file: A python module with T-rex traffic profile.
102     :param framesize: Frame size.
103     :param duration: Duration of traffic run in seconds (-1=infinite).
104     :param rate: Traffic rate [percentage, pps, bps].
105     :param port_0: Port 0 on the traffic generator.
106     :param port_1: Port 1 on the traffic generator.
107     :param latency: With latency stats.
108     :param async_start: Start the traffic and exit.
109     :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
110     :param force: Force start regardless of ports state.
111     :type profile_file: str
112     :type framesize: int or str
113     :type duration: float
114     :type rate: str
115     :type port_0: int
116     :type port_1: int
117     :type latency: bool
118     :type async_start: bool
119     :type traffic_directions: int
120     :type force: bool
121     """
122     client = None
123     total_rcvd = 0
124     total_sent = 0
125     approximated_duration = 0.0
126     lost_a = 0
127     lost_b = 0
128     lat_a = u"-1/-1/-1/"
129     lat_b = u"-1/-1/-1/"
130
131     # Read the profile:
132     try:
133         print(f"### Profile file:\n{profile_file}")
134         profile = STLProfile.load(
135             profile_file, direction=0, port_id=0, framesize=framesize,
136             rate=rate
137         )
138         streams = profile.get_streams()
139     except STLError:
140         print(f"Error while loading profile '{profile_file}'!")
141         raise
142
143     try:
144         # Create the client:
145         client = STLClient()
146         # Connect to server:
147         client.connect()
148         # Prepare our ports (the machine has 0 <--> 1 with static route):
149         client.reset(ports=[port_0, port_1])
150         client.remove_all_streams(ports=[port_0, port_1])
151
152         if u"macsrc" in profile_file:
153             client.set_port_attr(ports=[port_0, port_1], promiscuous=True)
154         if isinstance(framesize, int):
155             last_stream_a = int((len(streams) - 2 ) / 2)
156             last_stream_b = (last_stream_a * 2)
157             client.add_streams(streams[0:last_stream_a], ports=[port_0])
158             if traffic_directions > 1:
159                 client.add_streams(
160                     streams[last_stream_a:last_stream_b], ports=[port_1])
161         elif isinstance(framesize, str):
162             client.add_streams(streams[0:3], ports=[port_0])
163             if traffic_directions > 1:
164                 client.add_streams(streams[3:6], ports=[port_1])
165         if latency:
166             try:
167                 if isinstance(framesize, int):
168                     client.add_streams(streams[last_stream_b], ports=[port_0])
169                     if traffic_directions > 1:
170                         client.add_streams(
171                             streams[last_stream_b + 1], ports=[port_1])
172                 elif isinstance(framesize, str):
173                     latency = False
174             except STLError:
175                 # Disable latency if NIC does not support requested stream type
176                 print(u"##### FAILED to add latency streams #####")
177                 latency = False
178         ports = [port_0]
179         if traffic_directions > 1:
180             ports.append(port_1)
181
182         # Clear the stats before injecting:
183         client.clear_stats()
184         lost_a = 0
185         lost_b = 0
186
187         # Choose rate and start traffic:
188         client.start(
189             ports=ports,
190             mult=rate,
191             duration=duration,
192             force=force,
193             core_mask=STLClient.CORE_MASK_PIN,
194         )
195
196         if async_start:
197             # For async stop, we need to export the current snapshot.
198             xsnap0 = client.ports[0].get_xstats().reference_stats
199             print(f"Xstats snapshot 0: {xsnap0!r}")
200             if traffic_directions > 1:
201                 xsnap1 = client.ports[1].get_xstats().reference_stats
202                 print(f"Xstats snapshot 1: {xsnap1!r}")
203         else:
204             # Block until done:
205             time_start = time.monotonic()
206             client.wait_on_traffic(ports=ports, timeout=duration+30)
207             time_stop = time.monotonic()
208             approximated_duration = time_stop - time_start
209
210             if client.get_warnings():
211                 for warning in client.get_warnings():
212                     print(warning)
213
214             # Read the stats after the test
215             stats = client.get_stats()
216
217             print(u"##### Statistics #####")
218             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
219
220             lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
221             if traffic_directions > 1:
222                 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
223
224             # Stats index is not a port number, but "pgid".
225             if latency:
226                 lat_obj = stats[u"latency"][0][u"latency"]
227                 lat_a = fmt_latency(
228                     str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
229                     str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
230                 if traffic_directions > 1:
231                     lat_obj = stats[u"latency"][1][u"latency"]
232                     lat_b = 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
236             if traffic_directions > 1:
237                 total_sent = stats[0][u"opackets"] + stats[1][u"opackets"]
238                 total_rcvd = stats[0][u"ipackets"] + stats[1][u"ipackets"]
239             else:
240                 total_sent = stats[port_0][u"opackets"]
241                 total_rcvd = stats[port_1][u"ipackets"]
242
243             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
244             if traffic_directions > 1:
245                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
246
247     except STLError:
248         print(u"T-Rex STL runtime error!", file=sys.stderr)
249         raise
250
251     finally:
252         if async_start:
253             if client:
254                 client.disconnect(stop_traffic=False, release_ports=True)
255         else:
256             if client:
257                 client.disconnect()
258             print(
259                 f"rate={rate!r}; "
260                 f"total_received={total_rcvd}; "
261                 f"total_sent={total_sent}; "
262                 f"frame_loss={lost_a + lost_b}; "
263                 f"target_duration={duration!r}; "
264                 f"approximated_duration={approximated_duration!r}; "
265                 f"latency_stream_0(usec)={lat_a}; "
266                 f"latency_stream_1(usec)={lat_b}; "
267             )
268
269
270 def main():
271     """Main function for the traffic generator using T-rex.
272
273     It verifies the given command line arguments and runs "simple_burst"
274     function.
275     """
276     parser = argparse.ArgumentParser()
277     parser.add_argument(
278         u"-p", u"--profile", required=True, type=str,
279         help=u"Python traffic profile."
280     )
281     parser.add_argument(
282         u"-d", u"--duration", required=True, type=float,
283         help=u"Duration of traffic run."
284     )
285     parser.add_argument(
286         u"-s", u"--frame_size", required=True,
287         help=u"Size of a Frame without padding and IPG."
288     )
289     parser.add_argument(
290         u"-r", u"--rate", required=True,
291         help=u"Traffic rate with included units (pps)."
292     )
293     parser.add_argument(
294         u"--port_0", required=True, type=int,
295         help=u"Port 0 on the traffic generator."
296     )
297     parser.add_argument(
298         u"--port_1", required=True, type=int,
299         help=u"Port 1 on the traffic generator."
300     )
301     parser.add_argument(
302         u"--async_start", action=u"store_true", default=False,
303         help=u"Non-blocking call of the script."
304     )
305     parser.add_argument(
306         u"--latency", action=u"store_true", default=False,
307         help=u"Add latency stream."
308     )
309     parser.add_argument(
310         u"--traffic_directions", type=int, default=2,
311         help=u"Send bi- (2) or uni- (1) directional traffic."
312     )
313     parser.add_argument(
314         u"--force", action=u"store_true", default=False,
315         help=u"Force start regardless of ports state."
316     )
317
318     args = parser.parse_args()
319
320     try:
321         framesize = int(args.frame_size)
322     except ValueError:
323         framesize = args.frame_size
324
325     simple_burst(
326         profile_file=args.profile,
327         duration=args.duration,
328         framesize=framesize,
329         rate=args.rate,
330         port_0=args.port_0,
331         port_1=args.port_1,
332         latency=args.latency,
333         async_start=args.async_start,
334         traffic_directions=args.traffic_directions,
335         force=args.force,
336     )
337
338
339 if __name__ == u"__main__":
340     main()