b888bcdea9f07332bcf31e64f75c04d596581ee1
[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, hdrh):
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     :param hdrh: Base64 encoded compressed HDRHistogram object.
37     :type lat_min: str
38     :type lat_avg: str
39     :type lat_max: str
40     :type hdrh: str
41     :return: Formatted and rounded output (hdrh unchanged) "min/avg/max/hdrh".
42     :rtype: str
43     """
44     try:
45         t_min = int(round(float(lat_min)))
46     except ValueError:
47         t_min = int(-1)
48     try:
49         t_avg = int(round(float(lat_avg)))
50     except ValueError:
51         t_avg = int(-1)
52     try:
53         t_max = int(round(float(lat_max)))
54     except ValueError:
55         t_max = int(-1)
56
57     return "/".join(str(tmp) for tmp in (t_min, t_avg, t_max, hdrh))
58
59
60 def simple_burst(profile_file, duration, framesize, rate, warmup_time, port_0,
61                  port_1, latency, async_start=False, traffic_directions=2):
62     """Send traffic and measure packet loss and latency.
63
64     Procedure:
65      - reads the given traffic profile with streams,
66      - connects to the T-rex client,
67      - resets the ports,
68      - removes all existing streams,
69      - adds streams from the traffic profile to the ports,
70      - if the warm-up time is more than 0, sends the warm-up traffic, reads the
71        statistics,
72      - clears the statistics from the client,
73      - starts the traffic,
74      - waits for the defined time (or runs forever if async mode is defined),
75      - stops the traffic,
76      - reads and displays the statistics and
77      - disconnects from the client.
78
79     :param profile_file: A python module with T-rex traffic profile.
80     :param framesize: Frame size.
81     :param duration: Duration of traffic run in seconds (-1=infinite).
82     :param rate: Traffic rate [percentage, pps, bps].
83     :param warmup_time: Traffic warm-up time in seconds, 0 = disable.
84     :param port_0: Port 0 on the traffic generator.
85     :param port_1: Port 1 on the traffic generator.
86     :param latency: With latency stats.
87     :param async_start: Start the traffic and exit.
88     :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic.
89     :type profile_file: str
90     :type framesize: int or str
91     :type duration: float
92     :type rate: str
93     :type warmup_time: float
94     :type port_0: int
95     :type port_1: int
96     :type latency: bool
97     :type async_start: bool
98     :type traffic_directions: int
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
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 traffic_directions > 1:
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
173             lost_a = stats[port_0]["opackets"] - stats[port_1]["ipackets"]
174             if traffic_directions > 1:
175                 lost_b = stats[port_1]["opackets"] - stats[port_0]["ipackets"]
176
177             print("\npackets lost from {p_0} --> {p_1}: {v} pkts".format(
178                 p_0=port_0, p_1=port_1, v=lost_a))
179             if traffic_directions > 1:
180                 print("packets lost from {p_1} --> {p_0}: {v} pkts".format(
181                     p_0=port_0, p_1=port_1, v=lost_b))
182
183         # Clear the stats before injecting:
184         client.clear_stats()
185         lost_a = 0
186         lost_b = 0
187
188         # Choose rate and start traffic:
189         client.start(ports=ports, mult=rate, duration=duration)
190
191         if async_start:
192             # For async stop, we need to export the current snapshot.
193             xsnap0 = client.ports[0].get_xstats().reference_stats
194             print("Xstats snapshot 0: {s!r}".format(s=xsnap0))
195             if traffic_directions > 1:
196                 xsnap1 = client.ports[1].get_xstats().reference_stats
197                 print("Xstats snapshot 1: {s!r}".format(s=xsnap1))
198         else:
199             # Block until done:
200             client.wait_on_traffic(ports=ports, timeout=duration+30)
201
202             if client.get_warnings():
203                 for warning in client.get_warnings():
204                     print(warning)
205
206             # Read the stats after the test
207             stats = client.get_stats()
208
209             print("##### Statistics #####")
210             print(json.dumps(stats, indent=4, separators=(',', ': ')))
211
212             lost_a = stats[port_0]["opackets"] - stats[port_1]["ipackets"]
213             if traffic_directions > 1:
214                 lost_b = stats[port_1]["opackets"] - stats[port_0]["ipackets"]
215
216             # Stats index is not a port number, but "pgid".
217             # TODO: Find out what "pgid" means.
218             if latency:
219                 lat_obj = stats["latency"][0]["latency"]
220                 lat_a = fmt_latency(
221                     str(lat_obj["total_min"]), str(lat_obj["average"]),
222                     str(lat_obj["total_max"]), str(lat_obj["hdrh"]))
223                 if traffic_directions > 1:
224                     lat_obj = stats["latency"][1]["latency"]
225                     lat_b = fmt_latency(
226                         str(lat_obj["total_min"]), str(lat_obj["average"]),
227                         str(lat_obj["total_max"]), str(lat_obj["hdrh"]))
228
229             if traffic_directions > 1:
230                 total_sent = stats[0]["opackets"] + stats[1]["opackets"]
231                 total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
232             else:
233                 total_sent = stats[port_0]["opackets"]
234                 total_rcvd = stats[port_1]["ipackets"]
235
236             print("\npackets lost from {p_0} --> {p_1}:   {v} pkts".format(
237                 p_0=port_0, p_1=port_1, v=lost_a))
238             if traffic_directions > 1:
239                 print("packets lost from {p_1} --> {p_0}:   {v} pkts".format(
240                     p_0=port_0, p_1=port_1, v=lost_b))
241
242     except STLError as ex_error:
243         print(ex_error, file=sys.stderr)
244         sys.exit(1)
245
246     finally:
247         if async_start:
248             if client:
249                 client.disconnect(stop_traffic=False, release_ports=True)
250         else:
251             if client:
252                 client.disconnect()
253             print("rate={0!r}, totalReceived={1}, totalSent={2}, "
254                   "frameLoss={3}, latencyStream0(usec)={4}, "
255                   "latencyStream1(usec)={5}, targetDuration={d!r}".
256                   format(rate, total_rcvd, total_sent, lost_a + lost_b,
257                          lat_a, lat_b, d=duration))
258
259
260 def main():
261     """Main function for the traffic generator using T-rex.
262
263     It verifies the given command line arguments and runs "simple_burst"
264     function.
265     """
266     parser = argparse.ArgumentParser()
267     parser.add_argument("-p", "--profile",
268                         required=True,
269                         type=str,
270                         help="Python traffic profile.")
271     parser.add_argument("-d", "--duration",
272                         required=True,
273                         type=float,
274                         help="Duration of traffic run.")
275     parser.add_argument("-s", "--frame_size",
276                         required=True,
277                         help="Size of a Frame without padding and IPG.")
278     parser.add_argument("-r", "--rate",
279                         required=True,
280                         help="Traffic rate with included units (%, pps).")
281     parser.add_argument("-w", "--warmup_time",
282                         type=float,
283                         default=5.0,
284                         help="Traffic warm-up time in seconds, 0 = disable.")
285     parser.add_argument("--port_0",
286                         required=True,
287                         type=int,
288                         help="Port 0 on the traffic generator.")
289     parser.add_argument("--port_1",
290                         required=True,
291                         type=int,
292                         help="Port 1 on the traffic generator.")
293     parser.add_argument("--async_start",
294                         action="store_true",
295                         default=False,
296                         help="Non-blocking call of the script.")
297     parser.add_argument("--latency",
298                         action="store_true",
299                         default=False,
300                         help="Add latency stream.")
301     parser.add_argument("--traffic_directions",
302                         type=int,
303                         default=2,
304                         help="Send bi- (2) or uni- (1) directional traffic.")
305
306     args = parser.parse_args()
307
308     try:
309         framesize = int(args.frame_size)
310     except ValueError:
311         framesize = args.frame_size
312
313     simple_burst(profile_file=args.profile,
314                  duration=args.duration,
315                  framesize=framesize,
316                  rate=args.rate,
317                  warmup_time=args.warmup_time,
318                  port_0=args.port_0,
319                  port_1=args.port_1,
320                  latency=args.latency,
321                  async_start=args.async_start,
322                  traffic_directions=args.traffic_directions)
323
324
325 if __name__ == '__main__':
326     main()