2837778f7a98c7941c7b2a2a7ada29fe702223a6
[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.61/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, traffic_directions=2):
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 traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
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 traffic_directions: int
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
205                 lost_b = stats[port_1]["opackets"] - stats[port_0]["ipackets"]
206
207             # Stats index is not a port number, but "pgid".
208             # TODO: Find out what "pgid" means.
209             if latency:
210                 lat_a = fmt_latency(
211                     str(stats["latency"][0]["latency"]["total_min"]),
212                     str(stats["latency"][0]["latency"]["average"]),
213                     str(stats["latency"][0]["latency"]["total_max"]))
214                 if traffic_directions > 1:
215                     lat_b = fmt_latency(
216                         str(stats["latency"][1]["latency"]["total_min"]),
217                         str(stats["latency"][1]["latency"]["average"]),
218                         str(stats["latency"][1]["latency"]["total_max"]))
219
220             if traffic_directions > 1:
221                 total_sent = stats[0]["opackets"] + stats[1]["opackets"]
222                 total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
223             else:
224                 total_sent = stats[port_0]["opackets"]
225                 total_rcvd = stats[port_1]["ipackets"]
226
227             print("\npackets lost from {p_0} --> {p_1}:   {v} pkts".format(
228                 p_0=port_0, p_1=port_1, v=lost_a))
229             if traffic_directions > 1:
230                 print("packets lost from {p_1} --> {p_0}:   {v} pkts".format(
231                 p_0=port_0, p_1=port_1, v=lost_b))
232
233     except STLError as ex_error:
234         print(ex_error, file=sys.stderr)
235         sys.exit(1)
236
237     finally:
238         if async_start:
239             if client:
240                 client.disconnect(stop_traffic=False, release_ports=True)
241         else:
242             if client:
243                 client.disconnect()
244             print("rate={0!r}, totalReceived={1}, totalSent={2}, "
245                   "frameLoss={3}, latencyStream0(usec)={4}, "
246                   "latencyStream1(usec)={5}, targetDuration={d!r}".
247                   format(rate, total_rcvd, total_sent, lost_a + lost_b,
248                          lat_a, lat_b, d=duration))
249
250
251 def main():
252     """Main function for the traffic generator using T-rex.
253
254     It verifies the given command line arguments and runs "simple_burst"
255     function.
256     """
257     parser = argparse.ArgumentParser()
258     parser.add_argument("-p", "--profile",
259                         required=True,
260                         type=str,
261                         help="Python traffic profile.")
262     parser.add_argument("-d", "--duration",
263                         required=True,
264                         type=float,
265                         help="Duration of traffic run.")
266     parser.add_argument("-s", "--frame_size",
267                         required=True,
268                         help="Size of a Frame without padding and IPG.")
269     parser.add_argument("-r", "--rate",
270                         required=True,
271                         help="Traffic rate with included units (%, pps).")
272     parser.add_argument("-w", "--warmup_time",
273                         type=float,
274                         default=5.0,
275                         help="Traffic warm-up time in seconds, 0 = disable.")
276     parser.add_argument("--port_0",
277                         required=True,
278                         type=int,
279                         help="Port 0 on the traffic generator.")
280     parser.add_argument("--port_1",
281                         required=True,
282                         type=int,
283                         help="Port 1 on the traffic generator.")
284     parser.add_argument("--async",
285                         action="store_true",
286                         default=False,
287                         help="Non-blocking call of the script.")
288     parser.add_argument("--latency",
289                         action="store_true",
290                         default=False,
291                         help="Add latency stream.")
292     parser.add_argument("--traffic_directions",
293                         type=int,
294                         default=2,
295                         help="Send bi- (2) or uni- (1) directional traffic.")
296
297     args = parser.parse_args()
298
299     try:
300         framesize = int(args.frame_size)
301     except ValueError:
302         framesize = args.frame_size
303
304     simple_burst(profile_file=args.profile,
305                  duration=args.duration,
306                  framesize=framesize,
307                  rate=args.rate,
308                  warmup_time=args.warmup_time,
309                  port_0=args.port_0,
310                  port_1=args.port_1,
311                  latency=args.latency,
312                  async_start=args.async,
313                  traffic_directions=args.traffic_directions)
314
315
316 if __name__ == '__main__':
317     main()