feat(core): Multilink TRex Sync mode I.
[csit.git] / GPL / tools / trex / trex_stl_profile.py
1 #!/usr/bin/python3
2
3 # Copyright (c) 2023 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, "/opt/trex-core-3.03/scripts/automation/trex_control_plane/interactive/"
38 )
39 from trex.stl.api import STLClient, STLProfile, STLError
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 "/".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         delay=0.0,
84     ):
85     """Send traffic and measure packet loss and latency.
86
87     Procedure:
88      - reads the given traffic profile with streams,
89      - connects to the T-rex client,
90      - resets the ports,
91      - removes all existing streams,
92      - adds streams from the traffic profile to the ports,
93      - if the warm-up time is more than 0, sends the warm-up traffic, reads the
94        statistics,
95      - clears the statistics from the client,
96      - starts the traffic,
97      - waits for the defined time (or runs forever if async mode is defined),
98      - stops the traffic,
99      - reads and displays the statistics and
100      - disconnects from the client.
101
102     :param profile_file: A python module with T-rex traffic profile.
103     :param framesize: Frame size.
104     :param duration: Duration of traffic run in seconds (-1=infinite).
105     :param rate: Traffic rate [percentage, pps, bps].
106     :param port_0: Port 0 on the traffic generator.
107     :param port_1: Port 1 on the traffic generator.
108     :param latency: With latency stats.
109     :param async_start: Start the traffic and exit.
110     :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
111     :param force: Force start regardless of ports state.
112     :param delay: Sleep overhead [s].
113     :type profile_file: str
114     :type framesize: int or str
115     :type duration: float
116     :type rate: str
117     :type port_0: int
118     :type port_1: int
119     :type latency: bool
120     :type async_start: bool
121     :type traffic_directions: int
122     :type force: bool
123     :type delay: float
124     """
125     client = None
126     total_rcvd = 0
127     total_sent = 0
128     approximated_duration = 0.0
129     lat_a = "-1/-1/-1/"
130     lat_b = "-1/-1/-1/"
131
132     # Read the profile:
133     try:
134         print(f"### Profile file:\n{profile_file}")
135         profile = STLProfile.load(
136             profile_file, direction=0, port_id=0, framesize=framesize,
137             rate=rate
138         )
139         streams = profile.get_streams()
140     except STLError:
141         print(f"Error while loading profile '{profile_file}'!")
142         raise
143
144     try:
145         # Create the client:
146         client = STLClient()
147         # Connect to server:
148         client.connect()
149         # Prepare our ports (the machine has 0 <--> 1 with static route):
150         client.reset()
151         client.remove_all_streams()
152
153         if "macsrc" in profile_file:
154             client.set_port_attr(promiscuous=True)
155         if isinstance(framesize, int):
156             last_stream_a = int((len(streams) - 2) / 2)
157             last_stream_b = (last_stream_a * 2)
158             client.add_streams(streams[0:last_stream_a], ports=[port_0])
159             if traffic_directions > 1:
160                 client.add_streams(
161                     streams[last_stream_a:last_stream_b], ports=[port_1])
162         elif isinstance(framesize, str):
163             client.add_streams(streams[0:3], ports=[port_0])
164             if traffic_directions > 1:
165                 client.add_streams(streams[3:6], ports=[port_1])
166         if latency:
167             try:
168                 if isinstance(framesize, int):
169                     client.add_streams(streams[last_stream_b], ports=[port_0])
170                     if traffic_directions > 1:
171                         client.add_streams(
172                             streams[last_stream_b + 1], ports=[port_1])
173                 elif isinstance(framesize, str):
174                     latency = False
175             except STLError:
176                 # Disable latency if NIC does not support requested stream type
177                 print("##### FAILED to add latency streams #####")
178                 latency = False
179         # Even for unidir, both ports are needed to see both rx and tx.
180         ports = [port_0, port_1]
181
182         # Clear the stats before injecting:
183         client.clear_stats()
184
185         # Choose rate and start traffic:
186         client.start(
187             ports=ports[:traffic_directions],
188             mult=rate,
189             duration=duration,
190             force=force,
191             core_mask=STLClient.CORE_MASK_PIN,
192         )
193
194         if async_start:
195             # For async stop, we need to export the current snapshot.
196             for i in range(len(client.ports)):
197                 xsnap = client.ports[i].get_xstats().reference_stats
198                 print(f"Xstats snapshot {i}: {xsnap!r}")
199         else:
200             time_start = time.monotonic()
201             # wait_on_traffic fails if duration stretches by 30 seconds or more.
202             # TRex has some overhead, wait some more.
203             time.sleep(duration + delay)
204             client.stop()
205             time_stop = time.monotonic()
206             approximated_duration = time_stop - time_start - delay
207             # Read the stats after the traffic stopped (or time up).
208             stats = client.get_stats()
209             if client.get_warnings():
210                 for warning in client.get_warnings():
211                     print(warning)
212             # Now finish the complete reset.
213             client.reset()
214
215             print("##### Statistics #####")
216             print(json.dumps(stats, indent=4, separators=(",", ": ")))
217
218             nr_ports = len(client.ports)
219             for i,j in zip(range(nr_ports)[0::2], range(nr_ports)[1::2]):
220                 lost_r = stats[i]["opackets"] - stats[j]["ipackets"]
221                 lost_l = stats[j]["opackets"] - stats[i]["ipackets"]
222                 print(f"packets lost from {i} --> {j}: {lost_r} pkts")
223                 print(f"packets lost from {j} --> {i}: {lost_l} pkts")
224
225             # Stats index is not a port number, but "pgid".
226             # We will take latency read from only first link.
227             if latency:
228                 lat_obj = stats["latency"][0]["latency"]
229                 lat_a = fmt_latency(
230                     str(lat_obj["total_min"]), str(lat_obj["average"]),
231                     str(lat_obj["total_max"]), str(lat_obj["hdrh"]))
232                 # Do not bother with the other dir latency if unidir.
233                 if traffic_directions > 1:
234                     lat_obj = stats["latency"][1]["latency"]
235                     lat_b = fmt_latency(
236                         str(lat_obj["total_min"]), str(lat_obj["average"]),
237                         str(lat_obj["total_max"]), str(lat_obj["hdrh"]))
238
239             total_rcvd = stats["total"]["ipackets"]
240             total_sent = stats["total"]["opackets"]
241
242     except STLError:
243         print("T-Rex STL runtime error!", file=sys.stderr)
244         raise
245
246     finally:
247         if async_start:
248             if client:
249                 client.disconnect(stop_traffic=False, release_ports=True)
250         else:
251             if client:
252                 client.disconnect()
253             print(
254                 f"rate={rate!r}; "
255                 f"total_received={total_rcvd}; "
256                 f"total_sent={total_sent}; "
257                 f"frame_loss={total_sent - total_rcvd}; "
258                 f"target_duration={duration!r}; "
259                 f"approximated_duration={approximated_duration!r}; "
260                 f"latency_stream_0(usec)={lat_a}; "
261                 f"latency_stream_1(usec)={lat_b}; "
262             )
263
264
265 def main():
266     """Main function for the traffic generator using T-rex.
267
268     It verifies the given command line arguments and runs "simple_burst"
269     function.
270     """
271     parser = argparse.ArgumentParser()
272     parser.add_argument(
273         "-p", "--profile", required=True, type=str,
274         help="Python traffic profile."
275     )
276     parser.add_argument(
277         "-d", "--duration", required=True, type=float,
278         help="Duration of traffic run."
279     )
280     parser.add_argument(
281         "-s", "--frame_size", required=True,
282         help="Size of a Frame without padding and IPG."
283     )
284     parser.add_argument(
285         "-r", "--rate", required=True,
286         help="Traffic rate with included units (pps)."
287     )
288     parser.add_argument(
289         "--port_0", required=True, type=int,
290         help="Port 0 on the traffic generator."
291     )
292     parser.add_argument(
293         "--port_1", required=True, type=int,
294         help="Port 1 on the traffic generator."
295     )
296     parser.add_argument(
297         "--async_start", action="store_true", default=False,
298         help="Non-blocking call of the script."
299     )
300     parser.add_argument(
301         "--latency", action="store_true", default=False,
302         help="Add latency stream."
303     )
304     parser.add_argument(
305         "--traffic_directions", type=int, default=2,
306         help="Send bi- (2) or uni- (1) directional traffic."
307     )
308     parser.add_argument(
309         "--force", action="store_true", default=False,
310         help="Force start regardless of ports state."
311     )
312     parser.add_argument(
313         "--delay", required=True, type=float, default=0.0,
314         help="Delay assumed for traffic, sleep time is increased by this [s]."
315     )
316
317     args = parser.parse_args()
318
319     try:
320         framesize = int(args.frame_size)
321     except ValueError:
322         framesize = args.frame_size
323
324     simple_burst(
325         profile_file=args.profile,
326         duration=args.duration,
327         framesize=framesize,
328         rate=args.rate,
329         port_0=args.port_0,
330         port_1=args.port_1,
331         latency=args.latency,
332         async_start=args.async_start,
333         traffic_directions=args.traffic_directions,
334         force=args.force,
335         delay=args.delay,
336     )
337
338
339 if __name__ == "__main__":
340     main()