Python3: resources and libraries
[csit.git] / resources / tools / trex / trex_stateless_profile.py
1 #!/usr/bin/python3
2
3 # Copyright (c) 2019 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
25 sys.path.insert(
26     0, "/opt/trex-core-2.61/scripts/automation/trex_control_plane/interactive/"
27 )
28 from trex.stl.api import *
29
30
31 def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
32     """Return formatted, rounded latency.
33
34     :param lat_min: Min latency
35     :param lat_avg: Average latency
36     :param lat_max: Max latency
37     :param hdrh: Base64 encoded compressed HDRHistogram object.
38     :type lat_min: str
39     :type lat_avg: str
40     :type lat_max: str
41     :type hdrh: str
42     :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
43     :rtype: str
44     """
45     try:
46         t_min = int(round(float(lat_min)))
47     except ValueError:
48         t_min = int(-1)
49     try:
50         t_avg = int(round(float(lat_avg)))
51     except ValueError:
52         t_avg = int(-1)
53     try:
54         t_max = int(round(float(lat_max)))
55     except ValueError:
56         t_max = int(-1)
57
58     return u"/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
59
60
61 def simple_burst(
62         profile_file, duration, framesize, rate, warmup_time, port_0, port_1,
63         latency, async_start=False, traffic_directions=2):
64     """Send traffic and measure packet loss and latency.
65
66     Procedure:
67      - reads the given traffic profile with streams,
68      - connects to the T-rex client,
69      - resets the ports,
70      - removes all existing streams,
71      - adds streams from the traffic profile to the ports,
72      - if the warm-up time is more than 0, sends the warm-up traffic, reads the
73        statistics,
74      - clears the statistics from the client,
75      - starts the traffic,
76      - waits for the defined time (or runs forever if async mode is defined),
77      - stops the traffic,
78      - reads and displays the statistics and
79      - disconnects from the client.
80
81     :param profile_file: A python module with T-rex traffic profile.
82     :param framesize: Frame size.
83     :param duration: Duration of traffic run in seconds (-1=infinite).
84     :param rate: Traffic rate [percentage, pps, bps].
85     :param warmup_time: Traffic warm-up time in seconds, 0 = disable.
86     :param port_0: Port 0 on the traffic generator.
87     :param port_1: Port 1 on the traffic generator.
88     :param latency: With latency stats.
89     :param async_start: Start the traffic and exit.
90     :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
91     :type profile_file: str
92     :type framesize: int or str
93     :type duration: float
94     :type rate: str
95     :type warmup_time: float
96     :type port_0: int
97     :type port_1: int
98     :type latency: bool
99     :type async_start: bool
100     :type traffic_directions: int
101     """
102     client = None
103     total_rcvd = 0
104     total_sent = 0
105     lost_a = 0
106     lost_b = 0
107     lat_a = u"-1/-1/-1/"
108     lat_b = u"-1/-1/-1/"
109
110     # Read the profile:
111     try:
112         print(f"### Profile file:\n{profile_file}")
113         profile = STLProfile.load(
114             profile_file, direction=0, port_id=0, framesize=framesize
115         )
116         streams = profile.get_streams()
117     except STLError as err:
118         print(f"Error while loading profile '{profile_file}' {err!r}")
119         sys.exit(1)
120
121     try:
122         # Create the client:
123         client = STLClient()
124         # Connect to server:
125         client.connect()
126         # Prepare our ports (the machine has 0 <--> 1 with static route):
127         client.reset(ports=[port_0, port_1])
128         client.remove_all_streams(ports=[port_0, port_1])
129
130         if u"macsrc" in profile_file:
131             client.set_port_attr(ports=[port_0, port_1], promiscuous=True)
132         if isinstance(framesize, int):
133             client.add_streams(streams[0], ports=[port_0])
134             if traffic_directions > 1:
135                 client.add_streams(streams[1], ports=[port_1])
136         elif isinstance(framesize, str):
137             client.add_streams(streams[0:3], ports=[port_0])
138             if traffic_directions > 1:
139                 client.add_streams(streams[3:6], ports=[port_1])
140         if latency:
141             try:
142                 if isinstance(framesize, int):
143                     client.add_streams(streams[2], ports=[port_0])
144                     if traffic_directions > 1:
145                         client.add_streams(streams[3], ports=[port_1])
146                 elif isinstance(framesize, str):
147                     latency = False
148             except STLError:
149                 # Disable latency if NIC does not support requested stream type
150                 print(u"##### FAILED to add latency streams #####")
151                 latency = False
152         ports = [port_0]
153         if traffic_directions > 1:
154             ports.append(port_1)
155         # Warm-up phase:
156         if warmup_time > 0:
157             # Clear the stats before injecting:
158             client.clear_stats()
159
160             # Choose rate and start traffic:
161             client.start(ports=ports, mult=rate, duration=warmup_time)
162
163             # Block until done:
164             client.wait_on_traffic(ports=ports, timeout=warmup_time+30)
165
166             if client.get_warnings():
167                 for warning in client.get_warnings():
168                     print(warning)
169
170             # Read the stats after the test:
171             stats = client.get_stats()
172
173             print(u"##### Warmup statistics #####")
174             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
175
176             lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
177             if traffic_directions > 1:
178                 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
179
180             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
181             if traffic_directions > 1:
182                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
183
184         # Clear the stats before injecting:
185         client.clear_stats()
186         lost_a = 0
187         lost_b = 0
188
189         # Choose rate and start traffic:
190         client.start(ports=ports, mult=rate, duration=duration)
191
192         if async_start:
193             # For async stop, we need to export the current snapshot.
194             xsnap0 = client.ports[0].get_xstats().reference_stats
195             print(f"Xstats snapshot 0: {xsnap0!r}")
196             if traffic_directions > 1:
197                 xsnap1 = client.ports[1].get_xstats().reference_stats
198                 print(f"Xstats snapshot 1: {xsnap1!r}")
199         else:
200             # Block until done:
201             client.wait_on_traffic(ports=ports, timeout=duration+30)
202
203             if client.get_warnings():
204                 for warning in client.get_warnings():
205                     print(warning)
206
207             # Read the stats after the test
208             stats = client.get_stats()
209
210             print(u"##### Statistics #####")
211             print(json.dumps(stats, indent=4, separators=(u",", u": ")))
212
213             lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
214             if traffic_directions > 1:
215                 lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
216
217             # Stats index is not a port number, but "pgid".
218             # TODO: Find out what "pgid" means.
219             if latency:
220                 lat_obj = stats[u"latency"][0][u"latency"]
221                 lat_a = fmt_latency(
222                     str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
223                     str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
224                 if traffic_directions > 1:
225                     lat_obj = stats[u"latency"][1][u"latency"]
226                     lat_b = fmt_latency(
227                         str(lat_obj[u"total_min"]), str(lat_obj[u"average"]),
228                         str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"]))
229
230             if traffic_directions > 1:
231                 total_sent = stats[0][u"opackets"] + stats[1][u"opackets"]
232                 total_rcvd = stats[0][u"ipackets"] + stats[1][u"ipackets"]
233             else:
234                 total_sent = stats[port_0][u"opackets"]
235                 total_rcvd = stats[port_1][u"ipackets"]
236
237             print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts")
238             if traffic_directions > 1:
239                 print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
240
241     except STLError as ex_error:
242         print(ex_error, file=sys.stderr)
243         sys.exit(1)
244
245     finally:
246         if async_start:
247             if client:
248                 client.disconnect(stop_traffic=False, release_ports=True)
249         else:
250             if client:
251                 client.disconnect()
252             print(
253                 f"rate={rate!r}, totalReceived={total_rcvd}, "
254                 f"totalSent={total_sent}, frameLoss={lost_a + lost_b}, "
255                 f"latencyStream0(usec)={lat_a}, latencyStream1(usec)={lat_b}, "
256                 f"targetDuration={duration!r}"
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"-w", u"--warmup_time", type=float, default=5.0,
285         help=u"Traffic warm-up time in seconds, 0 = disable."
286     )
287     parser.add_argument(
288         u"--port_0", required=True, type=int,
289         help=u"Port 0 on the traffic generator."
290     )
291     parser.add_argument(
292         u"--port_1", required=True, type=int,
293         help=u"Port 1 on the traffic generator."
294     )
295     parser.add_argument(
296         u"--async_start", action=u"store_true", default=False,
297         help=u"Non-blocking call of the script."
298     )
299     parser.add_argument(
300         u"--latency", action=u"store_true", default=False,
301         help=u"Add latency stream."
302     )
303     parser.add_argument(
304         u"--traffic_directions", type=int, default=2,
305         help=u"Send bi- (2) or uni- (1) directional traffic."
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, duration=args.duration, framesize=framesize,
317         rate=args.rate, warmup_time=args.warmup_time, port_0=args.port_0,
318         port_1=args.port_1, latency=args.latency, async_start=args.async_start,
319         traffic_directions=args.traffic_directions
320     )
321
322
323 if __name__ == u"__main__":
324     main()