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