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