15e21570574316923ea5354f70e4ca4a982115de
[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 sys
22 import argparse
23 import json
24
25 sys.path.insert(0, "/opt/trex-core-2.54/scripts/automation/"
26                    "trex_control_plane/interactive/")
27 from trex.stl.api import *
28
29
30 def fmt_latency(lat_min, lat_avg, lat_max):
31     """Return formatted, rounded latency.
32
33     :param lat_min: Min latency
34     :param lat_avg: Average latency
35     :param lat_max: Max latency
36     :type lat_min: string
37     :type lat_avg: string
38     :type lat_max: string
39     :return: Formatted and rounded output "min/avg/max"
40     :rtype: string
41     """
42     try:
43         t_min = int(round(float(lat_min)))
44     except ValueError:
45         t_min = int(-1)
46     try:
47         t_avg = int(round(float(lat_avg)))
48     except ValueError:
49         t_avg = int(-1)
50     try:
51         t_max = int(round(float(lat_max)))
52     except ValueError:
53         t_max = int(-1)
54
55     return "/".join(str(tmp) for tmp in (t_min, t_avg, t_max))
56
57
58 def simple_burst(profile_file, duration, framesize, rate, warmup_time, port_0,
59                  port_1, latency, async_start=False, unidirection=False):
60     """Send traffic and measure packet loss and latency.
61
62     Procedure:
63      - reads the given traffic profile with streams,
64      - connects to the T-rex client,
65      - resets the ports,
66      - removes all existing streams,
67      - adds streams from the traffic profile to the ports,
68      - if the warm-up time is more than 0, sends the warm-up traffic, reads the
69        statistics,
70      - clears the statistics from the client,
71      - starts the traffic,
72      - waits for the defined time (or runs forever if async mode is defined),
73      - stops the traffic,
74      - reads and displays the statistics and
75      - disconnects from the client.
76
77     :param profile_file: A python module with T-rex traffic profile.
78     :param framesize: Frame size.
79     :param duration: Duration of traffic run in seconds (-1=infinite).
80     :param rate: Traffic rate [percentage, pps, bps].
81     :param warmup_time: Traffic warm-up time in seconds, 0 = disable.
82     :param port_0: Port 0 on the traffic generator.
83     :param port_1: Port 1 on the traffic generator.
84     :param latency: With latency stats.
85     :param async_start: Start the traffic and exit.
86     :param unidirection: Traffic is unidirectional.
87     :type profile_file: str
88     :type framesize: int or str
89     :type duration: float
90     :type rate: str
91     :type warmup_time: float
92     :type port_0: int
93     :type port_1: int
94     :type latency: bool
95     :type async_start: bool
96     :type unidirection: bool
97     """
98     client = None
99     total_rcvd = 0
100     total_sent = 0
101     lost_a = 0
102     lost_b = 0
103     lat_a = "-1/-1/-1"
104     lat_b = "-1/-1/-1"
105
106     # Read the profile:
107     try:
108         print("### Profile file:\n{}".format(profile_file))
109         profile = STLProfile.load(profile_file, direction=0, port_id=0,
110                                   framesize=framesize)
111         streams = profile.get_streams()
112     except STLError as err:
113         print("Error while loading profile '{0}' {1}".format(profile_file, err))
114         sys.exit(1)
115
116     try:
117         # Create the client:
118         client = STLClient()
119         # Connect to server:
120         client.connect()
121         # Prepare our ports (the machine has 0 <--> 1 with static route):
122         client.reset(ports=[port_0, port_1])
123         client.remove_all_streams(ports=[port_0, port_1])
124
125         if "macsrc" in profile_file:
126             client.set_port_attr(ports=[port_0, port_1], promiscuous=True)
127         if isinstance(framesize, int):
128             client.add_streams(streams[0], ports=[port_0])
129             if not unidirection:
130                 client.add_streams(streams[1], ports=[port_1])
131         elif isinstance(framesize, str):
132             client.add_streams(streams[0:3], ports=[port_0])
133             if not unidirection:
134                 client.add_streams(streams[3:6], ports=[port_1])
135         if latency:
136             try:
137                 if isinstance(framesize, int):
138                     client.add_streams(streams[2], ports=[port_0])
139                     if not unidirection:
140                         client.add_streams(streams[3], ports=[port_1])
141                 elif isinstance(framesize, str):
142                     latency = False
143             except STLError:
144                 # Disable latency if NIC does not support requested stream type
145                 print("##### FAILED to add latency streams #####")
146                 latency = False
147         ports = [port_0]
148         if not unidirection:
149             ports.append(port_1)
150         # Warm-up phase:
151         if warmup_time > 0:
152             # Clear the stats before injecting:
153             client.clear_stats()
154
155             # Choose rate and start traffic:
156             client.start(ports=ports, mult=rate, duration=warmup_time)
157
158             # Block until done:
159             client.wait_on_traffic(ports=ports, timeout=warmup_time+30)
160
161             if client.get_warnings():
162                 for warning in client.get_warnings():
163                     print(warning)
164
165             # Read the stats after the test:
166             stats = client.get_stats()
167
168             print("##### Warmup statistics #####")
169             print(json.dumps(stats, indent=4, separators=(',', ': ')))
170
171             lost_a = stats[port_0]["opackets"] - stats[port_1]["ipackets"]
172             if not unidirection:
173                 lost_b = stats[port_1]["opackets"] - stats[port_0]["ipackets"]
174
175             print("\npackets lost from {p_0} --> {p_1}: {v} pkts".format(
176                 p_0=port_0, p_1=port_1, v=lost_a))
177             if not unidirection:
178                 print("packets lost from {p_1} --> {p_0}: {v} pkts".format(
179                     p_0=port_0, p_1=port_1, v=lost_b))
180
181         # Clear the stats before injecting:
182         client.clear_stats()
183         lost_a = 0
184         lost_b = 0
185
186         # Choose rate and start traffic:
187         client.start(ports=ports, mult=rate, duration=duration)
188
189         if not async_start:
190             # Block until done:
191             client.wait_on_traffic(ports=ports, timeout=duration+30)
192
193             if client.get_warnings():
194                 for warning in client.get_warnings():
195                     print(warning)
196
197             # Read the stats after the test
198             stats = client.get_stats()
199
200             print("##### Statistics #####")
201             print(json.dumps(stats, indent=4, separators=(',', ': ')))
202
203             lost_a = stats[port_0]["opackets"] - stats[port_1]["ipackets"]
204             if not unidirection:
205                 lost_b = stats[port_1]["opackets"] - stats[port_0]["ipackets"]
206
207             if latency:
208                 lat_a = fmt_latency(
209                     str(stats["latency"][port_0]["latency"]["total_min"]),
210                     str(stats["latency"][port_0]["latency"]["average"]),
211                     str(stats["latency"][port_0]["latency"]["total_max"]))
212                 if not unidirection:
213                     lat_b = fmt_latency(
214                         str(stats["latency"][port_1]["latency"]["total_min"]),
215                         str(stats["latency"][port_1]["latency"]["average"]),
216                         str(stats["latency"][port_1]["latency"]["total_max"]))
217
218             if not unidirection:
219                 total_sent = stats[0]["opackets"] + stats[1]["opackets"]
220                 total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
221             else:
222                 total_sent = stats[port_0]["opackets"]
223                 total_rcvd = stats[port_1]["ipackets"]
224
225             print("\npackets lost from {p_0} --> {p_1}:   {v} pkts".format(
226                 p_0=port_0, p_1=port_1, v=lost_a))
227             if not unidirection:
228                 print("packets lost from {p_1} --> {p_0}:   {v} pkts".format(
229                 p_0=port_0, p_1=port_1, v=lost_b))
230
231     except STLError as ex_error:
232         print(ex_error, file=sys.stderr)
233         sys.exit(1)
234
235     finally:
236         if async_start:
237             if client:
238                 client.disconnect(stop_traffic=False, release_ports=True)
239         else:
240             if client:
241                 client.disconnect()
242             print("rate={0!r}, totalReceived={1}, totalSent={2}, "
243                   "frameLoss={3}, latencyStream0(usec)={4}, "
244                   "latencyStream1(usec)={5}, targetDuration={d!r}".
245                   format(rate, total_rcvd, total_sent, lost_a + lost_b,
246                          lat_a, lat_b, d=duration))
247
248
249 def main():
250     """Main function for the traffic generator using T-rex.
251
252     It verifies the given command line arguments and runs "simple_burst"
253     function.
254     """
255     parser = argparse.ArgumentParser()
256     parser.add_argument("-p", "--profile",
257                         required=True,
258                         type=str,
259                         help="Python traffic profile.")
260     parser.add_argument("-d", "--duration",
261                         required=True,
262                         type=float,
263                         help="Duration of traffic run.")
264     parser.add_argument("-s", "--frame_size",
265                         required=True,
266                         help="Size of a Frame without padding and IPG.")
267     parser.add_argument("-r", "--rate",
268                         required=True,
269                         help="Traffic rate with included units (%, pps).")
270     parser.add_argument("-w", "--warmup_time",
271                         type=float,
272                         default=5.0,
273                         help="Traffic warm-up time in seconds, 0 = disable.")
274     parser.add_argument("--port_0",
275                         required=True,
276                         type=int,
277                         help="Port 0 on the traffic generator.")
278     parser.add_argument("--port_1",
279                         required=True,
280                         type=int,
281                         help="Port 1 on the traffic generator.")
282     parser.add_argument("--async",
283                         action="store_true",
284                         default=False,
285                         help="Non-blocking call of the script.")
286     parser.add_argument("--latency",
287                         action="store_true",
288                         default=False,
289                         help="Add latency stream.")
290     parser.add_argument("--unidirection",
291                         action="store_true",
292                         default=False,
293                         help="Send unidirection traffic.")
294
295     args = parser.parse_args()
296
297     try:
298         framesize = int(args.frame_size)
299     except ValueError:
300         framesize = args.frame_size
301
302     simple_burst(profile_file=args.profile,
303                  duration=args.duration,
304                  framesize=framesize,
305                  rate=args.rate,
306                  warmup_time=args.warmup_time,
307                  port_0=args.port_0,
308                  port_1=args.port_1,
309                  latency=args.latency,
310                  async_start=args.async,
311                  unidirection=args.unidirection)
312
313
314 if __name__ == '__main__':
315     main()