1 # Copyright (c) 2022 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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """iPerf3 utilities library."""
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
27 """iPerf3 traffic generator utilities."""
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
37 def get_iperf_type(node):
38 """Log and return the installed traffic generator type.
40 :param node: Node from topology file.
42 :returns: Traffic generator type string.
48 def get_iperf_version(node):
49 """Log and return the installed traffic generator version.
51 :param node: Node from topology file.
53 :returns: Traffic generator version string.
56 command = f"iperf3 --version | head -1"
57 message = u"Get iPerf version failed!"
58 stdout, _ = exec_cmd_no_error(node, command, message=message)
61 def initialize_iperf_server(
62 self, node, pf_key, interface, bind, bind_gw, bind_mask,
63 namespace=None, cpu_skip_cnt=0, cpu_cnt=1, instances=1):
64 """iPerf3 initialization.
66 :param node: Topology node running iPerf3 server.
67 :param pf_key: First TG's interface (To compute numa location).
68 :param interface: Name of TG bind interface.
69 :param bind: Bind to host, one of node's addresses.
70 :param bind_gw: Bind gateway (required for default route).
71 :param bind_mask: Bind address mask.
72 :param namespace: Name of TG namespace to execute.
73 :param cpu_skip_cnt: Amount of CPU cores to skip.
74 :param cpu_cnt: iPerf3 main thread count.
75 :param instances: Number of simultaneous iPerf3 instances.
83 :type cpu_skip_cnt: int
87 if Iperf3.is_iperf_running(node):
88 Iperf3.teardown_iperf(node)
91 IPUtil.set_linux_interface_ip(
92 node, interface=interface, ip_addr=bind, prefix=bind_mask,
94 IPUtil.set_linux_interface_up(
95 node, interface=interface, namespace=namespace)
96 Namespaces.add_default_route_to_namespace(
97 node, namespace=namespace, default_route=bind_gw)
99 # Compute affinity for iPerf server.
100 self._s_affinity = CpuUtils.get_affinity_iperf(
101 node, pf_key, cpu_skip_cnt=cpu_skip_cnt,
102 cpu_cnt=cpu_cnt * instances)
103 # Compute affinity for iPerf client.
104 self._c_affinity = CpuUtils.get_affinity_iperf(
105 node, pf_key, cpu_skip_cnt=cpu_skip_cnt + cpu_cnt * instances,
106 cpu_cnt=cpu_cnt * instances)
108 for i in range(0, instances):
109 Iperf3.start_iperf_server(
110 node, namespace=namespace, port=5201 + i,
111 affinity=self._s_affinity)
114 def start_iperf_server(
115 node, namespace=None, port=5201, affinity=None):
116 """Start iPerf3 server instance as a deamon.
118 :param node: Topology node running iPerf3 server.
119 :param namespace: Name of TG namespace to execute.
120 :param port: The server port for the server to listen on.
121 :param affinity: iPerf3 server affinity.
127 cmd = IPerf3Server.iperf3_cmdline(
128 namespace=namespace, port=port, affinity=affinity)
130 node, cmd, sudo=True, message=u"Failed to start iPerf3 server!")
133 def is_iperf_running(node):
134 """Check if iPerf3 is running using pgrep.
136 :param node: Topology node running iPerf3.
138 :returns: True if iPerf3 is running otherwise False.
141 ret, _, _ = exec_cmd(node, u"pgrep iperf3", sudo=True)
142 return bool(int(ret) == 0)
145 def teardown_iperf(node):
148 :param node: Topology node running iPerf3.
151 pidfile = u"/tmp/iperf3_server.pid"
152 logfile = u"/tmp/iperf3.log"
156 f"sh -c 'if [ -f {pidfile} ]; then "
161 sudo=True, message=u"iPerf3 kill failed!")
163 def iperf_client_start_remote_exec(
164 self, node, duration, rate, frame_size, async_call=False,
165 warmup_time=0, traffic_directions=1, namespace=None, udp=False,
166 host=None, bind=None, affinity=None):
167 """Execute iPerf3 client script on remote node over ssh to start running
170 :param node: Topology node running iPerf3.
171 :param duration: Time expressed in seconds for how long to send traffic.
172 :param rate: Traffic rate.
173 :param frame_size: L2 frame size to send (without padding and IPG).
174 :param async_call: If enabled then don't wait for all incoming traffic.
175 :param warmup_time: Warmup time period.
176 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
178 :param namespace: Namespace to execute iPerf3 client on.
179 :param udp: UDP traffic.
180 :param host: Client connecting to an iPerf server running on host.
181 :param bind: Client bind IP address.
182 :param affinity: iPerf3 client affinity.
184 :type duration: float
186 :type frame_size: str
187 :type async_call: bool
188 :type warmup_time: float
189 :type traffic_directions: int
195 :returns: List of iPerf3 PIDs.
198 if not isinstance(duration, (float, int)):
199 duration = float(duration)
200 if not isinstance(warmup_time, (float, int)):
201 warmup_time = float(warmup_time)
203 affinity = self._c_affinity
207 kwargs[u"namespace"] = namespace
208 kwargs[u"host"] = host
209 kwargs[u"bind"] = bind
212 kwargs[u"affinity"] = affinity
213 kwargs[u"duration"] = duration
214 kwargs[u"rate"] = rate
215 kwargs[u"frame_size"] = frame_size
216 kwargs[u"warmup_time"] = warmup_time
217 kwargs[u"traffic_directions"] = traffic_directions
218 kwargs[u"async_call"] = async_call
220 cmd = IPerf3Client.iperf3_cmdline(**kwargs)
222 stdout, _ = exec_cmd_no_error(
223 node, cmd, timeout=int(duration) + 30,
224 message=u"iPerf3 runtime error!")
227 return stdout.split()
228 return json.loads(stdout)
231 def iperf_client_stop_remote_exec(node, pids):
232 """Stop iPerf3 client execution.
234 :param pids: PID or List of PIDs of iPerf3 client.
235 :type pids: str or list
237 if not isinstance(pids, list):
242 node, f"kill {pid}", sudo=True, message=u"Kill iPerf3 failed!")
246 """iPerf3 server utilities."""
249 def iperf3_cmdline(**kwargs):
250 """Get iPerf3 server command line.
252 :param kwargs: List of iPerf3 server parameters.
254 :returns: iPerf3 server command line.
258 if kwargs['namespace']:
259 cmd.add(f"ip netns exec {kwargs['namespace']}")
262 cmd_options = OptionString(prefix=u"--")
263 # Run iPerf in server mode. (This will only allow one iperf connection
268 # Run the server in background as a daemon.
269 cmd_options.add_if_from_dict(
270 u"daemon", u"daemon", kwargs, True)
272 # Write a file with the process ID, most useful when running as a
274 cmd_options.add_with_value_from_dict(
275 u"pidfile", u"pidfile", kwargs, f"/tmp/iperf3_server.pid")
277 # Send output to a log file.
278 cmd_options.add_with_value_from_dict(
279 u"logfile", u"logfile", kwargs, f"/tmp/iperf3.log")
281 # The server port for the server to listen on and the client to
282 # connect to. This should be the same in both client and server.
284 cmd_options.add_with_value_from_dict(
285 u"port", u"port", kwargs, 5201)
287 # Set the CPU affinity, if possible (Linux and FreeBSD only).
288 cmd_options.add_with_value_from_dict(
289 u"affinity", u"affinity", kwargs)
291 # Output in JSON format.
292 cmd_options.add_if_from_dict(
293 u"json", u"json", kwargs, True)
295 # Give more detailed output.
296 cmd_options.add_if_from_dict(
297 u"verbose", u"verbose", kwargs, True)
299 return cmd.extend(cmd_options)
303 """iPerf3 client utilities."""
306 def iperf3_cmdline(**kwargs):
307 """Get iperf_client driver command line.
309 :param kwargs: List of iperf_client driver parameters.
311 :returns: iperf_client driver command line.
316 dirname = f"{Constants.REMOTE_FW_DIR}/resources/tools/iperf"
317 cmd.add(f"'{dirname}/iperf_client.py'")
319 cmd_options = OptionString(prefix=u"--")
320 # Namespace to execute iPerf3 client on.
321 cmd_options.add_with_value_from_dict(
322 u"namespace", u"namespace", kwargs)
324 # Client connecting to an iPerf3 server running on host.
325 cmd_options.add_with_value_from_dict(
326 u"host", u"host", kwargs)
328 # Client bind IP address.
329 cmd_options.add_with_value_from_dict(
330 u"bind", u"bind", kwargs)
332 # Use UDP rather than TCP.
333 cmd_options.add_if_from_dict(
334 u"udp", u"udp", kwargs, False)
336 # Set the CPU affinity, if possible.
337 cmd_options.add_with_value_from_dict(
338 u"affinity", u"affinity", kwargs)
340 # Time expressed in seconds for how long to send traffic.
341 cmd_options.add_with_value_from_dict(
342 u"duration", u"duration", kwargs)
344 # Send bi- (2) or uni- (1) directional traffic.
345 cmd_options.add_with_value_from_dict(
346 u"traffic_directions", u"traffic_directions", kwargs, 1)
348 # Traffic warm-up time in seconds, (0=disable).
349 cmd_options.add_with_value_from_dict(
350 u"warmup_time", u"warmup_time", kwargs, 5.0)
352 # L2 frame size to send (without padding and IPG).
353 cmd_options.add_with_value_from_dict(
354 u"frame_size", u"frame_size", kwargs)
356 # Traffic rate expressed with units.
357 cmd_options.add_with_value_from_dict(
358 u"rate", u"rate", kwargs)
360 # If enabled then don't wait for all incoming traffic.
361 cmd_options.add_if_from_dict(
362 u"async_start", u"async_call", kwargs, False)
364 # Number of iPerf3 client parallel instances.
365 cmd_options.add_with_value_from_dict(
366 u"instances", u"instances", kwargs, 1)
368 # Number of iPerf3 client parallel flows.
369 cmd_options.add_with_value_from_dict(
370 u"parallel", u"parallel", kwargs, 8)
372 return cmd.extend(cmd_options)