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