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