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