STL traffic: Use the sleep+stop logic
[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.88/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         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     lost_a = 0
130     lost_b = 0
131     lat_a = u"-1/-1/-1/"
132     lat_b = u"-1/-1/-1/"
133
134     # Read the profile:
135     try:
136         print(f"### Profile file:\n{profile_file}")
137         profile = STLProfile.load(
138             profile_file, direction=0, port_id=0, framesize=framesize,
139             rate=rate
140         )
141         streams = profile.get_streams()
142     except STLError:
143         print(f"Error while loading profile '{profile_file}'!")
144         raise
145
146     try:
147         # Create the client:
148         client = STLClient()
149         # Connect to server:
150         client.connect()
151         # Prepare our ports (the machine has 0 <--> 1 with static route):
152         client.reset(ports=[port_0, port_1])
153         client.remove_all_streams(ports=[port_0, port_1])
154
155         if u"macsrc" in profile_file:
156             client.set_port_attr(ports=[port_0, port_1], promiscuous=True)
157         if isinstance(framesize, int):
158             last_stream_a = int((len(streams) - 2 ) / 2)
159             last_stream_b = (last_stream_a * 2)
160             client.add_streams(streams[0:last_stream_a], ports=[port_0])
161             if traffic_directions > 1:
162                 client.add_streams(
163                     streams[last_stream_a:last_stream_b], ports=[port_1])
164         elif isinstance(framesize, str):
165             client.add_streams(streams[0:3], ports=[port_0])
166             if traffic_directions > 1:
167                 client.add_streams(streams[3:6], ports=[port_1])
168         if latency:
169             try:
170                 if isinstance(framesize, int):
171                     client.add_streams(streams[last_stream_b], ports=[port_0])
172                     if traffic_directions > 1:
173                         client.add_streams(
174                             streams[last_stream_b + 1], ports=[port_1])
175                 elif isinstance(framesize, str):
176                     latency = False
177             except STLError:
178                 # Disable latency if NIC does not support requested stream type
179                 print(u"##### FAILED to add latency streams #####")
180                 latency = False
181         ports = [port_0]
182         if traffic_directions > 1:
183             ports.append(port_1)
184
185         # Clear the stats before injecting:
186         client.clear_stats()
187         lost_a = 0
188         lost_b = 0
189
190         # Choose rate and start traffic:
191         client.start(
192             ports=ports,
193             mult=rate,
194             duration=duration,
195             force=force,
196             core_mask=STLClient.CORE_MASK_PIN,
197         )
198
199         if async_start:
200             # For async stop, we need to export the current snapshot.
201             xsnap0 = client.ports[0].get_xstats().reference_stats
202             print(f"Xstats snapshot 0: {xsnap0!r}")
203             if traffic_directions > 1:
204                 xsnap1 = client.ports[1].get_xstats().reference_stats
205                 print(f"Xstats snapshot 1: {xsnap1!r}")
206         else:
207             time_start = time.monotonic()
208             # wait_on_traffic fails if duration stretches by 30 seconds or more.
209             # TRex has some overhead, wait some more.
210             time.sleep(duration + delay)
211             client.stop()
212             time_stop = time.monotonic()
213             approximated_duration = time_stop - time_start - delay
214             # Read the stats after the traffic stopped (or time up).
215             stats = client.get_stats()
216             if client.get_warnings():
217                 for warning in client.get_warnings():
218                     print(warning)
219             # Now finish the complete reset.
220             client.reset()
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
248             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
249             if traffic_directions > 1:
250                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
251
252     except STLError:
253         print(u"T-Rex STL runtime error!", file=sys.stderr)
254         raise
255
256     finally:
257         if async_start:
258             if client:
259                 client.disconnect(stop_traffic=False, release_ports=True)
260         else:
261             if client:
262                 client.disconnect()
263             print(
264                 f"rate={rate!r}; "
265                 f"total_received={total_rcvd}; "
266                 f"total_sent={total_sent}; "
267                 f"frame_loss={lost_a + lost_b}; "
268                 f"target_duration={duration!r}; "
269                 f"approximated_duration={approximated_duration!r}; "
270                 f"latency_stream_0(usec)={lat_a}; "
271                 f"latency_stream_1(usec)={lat_b}; "
272             )
273
274
275 def main():
276     """Main function for the traffic generator using T-rex.
277
278     It verifies the given command line arguments and runs "simple_burst"
279     function.
280     """
281     parser = argparse.ArgumentParser()
282     parser.add_argument(
283         u"-p", u"--profile", required=True, type=str,
284         help=u"Python traffic profile."
285     )
286     parser.add_argument(
287         u"-d", u"--duration", required=True, type=float,
288         help=u"Duration of traffic run."
289     )
290     parser.add_argument(
291         u"-s", u"--frame_size", required=True,
292         help=u"Size of a Frame without padding and IPG."
293     )
294     parser.add_argument(
295         u"-r", u"--rate", required=True,
296         help=u"Traffic rate with included units (pps)."
297     )
298     parser.add_argument(
299         u"--port_0", required=True, type=int,
300         help=u"Port 0 on the traffic generator."
301     )
302     parser.add_argument(
303         u"--port_1", required=True, type=int,
304         help=u"Port 1 on the traffic generator."
305     )
306     parser.add_argument(
307         u"--async_start", action=u"store_true", default=False,
308         help=u"Non-blocking call of the script."
309     )
310     parser.add_argument(
311         u"--latency", action=u"store_true", default=False,
312         help=u"Add latency stream."
313     )
314     parser.add_argument(
315         u"--traffic_directions", type=int, default=2,
316         help=u"Send bi- (2) or uni- (1) directional traffic."
317     )
318     parser.add_argument(
319         u"--force", action=u"store_true", default=False,
320         help=u"Force start regardless of ports state."
321     )
322     parser.add_argument(
323         u"--delay", required=True, type=float, default=0.0,
324         help=u"Delay assumed for traffic, sleep time is increased by this [s]."
325     )
326
327     args = parser.parse_args()
328
329     try:
330         framesize = int(args.frame_size)
331     except ValueError:
332         framesize = args.frame_size
333
334     simple_burst(
335         profile_file=args.profile,
336         duration=args.duration,
337         framesize=framesize,
338         rate=args.rate,
339         port_0=args.port_0,
340         port_1=args.port_1,
341         latency=args.latency,
342         async_start=args.async_start,
343         traffic_directions=args.traffic_directions,
344         force=args.force,
345         delay=args.delay,
346     )
347
348
349 if __name__ == u"__main__":
350     main()