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