Dpdk in VM: Increase num_mbufs
[csit.git] / resources / libraries / python / Iperf3.py
1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """iPerf3 utilities library."""
15
16 import json
17
18 from resources.libraries.python.Constants import Constants
19 from resources.libraries.python.CpuUtils import CpuUtils
20 from resources.libraries.python.IPUtil import IPUtil
21 from resources.libraries.python.Namespaces import Namespaces
22 from resources.libraries.python.OptionString import OptionString
23 from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
24
25
26 class Iperf3:
27     """iPerf3 traffic generator utilities."""
28
29     def __init__(self):
30         """Initialize iPerf3 class."""
31         # Computed affinity for iPerf server.
32         self._s_affinity = None
33         # Computed affinity for iPerf client.
34         self._c_affinity = None
35
36     def initialize_iperf_server(
37             self, node, pf_key, interface, bind, bind_gw, bind_mask,
38             namespace=None, cpu_skip_cnt=0, cpu_cnt=1, instances=1):
39         """iPerf3 initialization.
40
41         :param node: Topology node running iPerf3 server.
42         :param pf_key: First TG's interface (To compute numa location).
43         :param interface: Name of TG bind interface.
44         :param bind: Bind to host, one of node's addresses.
45         :param bind_gw: Bind gateway (required for default route).
46         :param bind_mask: Bind address mask.
47         :param namespace: Name of TG namespace to execute.
48         :param cpu_skip_cnt: Amount of CPU cores to skip.
49         :param cpu_cnt: iPerf3 main thread count.
50         :param instances: Number of simultaneous iPerf3 instances.
51         :type node: dict
52         :type pf_key: str
53         :type interface: str
54         :type bind: str
55         :type bind_gw: str
56         :type bind_mask: str
57         :type namespace: str
58         :type cpu_skip_cnt: int
59         :type cpu_cnt: int
60         :type instances: int
61         """
62         if Iperf3.is_iperf_running(node):
63             Iperf3.teardown_iperf(node)
64
65         if namespace:
66             IPUtil.set_linux_interface_ip(
67                 node, interface=interface, ip_addr=bind, prefix=bind_mask,
68                 namespace=namespace)
69             IPUtil.set_linux_interface_up(
70                 node, interface=interface, namespace=namespace)
71             Namespaces.add_default_route_to_namespace(
72                 node, namespace=namespace, default_route=bind_gw)
73
74         # Compute affinity for iPerf server.
75         self._s_affinity = CpuUtils.get_affinity_iperf(
76             node, pf_key, cpu_skip_cnt=cpu_skip_cnt,
77             cpu_cnt=cpu_cnt * instances)
78         # Compute affinity for iPerf client.
79         self._c_affinity = CpuUtils.get_affinity_iperf(
80             node, pf_key, cpu_skip_cnt=cpu_skip_cnt + cpu_cnt * instances,
81             cpu_cnt=cpu_cnt * instances)
82
83         for i in range(0, instances):
84             Iperf3.start_iperf_server(
85                 node, namespace=namespace, port=5201 + i,
86                 affinity=self._s_affinity)
87
88     @staticmethod
89     def start_iperf_server(
90             node, namespace=None, port=5201, affinity=None):
91         """Start iPerf3 server instance as a deamon.
92
93         :param node: Topology node running iPerf3 server.
94         :param namespace: Name of TG namespace to execute.
95         :param port: The server port for the server to listen on.
96         :param affinity: iPerf3 server affinity.
97         :type node: dict
98         :type namespace: str
99         :type port: int
100         :type affinity: str
101         """
102         cmd = IPerf3Server.iperf3_cmdline(
103             namespace=namespace, port=port, affinity=affinity)
104         exec_cmd_no_error(
105             node, cmd, sudo=True, message=u"Failed to start iPerf3 server!")
106
107     @staticmethod
108     def is_iperf_running(node):
109         """Check if iPerf3 is running using pgrep.
110
111         :param node: Topology node running iPerf3.
112         :type node: dict
113         :returns: True if iPerf3 is running otherwise False.
114         :rtype: bool
115         """
116         ret, _, _ = exec_cmd(node, u"pgrep iperf3", sudo=True)
117         return bool(int(ret) == 0)
118
119     @staticmethod
120     def teardown_iperf(node):
121         """iPerf3 teardown.
122
123         :param node: Topology node running iPerf3.
124         :type node: dict
125         """
126         pidfile = u"/tmp/iperf3_server.pid"
127         logfile = u"/tmp/iperf3.log"
128
129         exec_cmd_no_error(
130             node,
131             f"sh -c 'if [ -f {pidfile} ]; then "
132             f"pkill iperf3; "
133             f"cat {logfile}; "
134             f"rm {logfile}; "
135             f"fi'",
136             sudo=True, message=u"iPerf3 kill failed!")
137
138     def iperf_client_start_remote_exec(
139             self, node, duration, rate, frame_size, async_call=False,
140             warmup_time=0, traffic_directions=1, namespace=None, udp=False,
141             host=None, bind=None, affinity=None):
142         """Execute iPerf3 client script on remote node over ssh to start running
143         traffic.
144
145         :param node: Topology node running iPerf3.
146         :param duration: Time expressed in seconds for how long to send traffic.
147         :param rate: Traffic rate.
148         :param frame_size: L2 frame size to send (without padding and IPG).
149         :param async_call: If enabled then don't wait for all incoming traffic.
150         :param warmup_time: Warmup time period.
151         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
152             Default: 1
153         :param namespace: Namespace to execute iPerf3 client on.
154         :param udp: UDP traffic.
155         :param host: Client connecting to an iPerf server running on host.
156         :param bind: Client bind IP address.
157         :param affinity: iPerf3 client affinity.
158         :type node: dict
159         :type duration: float
160         :type rate: str
161         :type frame_size: str
162         :type async_call: bool
163         :type warmup_time: float
164         :type traffic_directions: int
165         :type namespace: str
166         :type udp: bool
167         :type host: str
168         :type bind: str
169         :type affinity: str
170         :returns: List of iPerf3 PIDs.
171         :rtype: list
172         """
173         if not isinstance(duration, (float, int)):
174             duration = float(duration)
175         if not isinstance(warmup_time, (float, int)):
176             warmup_time = float(warmup_time)
177         if not affinity:
178             affinity = self._c_affinity
179
180         kwargs = dict()
181         if namespace:
182             kwargs[u"namespace"] = namespace
183         kwargs[u"host"] = host
184         kwargs[u"bind"] = bind
185         kwargs[u"udp"] = udp
186         if affinity:
187             kwargs[u"affinity"] = affinity
188         kwargs[u"duration"] = duration
189         kwargs[u"rate"] = rate
190         kwargs[u"frame_size"] = frame_size
191         kwargs[u"warmup_time"] = warmup_time
192         kwargs[u"traffic_directions"] = traffic_directions
193         kwargs[u"async_call"] = async_call
194
195         cmd = IPerf3Client.iperf3_cmdline(**kwargs)
196
197         stdout, _ = exec_cmd_no_error(
198             node, cmd, timeout=int(duration) + 30,
199             message=u"iPerf3 runtime error!")
200
201         if async_call:
202             return stdout.split()
203         return json.loads(stdout)
204
205     @staticmethod
206     def iperf_client_stop_remote_exec(node, pids):
207         """Stop iPerf3 client execution.
208
209         :param pids: PID or List of PIDs of iPerf3 client.
210         :type pids: str or list
211         """
212         if not isinstance(pids, list):
213             pids = [pids]
214
215         for pid in pids:
216             exec_cmd_no_error(
217                 node, f"kill {pid}", sudo=True, message=u"Kill iPerf3 failed!")
218
219
220 class IPerf3Server:
221     """iPerf3 server utilities."""
222
223     @staticmethod
224     def iperf3_cmdline(**kwargs):
225         """Get iPerf3 server command line.
226
227         :param kwargs: List of iPerf3 server parameters.
228         :type kwargs: dict
229         :returns: iPerf3 server command line.
230         :rtype: OptionString
231         """
232         cmd = OptionString()
233         if kwargs['namespace']:
234             cmd.add(f"ip netns exec {kwargs['namespace']}")
235         cmd.add(f"iperf3")
236
237         cmd_options = OptionString(prefix=u"--")
238         # Run iPerf in server mode. (This will only allow one iperf connection
239         # at a time)
240         cmd_options.add(
241             u"server")
242
243         # Run the server in background as a daemon.
244         cmd_options.add_if_from_dict(
245             u"daemon", u"daemon", kwargs, True)
246
247         # Write a file with the process ID, most useful when running as a
248         # daemon.
249         cmd_options.add_with_value_from_dict(
250             u"pidfile", u"pidfile", kwargs, f"/tmp/iperf3_server.pid")
251
252         # Send output to a log file.
253         cmd_options.add_with_value_from_dict(
254             u"logfile", u"logfile", kwargs, f"/tmp/iperf3.log")
255
256         # The server port for the server to listen on and the client to
257         # connect to. This should be the same in both client and server.
258         # Default is 5201.
259         cmd_options.add_with_value_from_dict(
260             u"port", u"port", kwargs, 5201)
261
262         # Set the CPU affinity, if possible (Linux and FreeBSD only).
263         cmd_options.add_with_value_from_dict(
264             u"affinity", u"affinity", kwargs)
265
266         # Output in JSON format.
267         cmd_options.add_if_from_dict(
268             u"json", u"json", kwargs, True)
269
270         # Give more detailed output.
271         cmd_options.add_if_from_dict(
272             u"verbose", u"verbose", kwargs, True)
273
274         return cmd.extend(cmd_options)
275
276
277 class IPerf3Client:
278     """iPerf3 client utilities."""
279
280     @staticmethod
281     def iperf3_cmdline(**kwargs):
282         """Get iperf_client driver command line.
283
284         :param kwargs: List of iperf_client driver parameters.
285         :type kwargs: dict
286         :returns: iperf_client driver command line.
287         :rtype: OptionString
288         """
289         cmd = OptionString()
290         cmd.add(u"python3")
291         dirname = f"{Constants.REMOTE_FW_DIR}/resources/tools/iperf"
292         cmd.add(f"'{dirname}/iperf_client.py'")
293
294         cmd_options = OptionString(prefix=u"--")
295         # Namespace to execute iPerf3 client on.
296         cmd_options.add_with_value_from_dict(
297             u"namespace", u"namespace", kwargs)
298
299         # Client connecting to an iPerf3 server running on host.
300         cmd_options.add_with_value_from_dict(
301             u"host", u"host", kwargs)
302
303         # Client bind IP address.
304         cmd_options.add_with_value_from_dict(
305             u"bind", u"bind", kwargs)
306
307         # Use UDP rather than TCP.
308         cmd_options.add_if_from_dict(
309             u"udp", u"udp", kwargs, False)
310
311         # Set the CPU affinity, if possible.
312         cmd_options.add_with_value_from_dict(
313             u"affinity", u"affinity", kwargs)
314
315         # Time expressed in seconds for how long to send traffic.
316         cmd_options.add_with_value_from_dict(
317             u"duration", u"duration", kwargs)
318
319         # Send bi- (2) or uni- (1) directional traffic.
320         cmd_options.add_with_value_from_dict(
321             u"traffic_directions", u"traffic_directions", kwargs, 1)
322
323         # Traffic warm-up time in seconds, (0=disable).
324         cmd_options.add_with_value_from_dict(
325             u"warmup_time", u"warmup_time", kwargs, 5.0)
326
327         # L2 frame size to send (without padding and IPG).
328         cmd_options.add_with_value_from_dict(
329             u"frame_size", u"frame_size", kwargs)
330
331         # Traffic rate expressed with units.
332         cmd_options.add_with_value_from_dict(
333             u"rate", u"rate", kwargs)
334
335         # If enabled then don't wait for all incoming traffic.
336         cmd_options.add_if_from_dict(
337             u"async_start", u"async_call", kwargs, False)
338
339         # Number of iPerf3 client parallel instances.
340         cmd_options.add_with_value_from_dict(
341             u"instances", u"instances", kwargs, 1)
342
343         # Number of iPerf3 client parallel flows.
344         cmd_options.add_with_value_from_dict(
345             u"parallel", u"parallel", kwargs, 8)
346
347         return cmd.extend(cmd_options)