15a02259117fb122ce562f4a0f2016d54ebcfaf9
[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
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 async_start:
190             # For async stop, we need to export the current snapshot.
191             xsnap0 = client.ports[0].get_xstats().reference_stats
192             print("Xstats snapshot 0: {s!r}".format(s=xsnap0))
193             if traffic_directions > 1:
194                 xsnap1 = client.ports[1].get_xstats().reference_stats
195                 print("Xstats snapshot 1: {s!r}".format(s=xsnap1))
196         else:
197             # Block until done:
198             client.wait_on_traffic(ports=ports, timeout=duration+30)
199
200             if client.get_warnings():
201                 for warning in client.get_warnings():
202                     print(warning)
203
204             # Read the stats after the test
205             stats = client.get_stats()
206
207             print("##### Statistics #####")
208             print(json.dumps(stats, indent=4, separators=(',', ': ')))
209
210             lost_a = stats[port_0]["opackets"] - stats[port_1]["ipackets"]
211             if traffic_directions > 1:
212                 lost_b = stats[port_1]["opackets"] - stats[port_0]["ipackets"]
213
214             # Stats index is not a port number, but "pgid".
215             # TODO: Find out what "pgid" means.
216             if latency:
217                 lat_a = fmt_latency(
218                     str(stats["latency"][0]["latency"]["total_min"]),
219                     str(stats["latency"][0]["latency"]["average"]),
220                     str(stats["latency"][0]["latency"]["total_max"]))
221                 if traffic_directions > 1:
222                     lat_b = fmt_latency(
223                         str(stats["latency"][1]["latency"]["total_min"]),
224                         str(stats["latency"][1]["latency"]["average"]),
225                         str(stats["latency"][1]["latency"]["total_max"]))
226
227             if traffic_directions > 1:
228                 total_sent = stats[0]["opackets"] + stats[1]["opackets"]
229                 total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
230             else:
231                 total_sent = stats[port_0]["opackets"]
232                 total_rcvd = stats[port_1]["ipackets"]
233
234             print("\npackets lost from {p_0} --> {p_1}:   {v} pkts".format(
235                 p_0=port_0, p_1=port_1, v=lost_a))
236             if traffic_directions > 1:
237                 print("packets lost from {p_1} --> {p_0}:   {v} pkts".format(
238                     p_0=port_0, p_1=port_1, v=lost_b))
239
240     except STLError as ex_error:
241         print(ex_error, file=sys.stderr)
242         sys.exit(1)
243
244     finally:
245         if async_start:
246             if client:
247                 client.disconnect(stop_traffic=False, release_ports=True)
248         else:
249             if client:
250                 client.disconnect()
251             print("rate={0!r}, totalReceived={1}, totalSent={2}, "
252                   "frameLoss={3}, latencyStream0(usec)={4}, "
253                   "latencyStream1(usec)={5}, targetDuration={d!r}".
254                   format(rate, total_rcvd, total_sent, lost_a + lost_b,
255                          lat_a, lat_b, d=duration))
256
257
258 def main():
259     """Main function for the traffic generator using T-rex.
260
261     It verifies the given command line arguments and runs "simple_burst"
262     function.
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_start",
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("--traffic_directions",
300                         type=int,
301                         default=2,
302                         help="Send bi- (2) or uni- (1) directional 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_start,
320                  traffic_directions=args.traffic_directions)
321
322
323 if __name__ == '__main__':
324     main()