fix(ip6scale): Unify rnd profiles
[csit.git] / resources / libraries / python / TrafficGenerator.py
1 # Copyright (c) 2020 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 """Performance testing traffic generator library."""
15
16 import time
17
18 from robot.api import logger
19 from robot.libraries.BuiltIn import BuiltIn
20
21 from .Constants import Constants
22 from .CpuUtils import CpuUtils
23 from .DropRateSearch import DropRateSearch
24 from .MLRsearch.AbstractMeasurer import AbstractMeasurer
25 from .MLRsearch.MultipleLossRatioSearch import MultipleLossRatioSearch
26 from .MLRsearch.ReceiveRateMeasurement import ReceiveRateMeasurement
27 from .PLRsearch.PLRsearch import PLRsearch
28 from .OptionString import OptionString
29 from .ssh import exec_cmd_no_error, exec_cmd
30 from .topology import NodeType
31 from .topology import NodeSubTypeTG
32 from .topology import Topology
33
34 __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"]
35
36
37 def check_subtype(node):
38     """Return supported subtype of given node, or raise an exception.
39
40     Currently only one subtype is supported,
41     but we want our code to be ready for other ones.
42
43     :param node: Topology node to check. Can be None.
44     :type node: dict or NoneType
45     :returns: Subtype detected.
46     :rtype: NodeSubTypeTG
47     :raises RuntimeError: If node is not supported, message explains how.
48     """
49     if node.get(u"type") is None:
50         msg = u"Node type is not defined"
51     elif node[u"type"] != NodeType.TG:
52         msg = f"Node type is {node[u'type']!r}, not a TG"
53     elif node.get(u"subtype") is None:
54         msg = u"TG subtype is not defined"
55     elif node[u"subtype"] != NodeSubTypeTG.TREX:
56         msg = f"TG subtype {node[u'subtype']!r} is not supported"
57     else:
58         return NodeSubTypeTG.TREX
59     raise RuntimeError(msg)
60
61
62 class TGDropRateSearchImpl(DropRateSearch):
63     """Drop Rate Search implementation."""
64
65     # def __init__(self):
66     #     super(TGDropRateSearchImpl, self).__init__()
67
68     def measure_loss(
69             self, rate, frame_size, loss_acceptance, loss_acceptance_type,
70             traffic_profile, skip_warmup=False):
71         """Runs the traffic and evaluate the measured results.
72
73         :param rate: Offered traffic load.
74         :param frame_size: Size of frame.
75         :param loss_acceptance: Permitted drop ratio or frames count.
76         :param loss_acceptance_type: Type of permitted loss.
77         :param traffic_profile: Module name as a traffic profile identifier.
78             See GPL/traffic_profiles/trex for implemented modules.
79         :param skip_warmup: Start TRex without warmup traffic if true.
80         :type rate: float
81         :type frame_size: str
82         :type loss_acceptance: float
83         :type loss_acceptance_type: LossAcceptanceType
84         :type traffic_profile: str
85         :type skip_warmup: bool
86         :returns: Drop threshold exceeded? (True/False)
87         :rtype: bool
88         :raises NotImplementedError: If TG is not supported.
89         :raises RuntimeError: If TG is not specified.
90         """
91         # we need instance of TrafficGenerator instantiated by Robot Framework
92         # to be able to use trex_stl-*()
93         tg_instance = BuiltIn().get_library_instance(
94             u"resources.libraries.python.TrafficGenerator"
95         )
96         subtype = check_subtype(tg_instance.node)
97         if subtype == NodeSubTypeTG.TREX:
98             unit_rate = str(rate) + self.get_rate_type_str()
99             if skip_warmup:
100                 tg_instance.trex_stl_start_remote_exec(
101                     self.get_duration(), unit_rate, frame_size, traffic_profile,
102                     warmup_time=0.0
103                 )
104             else:
105                 tg_instance.trex_stl_start_remote_exec(
106                     self.get_duration(), unit_rate, frame_size, traffic_profile
107                 )
108             loss = tg_instance.get_loss()
109             sent = tg_instance.get_sent()
110             if self.loss_acceptance_type_is_percentage():
111                 loss = (float(loss) / float(sent)) * 100
112             logger.trace(
113                 f"comparing: {loss} < {loss_acceptance} {loss_acceptance_type}"
114             )
115             return float(loss) <= float(loss_acceptance)
116         return False
117
118     def get_latency(self):
119         """Returns min/avg/max latency.
120
121         :returns: Latency stats.
122         :rtype: list
123         """
124         tg_instance = BuiltIn().get_library_instance(
125             u"resources.libraries.python.TrafficGenerator"
126         )
127         return tg_instance.get_latency_int()
128
129
130 class TrexMode:
131     """Defines mode of T-Rex traffic generator."""
132     # Advanced stateful mode
133     ASTF = u"ASTF"
134     # Stateless mode
135     STL = u"STL"
136
137
138 # TODO: Pylint says too-many-instance-attributes.
139 class TrafficGenerator(AbstractMeasurer):
140     """Traffic Generator."""
141
142     # TODO: Remove "trex" from lines which could work with other TGs.
143
144     # Use one instance of TrafficGenerator for all tests in test suite
145     ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
146
147     def __init__(self):
148         self._node = None
149         self._mode = None
150         # TG interface order mapping
151         self._ifaces_reordered = False
152         # Result holding fields, to be removed.
153         self._result = None
154         self._loss = None
155         self._sent = None
156         self._latency = None
157         self._received = None
158         self._approximated_rate = None
159         self._approximated_duration = None
160         self._l7_data = None
161         # Measurement input fields, needed for async stop result.
162         self._start_time = None
163         self._rate = None
164         # Other input parameters, not knowable from measure() signature.
165         self.frame_size = None
166         self.traffic_profile = None
167         self.warmup_time = None
168         self.traffic_directions = None
169         self.negative_loss = None
170         self.use_latency = None
171         # Transient data needed for async measurements.
172         self._xstats = (None, None)
173         # TODO: Rename "xstats" to something opaque, so T-Rex is not privileged?
174
175     @property
176     def node(self):
177         """Getter.
178
179         :returns: Traffic generator node.
180         :rtype: dict
181         """
182         return self._node
183
184     def get_loss(self):
185         """Return number of lost packets.
186
187         :returns: Number of lost packets.
188         :rtype: str
189         """
190         return self._loss
191
192     def get_sent(self):
193         """Return number of sent packets.
194
195         :returns: Number of sent packets.
196         :rtype: str
197         """
198         return self._sent
199
200     def get_received(self):
201         """Return number of received packets.
202
203         :returns: Number of received packets.
204         :rtype: str
205         """
206         return self._received
207
208     def get_latency_int(self):
209         """Return rounded min/avg/max latency.
210
211         :returns: Latency stats.
212         :rtype: list
213         """
214         return self._latency
215
216     def get_approximated_rate(self):
217         """Return approximated rate computed as ratio of transmitted packets
218         over duration of trial.
219
220         :returns: Approximated rate.
221         :rtype: str
222         """
223         return self._approximated_rate
224
225     def get_l7_data(self):
226         """Return L7 data.
227
228         :returns: Number of received packets.
229         :rtype: dict
230         """
231         return self._l7_data
232
233     def check_mode(self, expected_mode):
234         """Check TG mode.
235
236         :param expected_mode: Expected traffic generator mode.
237         :type expected_mode: object
238         :raises RuntimeError: In case of unexpected TG mode.
239         """
240         if self._mode == expected_mode:
241             return
242         raise RuntimeError(
243             f"{self._node[u'subtype']} not running in {expected_mode} mode!"
244         )
245
246     # TODO: pylint says disable=too-many-locals.
247     def initialize_traffic_generator(
248             self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
249             tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
250             tg_if2_dst_mac=None):
251         """TG initialization.
252
253         TODO: Document why do we need (and how do we use) _ifaces_reordered.
254
255         :param tg_node: Traffic generator node.
256         :param tg_if1: TG - name of first interface.
257         :param tg_if2: TG - name of second interface.
258         :param tg_if1_adj_node: TG if1 adjecent node.
259         :param tg_if1_adj_if: TG if1 adjecent interface.
260         :param tg_if2_adj_node: TG if2 adjecent node.
261         :param tg_if2_adj_if: TG if2 adjecent interface.
262         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
263         :param tg_if1_dst_mac: Interface 1 destination MAC address.
264         :param tg_if2_dst_mac: Interface 2 destination MAC address.
265         :type tg_node: dict
266         :type tg_if1: str
267         :type tg_if2: str
268         :type tg_if1_adj_node: dict
269         :type tg_if1_adj_if: str
270         :type tg_if2_adj_node: dict
271         :type tg_if2_adj_if: str
272         :type osi_layer: str
273         :type tg_if1_dst_mac: str
274         :type tg_if2_dst_mac: str
275         :returns: nothing
276         :raises RuntimeError: In case of issue during initialization.
277         """
278         subtype = check_subtype(tg_node)
279         if subtype == NodeSubTypeTG.TREX:
280             self._node = tg_node
281             self._mode = TrexMode.ASTF if osi_layer == u"L7" else TrexMode.STL
282             if1 = dict()
283             if2 = dict()
284             if1[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if1)
285             if2[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if2)
286             if1[u"addr"] = Topology().get_interface_mac(self._node, tg_if1)
287             if2[u"addr"] = Topology().get_interface_mac(self._node, tg_if2)
288
289             if osi_layer == u"L2":
290                 if1[u"adj_addr"] = if2[u"addr"]
291                 if2[u"adj_addr"] = if1[u"addr"]
292             elif osi_layer in (u"L3", u"L7"):
293                 if1[u"adj_addr"] = Topology().get_interface_mac(
294                     tg_if1_adj_node, tg_if1_adj_if
295                 )
296                 if2[u"adj_addr"] = Topology().get_interface_mac(
297                     tg_if2_adj_node, tg_if2_adj_if
298                 )
299             else:
300                 raise ValueError(u"Unknown OSI layer!")
301
302             # in case of switched environment we can override MAC addresses
303             if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
304                 if1[u"adj_addr"] = tg_if1_dst_mac
305                 if2[u"adj_addr"] = tg_if2_dst_mac
306
307             if min(if1[u"pci"], if2[u"pci"]) != if1[u"pci"]:
308                 if1, if2 = if2, if1
309                 self._ifaces_reordered = True
310
311             master_thread_id, latency_thread_id, socket, threads = \
312                 CpuUtils.get_affinity_trex(
313                     self._node, tg_if1, tg_if2,
314                     tg_dtc=Constants.TREX_CORE_COUNT)
315
316             if osi_layer in (u"L2", u"L3", u"L7"):
317                 exec_cmd_no_error(
318                     self._node,
319                     f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
320                     f"- version: 2\n"
321                     f"  c: {len(threads)}\n"
322                     f"  limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
323                     f"  interfaces: [\"{if1[u'pci']}\",\"{if2[u'pci']}\"]\n"
324                     f"  port_info:\n"
325                     f"      - dest_mac: \'{if1[u'adj_addr']}\'\n"
326                     f"        src_mac: \'{if1[u'addr']}\'\n"
327                     f"      - dest_mac: \'{if2[u'adj_addr']}\'\n"
328                     f"        src_mac: \'{if2[u'addr']}\'\n"
329                     f"  platform :\n"
330                     f"      master_thread_id: {master_thread_id}\n"
331                     f"      latency_thread_id: {latency_thread_id}\n"
332                     f"      dual_if:\n"
333                     f"          - socket: {socket}\n"
334                     f"            threads: {threads}\n"
335                     f"EOF'",
336                     sudo=True, message=u"T-Rex config generation!"
337                 )
338             else:
339                 raise ValueError(u"Unknown OSI layer!")
340
341             TrafficGenerator.startup_trex(
342                 self._node, osi_layer, subtype=subtype
343             )
344
345     @staticmethod
346     def startup_trex(tg_node, osi_layer, subtype=None):
347         """Startup sequence for the TRex traffic generator.
348
349         :param tg_node: Traffic generator node.
350         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
351         :param subtype: Traffic generator sub-type.
352         :type tg_node: dict
353         :type osi_layer: str
354         :type subtype: NodeSubTypeTG
355         :raises RuntimeError: If T-Rex startup failed.
356         :raises ValueError: If OSI layer is not supported.
357         """
358         if not subtype:
359             subtype = check_subtype(tg_node)
360         if subtype == NodeSubTypeTG.TREX:
361             for _ in range(0, 3):
362                 # Kill TRex only if it is already running.
363                 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
364                 exec_cmd_no_error(
365                     tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
366                 )
367
368                 # Configure TRex.
369                 ports = ''
370                 for port in tg_node[u"interfaces"].values():
371                     if u'Mellanox' not in port.get(u'model'):
372                         ports += f" {port.get(u'pci_address')}"
373
374                 cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \
375                     f"./dpdk_nic_bind.py -u {ports} || true\""
376                 exec_cmd_no_error(
377                     tg_node, cmd, sudo=True,
378                     message=u"Unbind PCI ports from driver failed!"
379                 )
380
381                 # Start TRex.
382                 cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
383                 trex_cmd = OptionString([u"nohup", u"./t-rex-64"])
384                 trex_cmd.add(u"-i")
385                 trex_cmd.add(u"--prefix $(hostname)")
386                 trex_cmd.add(u"--hdrh")
387                 trex_cmd.add(u"--no-scapy-server")
388                 trex_cmd.add_if(u"--astf", osi_layer == u"L7")
389                 # OptionString does not create double space if extra is empty.
390                 trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}")
391                 inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &"
392                 cmd = f"sh -c \"{inner_command}\" > /dev/null"
393                 try:
394                     exec_cmd_no_error(tg_node, cmd, sudo=True)
395                 except RuntimeError:
396                     cmd = u"sh -c \"cat /tmp/trex.log\""
397                     exec_cmd_no_error(
398                         tg_node, cmd, sudo=True,
399                         message=u"Get TRex logs failed!"
400                     )
401                     raise RuntimeError(u"Start TRex failed!")
402
403                 # Test T-Rex API responsiveness.
404                 cmd = u"python3"
405                 cmd += f" {Constants.REMOTE_FW_DIR}/GPL/tools/trex/"
406                 if osi_layer in (u"L2", u"L3"):
407                     cmd += f"trex_stl_assert.py"
408                 elif osi_layer == u"L7":
409                     cmd += f"trex_astf_assert.py"
410                 else:
411                     raise ValueError(u"Unknown OSI layer!")
412                 try:
413                     exec_cmd_no_error(
414                         tg_node, cmd, sudo=True,
415                         message=u"T-Rex API is not responding!", retries=20
416                     )
417                 except RuntimeError:
418                     continue
419                 return
420             # After max retries TRex is still not responding to API critical
421             # error occurred.
422             exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True)
423             raise RuntimeError(u"Start T-Rex failed after multiple retries!")
424
425     @staticmethod
426     def is_trex_running(node):
427         """Check if T-Rex is running using pidof.
428
429         :param node: Traffic generator node.
430         :type node: dict
431         :returns: True if T-Rex is running otherwise False.
432         :rtype: bool
433         """
434         ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True)
435         return bool(int(ret) == 0)
436
437     @staticmethod
438     def teardown_traffic_generator(node):
439         """TG teardown.
440
441         :param node: Traffic generator node.
442         :type node: dict
443         :returns: nothing
444         :raises RuntimeError: If node type is not a TG,
445             or if T-Rex teardown fails.
446         """
447         subtype = check_subtype(node)
448         if subtype == NodeSubTypeTG.TREX:
449             exec_cmd_no_error(
450                 node,
451                 u"sh -c "
452                 u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"",
453                 sudo=False,
454                 message=u"T-Rex kill failed!"
455             )
456
457     def _parse_traffic_results(self, stdout):
458         """Parse stdout of scripts into fields of self.
459
460         Block of code to reuse, by sync start, or stop after async.
461
462         :param stdout: Text containing the standard output.
463         :type stdout: str
464         """
465         subtype = check_subtype(self._node)
466         if subtype == NodeSubTypeTG.TREX:
467             # Last line from console output
468             line = stdout.splitlines()[-1]
469             results = line.split(u",")
470             if results[-1] in (u" ", u""):
471                 results.pop(-1)
472             self._result = dict()
473             for result in results:
474                 key, value = result.split(u"=", maxsplit=1)
475                 self._result[key.strip()] = value
476             logger.info(f"TrafficGen results:\n{self._result}")
477             self._received = self._result.get(u"total_received")
478             self._sent = self._result.get(u"total_sent")
479             self._loss = self._result.get(u"frame_loss")
480             self._approximated_duration = \
481                 self._result.get(u"approximated_duration")
482             self._approximated_rate = self._result.get(u"approximated_rate")
483             self._latency = list()
484             self._latency.append(self._result.get(u"latency_stream_0(usec)"))
485             self._latency.append(self._result.get(u"latency_stream_1(usec)"))
486             if self._mode == TrexMode.ASTF:
487                 self._l7_data = dict()
488                 self._l7_data[u"client"] = dict()
489                 self._l7_data[u"client"][u"active_flows"] = \
490                     self._result.get(u"client_active_flows")
491                 self._l7_data[u"client"][u"established_flows"] = \
492                     self._result.get(u"client_established_flows")
493                 self._l7_data[u"client"][u"err_rx_throttled"] = \
494                     self._result.get(u"client_err_rx_throttled")
495                 self._l7_data[u"client"][u"err_c_nf_throttled"] = \
496                     self._result.get(u"client_err_nf_throttled")
497                 self._l7_data[u"client"][u"err_flow_overflow"] = \
498                     self._result.get(u"client_err_flow_overflow")
499                 self._l7_data[u"server"] = dict()
500                 self._l7_data[u"server"][u"active_flows"] = \
501                     self._result.get(u"server_active_flows")
502                 self._l7_data[u"server"][u"established_flows"] = \
503                     self._result.get(u"server_established_flows")
504                 self._l7_data[u"server"][u"err_rx_throttled"] = \
505                     self._result.get(u"client_err_rx_throttled")
506                 if u"udp" in self.traffic_profile:
507                     self._l7_data[u"client"][u"udp"] = dict()
508                     self._l7_data[u"client"][u"udp"][u"established_flows"] = \
509                         self._result.get(u"client_udp_connects")
510                     self._l7_data[u"client"][u"udp"][u"closed_flows"] = \
511                         self._result.get(u"client_udp_closed")
512                     self._l7_data[u"client"][u"udp"][u"tx_bytes"] = \
513                         self._result.get(u"client_udp_tx_bytes")
514                     self._l7_data[u"client"][u"udp"][u"rx_bytes"] = \
515                         self._result.get(u"client_udp_rx_bytes")
516                     self._l7_data[u"client"][u"udp"][u"tx_packets"] = \
517                         self._result.get(u"client_udp_tx_packets")
518                     self._l7_data[u"client"][u"udp"][u"rx_packets"] = \
519                         self._result.get(u"client_udp_rx_packets")
520                     self._l7_data[u"client"][u"udp"][u"keep_drops"] = \
521                         self._result.get(u"client_udp_keep_drops")
522                     self._l7_data[u"server"][u"udp"] = dict()
523                     self._l7_data[u"server"][u"udp"][u"accepted_flows"] = \
524                         self._result.get(u"server_udp_accepts")
525                     self._l7_data[u"server"][u"udp"][u"closed_flows"] = \
526                         self._result.get(u"server_udp_closed")
527                     self._l7_data[u"server"][u"udp"][u"tx_bytes"] = \
528                         self._result.get(u"server_udp_tx_bytes")
529                     self._l7_data[u"server"][u"udp"][u"rx_bytes"] = \
530                         self._result.get(u"server_udp_rx_bytes")
531                     self._l7_data[u"server"][u"udp"][u"tx_packets"] = \
532                         self._result.get(u"server_udp_tx_packets")
533                     self._l7_data[u"server"][u"udp"][u"rx_packets"] = \
534                         self._result.get(u"server_udp_rx_packets")
535                 elif u"tcp" in self.traffic_profile:
536                     self._l7_data[u"client"][u"tcp"] = dict()
537                     self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = \
538                         self._result.get(u"client_tcp_connect_inits")
539                     self._l7_data[u"client"][u"tcp"][u"established_flows"] = \
540                         self._result.get(u"client_tcp_connects")
541                     self._l7_data[u"client"][u"tcp"][u"closed_flows"] = \
542                         self._result.get(u"client_tcp_closed")
543                     self._l7_data[u"client"][u"tcp"][u"tx_bytes"] = \
544                         self._result.get(u"client_tcp_tx_bytes")
545                     self._l7_data[u"client"][u"tcp"][u"rx_bytes"] = \
546                         self._result.get(u"client_tcp_rx_bytes")
547                     self._l7_data[u"server"][u"tcp"] = dict()
548                     self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = \
549                         self._result.get(u"server_tcp_accepts")
550                     self._l7_data[u"server"][u"tcp"][u"established_flows"] = \
551                         self._result.get(u"server_tcp_connects")
552                     self._l7_data[u"server"][u"tcp"][u"closed_flows"] = \
553                         self._result.get(u"server_tcp_closed")
554                     self._l7_data[u"server"][u"tcp"][u"tx_bytes"] = \
555                         self._result.get(u"server_tcp_tx_bytes")
556                     self._l7_data[u"server"][u"tcp"][u"rx_bytes"] = \
557                         self._result.get(u"server_tcp_rx_bytes")
558
559     def trex_astf_stop_remote_exec(self, node):
560         """Execute T-Rex ASTF script on remote node over ssh to stop running
561         traffic.
562
563         Internal state is updated with measurement results.
564
565         :param node: T-Rex generator node.
566         :type node: dict
567         :raises RuntimeError: If stop traffic script fails.
568         """
569         command_line = OptionString().add(u"python3")
570         dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
571         command_line.add(f"'{dirname}/trex_astf_stop.py'")
572         command_line.change_prefix(u"--")
573         for index, value in enumerate(self._xstats):
574             if value is not None:
575                 value = value.replace(u"'", u"\"")
576                 command_line.add_equals(f"xstat{index}", f"'{value}'")
577         stdout, _ = exec_cmd_no_error(
578             node, command_line,
579             message=u"T-Rex ASTF runtime error!"
580         )
581         self._parse_traffic_results(stdout)
582
583     def trex_stl_stop_remote_exec(self, node):
584         """Execute T-Rex STL script on remote node over ssh to stop running
585         traffic.
586
587         Internal state is updated with measurement results.
588
589         :param node: T-Rex generator node.
590         :type node: dict
591         :raises RuntimeError: If stop traffic script fails.
592         """
593         command_line = OptionString().add(u"python3")
594         dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
595         command_line.add(f"'{dirname}/trex_stl_stop.py'")
596         command_line.change_prefix(u"--")
597         for index, value in enumerate(self._xstats):
598             if value is not None:
599                 value = value.replace(u"'", u"\"")
600                 command_line.add_equals(f"xstat{index}", f"'{value}'")
601         stdout, _ = exec_cmd_no_error(
602             node, command_line,
603             message=u"T-Rex STL runtime error!"
604         )
605         self._parse_traffic_results(stdout)
606
607     def stop_traffic_on_tg(self):
608         """Stop all traffic on TG.
609
610         :returns: Structure containing the result of the measurement.
611         :rtype: ReceiveRateMeasurement
612         :raises ValueError: If TG traffic profile is not supported.
613         """
614         subtype = check_subtype(self._node)
615         if subtype == NodeSubTypeTG.TREX:
616             if u"trex-astf" in self.traffic_profile:
617                 self.trex_astf_stop_remote_exec(self._node)
618             elif u"trex-stl" in self.traffic_profile:
619                 self.trex_stl_stop_remote_exec(self._node)
620             else:
621                 raise ValueError(u"Unsupported T-Rex traffic profile!")
622
623         return self.get_measurement_result()
624
625     def trex_astf_start_remote_exec(
626             self, duration, mult, frame_size, traffic_profile, async_call=False,
627             latency=True, warmup_time=5.0, traffic_directions=2, tx_port=0,
628             rx_port=1):
629         """Execute T-Rex ASTF script on remote node over ssh to start running
630         traffic.
631
632         In sync mode, measurement results are stored internally.
633         In async mode, initial data including xstats are stored internally.
634
635         :param duration: Time expresed in seconds for how long to send traffic.
636         :param mult: Traffic rate expressed with units (pps, %)
637         :param frame_size: L2 frame size to send (without padding and IPG).
638         :param traffic_profile: Module name as a traffic profile identifier.
639             See GPL/traffic_profiles/trex for implemented modules.
640         :param async_call: If enabled then don't wait for all incoming traffic.
641         :param latency: With latency measurement.
642         :param warmup_time: Warmup time period.
643         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
644             Default: 2
645         :param tx_port: Traffic generator transmit port for first flow.
646             Default: 0
647         :param rx_port: Traffic generator receive port for first flow.
648             Default: 1
649         :type duration: float
650         :type mult: int
651         :type frame_size: str
652         :type traffic_profile: str
653         :type async_call: bool
654         :type latency: bool
655         :type warmup_time: float
656         :type traffic_directions: int
657         :type tx_port: int
658         :type rx_port: int
659         :raises RuntimeError: In case of T-Rex driver issue.
660         """
661         self.check_mode(TrexMode.ASTF)
662         p_0, p_1 = (rx_port, tx_port) if self._ifaces_reordered \
663             else (tx_port, rx_port)
664         if not isinstance(duration, (float, int)):
665             duration = float(duration)
666         if not isinstance(warmup_time, (float, int)):
667             warmup_time = float(warmup_time)
668
669         command_line = OptionString().add(u"python3")
670         dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
671         command_line.add(f"'{dirname}/trex_astf_profile.py'")
672         command_line.change_prefix(u"--")
673         dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
674         command_line.add_with_value(
675             u"profile", f"'{dirname}/{traffic_profile}.py'"
676         )
677         command_line.add_with_value(u"duration", f"{duration!r}")
678         command_line.add_with_value(u"frame_size", frame_size)
679         command_line.add_with_value(u"mult", int(mult))
680         command_line.add_with_value(u"warmup_time", f"{warmup_time!r}")
681         command_line.add_with_value(u"port_0", p_0)
682         command_line.add_with_value(u"port_1", p_1)
683         command_line.add_with_value(u"traffic_directions", traffic_directions)
684         command_line.add_if(u"async_start", async_call)
685         command_line.add_if(u"latency", latency)
686         command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
687
688         stdout, _ = exec_cmd_no_error(
689             self._node, command_line,
690             timeout=int(duration) + 600 if u"tcp" in self.traffic_profile
691             else 60,
692             message=u"T-Rex ASTF runtime error!"
693         )
694
695         self.traffic_directions = traffic_directions
696         if async_call:
697             # no result
698             self._start_time = time.time()
699             self._rate = float(mult)
700             self._received = None
701             self._sent = None
702             self._loss = None
703             self._latency = None
704             xstats = [None, None]
705             self._l7_data = dict()
706             self._l7_data[u"client"] = dict()
707             self._l7_data[u"client"][u"active_flows"] = None
708             self._l7_data[u"client"][u"established_flows"] = None
709             self._l7_data[u"server"] = dict()
710             self._l7_data[u"server"][u"active_flows"] = None
711             self._l7_data[u"server"][u"established_flows"] = None
712             if u"udp" in self.traffic_profile:
713                 self._l7_data[u"client"][u"udp"] = dict()
714                 self._l7_data[u"client"][u"udp"][u"established_flows"] = None
715                 self._l7_data[u"client"][u"udp"][u"closed_flows"] = None
716                 self._l7_data[u"server"][u"udp"] = dict()
717                 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = None
718                 self._l7_data[u"server"][u"udp"][u"closed_flows"] = None
719             elif u"tcp" in self.traffic_profile:
720                 self._l7_data[u"client"][u"tcp"] = dict()
721                 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = None
722                 self._l7_data[u"client"][u"tcp"][u"established_flows"] = None
723                 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = None
724                 self._l7_data[u"server"][u"tcp"] = dict()
725                 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = None
726                 self._l7_data[u"server"][u"tcp"][u"established_flows"] = None
727                 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = None
728             else:
729                 logger.warn(u"Unsupported T-Rex ASTF traffic profile!")
730             index = 0
731             for line in stdout.splitlines():
732                 if f"Xstats snapshot {index}: " in line:
733                     xstats[index] = line[19:]
734                     index += 1
735                 if index == 2:
736                     break
737             self._xstats = tuple(xstats)
738         else:
739             self._parse_traffic_results(stdout)
740             self._start_time = None
741             self._rate = None
742
743     def trex_stl_start_remote_exec(
744             self, duration, rate, frame_size, traffic_profile, async_call=False,
745             latency=False, warmup_time=5.0, traffic_directions=2, tx_port=0,
746             rx_port=1):
747         """Execute T-Rex STL script on remote node over ssh to start running
748         traffic.
749
750         In sync mode, measurement results are stored internally.
751         In async mode, initial data including xstats are stored internally.
752
753         :param duration: Time expressed in seconds for how long to send traffic.
754         :param rate: Traffic rate expressed with units (pps, %)
755         :param frame_size: L2 frame size to send (without padding and IPG).
756         :param traffic_profile: Module name as a traffic profile identifier.
757             See GPL/traffic_profiles/trex for implemented modules.
758         :param async_call: If enabled then don't wait for all incoming traffic.
759         :param latency: With latency measurement.
760         :param warmup_time: Warmup time period.
761         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
762             Default: 2
763         :param tx_port: Traffic generator transmit port for first flow.
764             Default: 0
765         :param rx_port: Traffic generator receive port for first flow.
766             Default: 1
767         :type duration: float
768         :type rate: str
769         :type frame_size: str
770         :type traffic_profile: str
771         :type async_call: bool
772         :type latency: bool
773         :type warmup_time: float
774         :type traffic_directions: int
775         :type tx_port: int
776         :type rx_port: int
777         :raises RuntimeError: In case of T-Rex driver issue.
778         """
779         self.check_mode(TrexMode.STL)
780         p_0, p_1 = (rx_port, tx_port) if self._ifaces_reordered \
781             else (tx_port, rx_port)
782         if not isinstance(duration, (float, int)):
783             duration = float(duration)
784         if not isinstance(warmup_time, (float, int)):
785             warmup_time = float(warmup_time)
786
787         command_line = OptionString().add(u"python3")
788         dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
789         command_line.add(f"'{dirname}/trex_stl_profile.py'")
790         command_line.change_prefix(u"--")
791         dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
792         command_line.add_with_value(
793             u"profile", f"'{dirname}/{traffic_profile}.py'"
794         )
795         command_line.add_with_value(u"duration", f"{duration!r}")
796         command_line.add_with_value(u"frame_size", frame_size)
797         command_line.add_with_value(u"rate", f"{rate!r}")
798         command_line.add_with_value(u"warmup_time", f"{warmup_time!r}")
799         command_line.add_with_value(u"port_0", p_0)
800         command_line.add_with_value(u"port_1", p_1)
801         command_line.add_with_value(u"traffic_directions", traffic_directions)
802         command_line.add_if(u"async_start", async_call)
803         command_line.add_if(u"latency", latency)
804         command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
805
806         stdout, _ = exec_cmd_no_error(
807             self._node, command_line, timeout=int(duration) + 60,
808             message=u"T-Rex STL runtime error"
809         )
810
811         self.traffic_directions = traffic_directions
812         if async_call:
813             # no result
814             self._start_time = time.time()
815             self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
816             self._received = None
817             self._sent = None
818             self._loss = None
819             self._latency = None
820
821             xstats = [None, None]
822             index = 0
823             for line in stdout.splitlines():
824                 if f"Xstats snapshot {index}: " in line:
825                     xstats[index] = line[19:]
826                     index += 1
827                 if index == 2:
828                     break
829             self._xstats = tuple(xstats)
830         else:
831             self._parse_traffic_results(stdout)
832             self._start_time = None
833             self._rate = None
834
835     def send_traffic_on_tg(
836             self, duration, rate, frame_size, traffic_profile, warmup_time=5,
837             async_call=False, latency=False, traffic_directions=2, tx_port=0,
838             rx_port=1):
839         """Send traffic from all configured interfaces on TG.
840
841         In async mode, xstats is stored internally,
842         to enable getting correct result when stopping the traffic.
843         In both modes, stdout is returned,
844         but _parse_traffic_results only works in sync output.
845
846         Note that bidirectional traffic also contains flows
847         transmitted from rx_port and received in tx_port.
848         But some tests use asymmetric traffic, so those arguments are relevant.
849
850         Also note that traffic generator uses DPDK driver which might
851         reorder port numbers based on wiring and PCI numbering.
852         This method handles that, so argument values are invariant,
853         but you can see swapped valued in debug logs.
854
855         :param duration: Duration of test traffic generation in seconds.
856         :param rate: Traffic rate.
857             - T-Rex stateless mode => Offered load per interface in pps,
858             - T-Rex advanced stateful mode => multiplier of profile CPS.
859         :param frame_size: Frame size (L2) in Bytes.
860         :param traffic_profile: Module name as a traffic profile identifier.
861             See GPL/traffic_profiles/trex for implemented modules.
862         :param warmup_time: Warmup phase in seconds.
863         :param async_call: Async mode.
864         :param latency: With latency measurement.
865         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
866             Default: 2
867         :param tx_port: Traffic generator transmit port for first flow.
868             Default: 0
869         :param rx_port: Traffic generator receive port for first flow.
870             Default: 1
871         :type duration: float
872         :type rate: float
873         :type frame_size: str
874         :type traffic_profile: str
875         :type warmup_time: float
876         :type async_call: bool
877         :type latency: bool
878         :type traffic_directions: int
879         :type tx_port: int
880         :type rx_port: int
881         :returns: TG results.
882         :rtype: str
883         :raises ValueError: If TG traffic profile is not supported.
884         """
885         subtype = check_subtype(self._node)
886         if subtype == NodeSubTypeTG.TREX:
887             if self.traffic_profile != str(traffic_profile):
888                 self.traffic_profile = str(traffic_profile)
889             if u"trex-astf" in self.traffic_profile:
890                 self.trex_astf_start_remote_exec(
891                     duration, int(rate), frame_size, self.traffic_profile,
892                     async_call, latency, warmup_time, traffic_directions,
893                     tx_port, rx_port
894                 )
895             elif u"trex-stl" in self.traffic_profile:
896                 unit_rate_str = str(rate) + u"pps"
897                 self.trex_stl_start_remote_exec(
898                     duration, unit_rate_str, frame_size, self.traffic_profile,
899                     async_call, latency, warmup_time, traffic_directions,
900                     tx_port, rx_port
901                 )
902             else:
903                 raise ValueError(u"Unsupported T-Rex traffic profile!")
904
905         return self._result
906
907     def no_traffic_loss_occurred(self):
908         """Fail if loss occurred in traffic run.
909
910         :returns: nothing
911         :raises Exception: If loss occured.
912         """
913         if self._loss is None:
914             raise RuntimeError(u"The traffic generation has not been issued")
915         if self._loss != u"0":
916             raise RuntimeError(f"Traffic loss occurred: {self._loss}")
917
918     def fail_if_no_traffic_forwarded(self):
919         """Fail if no traffic forwarded.
920
921         :returns: nothing
922         :raises Exception: If no traffic forwarded.
923         """
924         if self._received is None:
925             raise RuntimeError(u"The traffic generation has not been issued")
926         if self._received == u"0":
927             raise RuntimeError(u"No traffic forwarded")
928
929     def partial_traffic_loss_accepted(
930             self, loss_acceptance, loss_acceptance_type):
931         """Fail if loss is higher then accepted in traffic run.
932
933         :param loss_acceptance: Permitted drop ratio or frames count.
934         :param loss_acceptance_type: Type of permitted loss.
935         :type loss_acceptance: float
936         :type loss_acceptance_type: LossAcceptanceType
937         :returns: nothing
938         :raises Exception: If loss is above acceptance criteria.
939         """
940         if self._loss is None:
941             raise Exception(u"The traffic generation has not been issued")
942
943         if loss_acceptance_type == u"percentage":
944             loss = (float(self._loss) / float(self._sent)) * 100
945         elif loss_acceptance_type == u"frames":
946             loss = float(self._loss)
947         else:
948             raise Exception(u"Loss acceptance type not supported")
949
950         if loss > float(loss_acceptance):
951             raise Exception(
952                 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
953             )
954
955     def set_rate_provider_defaults(
956             self, frame_size, traffic_profile, warmup_time=0.0,
957             traffic_directions=2, negative_loss=True, latency=False):
958         """Store values accessed by measure().
959
960         :param frame_size: Frame size identifier or value [B].
961         :param traffic_profile: Module name as a traffic profile identifier.
962             See GPL/traffic_profiles/trex for implemented modules.
963         :param warmup_time: Traffic duration before measurement starts [s].
964         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
965             Default: 2
966         :param negative_loss: If false, negative loss is reported as zero loss.
967         :param latency: Whether to measure latency during the trial.
968             Default: False.
969         :type frame_size: str or int
970         :type traffic_profile: str
971         :type warmup_time: float
972         :type traffic_directions: int
973         :type negative_loss: bool
974         :type latency: bool
975         """
976         self.frame_size = frame_size
977         self.traffic_profile = str(traffic_profile)
978         self.warmup_time = float(warmup_time)
979         self.traffic_directions = traffic_directions
980         self.negative_loss = negative_loss
981         self.use_latency = latency
982
983     def get_measurement_result(self, duration=None, transmit_rate=None):
984         """Return the result of last measurement as ReceiveRateMeasurement.
985
986         Separate function, as measurements can end either by time
987         or by explicit call, this is the common block at the end.
988
989         TODO: Fail on running or already reported measurement.
990
991         :param duration: Measurement duration [s] if known beforehand.
992             For explicitly stopped measurement it is estimated.
993         :param transmit_rate: Target aggregate transmit rate [pps].
994             If not given, computed assuming it was bidirectional.
995         :type duration: float or NoneType
996         :type transmit_rate: float or NoneType
997         :returns: Structure containing the result of the measurement.
998         :rtype: ReceiveRateMeasurement
999         """
1000         if duration is None:
1001             duration = time.time() - self._start_time
1002             self._start_time = None
1003         if transmit_rate is None:
1004             transmit_rate = self._rate * self.traffic_directions
1005         transmit_count = int(self.get_sent())
1006         loss_count = int(self.get_loss())
1007         if loss_count < 0 and not self.negative_loss:
1008             loss_count = 0
1009         measurement = ReceiveRateMeasurement(
1010             duration, transmit_rate, transmit_count, loss_count
1011         )
1012         measurement.latency = self.get_latency_int()
1013         return measurement
1014
1015     def measure(self, duration, transmit_rate):
1016         """Run trial measurement, parse and return aggregate results.
1017
1018         Aggregate means sum over traffic directions.
1019
1020         :param duration: Trial duration [s].
1021         :param transmit_rate: Target aggregate transmit rate [pps] / Connections
1022         per second (CPS) for UDP/TCP flows.
1023         :type duration: float
1024         :type transmit_rate: float
1025         :returns: Structure containing the result of the measurement.
1026         :rtype: ReceiveRateMeasurement
1027         :raises RuntimeError: If TG is not set or if node is not TG
1028             or if subtype is not specified.
1029         :raises NotImplementedError: If TG is not supported.
1030         """
1031         duration = float(duration)
1032         # TG needs target Tr per stream, but reports aggregate Tx and Dx.
1033         unit_rate_int = transmit_rate / float(self.traffic_directions)
1034         self.send_traffic_on_tg(
1035             duration,
1036             unit_rate_int,
1037             self.frame_size,
1038             self.traffic_profile,
1039             warmup_time=self.warmup_time,
1040             latency=self.use_latency,
1041             traffic_directions=self.traffic_directions
1042         )
1043         return self.get_measurement_result(duration, transmit_rate)
1044
1045
1046 class OptimizedSearch:
1047     """Class to be imported as Robot Library, containing search keywords.
1048
1049     Aside of setting up measurer and forwarding arguments,
1050     the main business is to translate min/max rate from unidir to aggregate.
1051     """
1052
1053     @staticmethod
1054     def perform_optimized_ndrpdr_search(
1055             frame_size, traffic_profile, minimum_transmit_rate,
1056             maximum_transmit_rate, packet_loss_ratio=0.005,
1057             final_relative_width=0.005, final_trial_duration=30.0,
1058             initial_trial_duration=1.0, number_of_intermediate_phases=2,
1059             timeout=720.0, doublings=1, traffic_directions=2, latency=False):
1060         """Setup initialized TG, perform optimized search, return intervals.
1061
1062         :param frame_size: Frame size identifier or value [B].
1063         :param traffic_profile: Module name as a traffic profile identifier.
1064             See GPL/traffic_profiles/trex for implemented modules.
1065         :param minimum_transmit_rate: Minimal uni-directional
1066             target transmit rate [pps].
1067         :param maximum_transmit_rate: Maximal uni-directional
1068             target transmit rate [pps].
1069         :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
1070         :param final_relative_width: Final lower bound transmit rate
1071             cannot be more distant that this multiple of upper bound [1].
1072         :param final_trial_duration: Trial duration for the final phase [s].
1073         :param initial_trial_duration: Trial duration for the initial phase
1074             and also for the first intermediate phase [s].
1075         :param number_of_intermediate_phases: Number of intermediate phases
1076             to perform before the final phase [1].
1077         :param timeout: The search will fail itself when not finished
1078             before this overall time [s].
1079         :param doublings: How many doublings to do in external search step.
1080             Default 1 is suitable for fairly stable tests,
1081             less stable tests might get better overal duration with 2 or more.
1082         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1083             Default: 2
1084         :param latency: Whether to measure latency during the trial.
1085             Default: False.
1086         :type frame_size: str or int
1087         :type traffic_profile: str
1088         :type minimum_transmit_rate: float
1089         :type maximum_transmit_rate: float
1090         :type packet_loss_ratio: float
1091         :type final_relative_width: float
1092         :type final_trial_duration: float
1093         :type initial_trial_duration: float
1094         :type number_of_intermediate_phases: int
1095         :type timeout: float
1096         :type doublings: int
1097         :type traffic_directions: int
1098         :type latency: bool
1099         :returns: Structure containing narrowed down NDR and PDR intervals
1100             and their measurements.
1101         :rtype: NdrPdrResult
1102         :raises RuntimeError: If total duration is larger than timeout.
1103         """
1104         minimum_transmit_rate *= traffic_directions
1105         maximum_transmit_rate *= traffic_directions
1106         # we need instance of TrafficGenerator instantiated by Robot Framework
1107         # to be able to use trex_stl-*()
1108         tg_instance = BuiltIn().get_library_instance(
1109             u"resources.libraries.python.TrafficGenerator"
1110         )
1111         tg_instance.set_rate_provider_defaults(
1112             frame_size,
1113             traffic_profile,
1114             traffic_directions=traffic_directions,
1115             latency=latency
1116         )
1117         algorithm = MultipleLossRatioSearch(
1118             measurer=tg_instance, final_trial_duration=final_trial_duration,
1119             final_relative_width=final_relative_width,
1120             number_of_intermediate_phases=number_of_intermediate_phases,
1121             initial_trial_duration=initial_trial_duration, timeout=timeout,
1122             doublings=doublings
1123         )
1124         result = algorithm.narrow_down_ndr_and_pdr(
1125             minimum_transmit_rate, maximum_transmit_rate, packet_loss_ratio
1126         )
1127         return result
1128
1129     @staticmethod
1130     def perform_soak_search(
1131             frame_size, traffic_profile, minimum_transmit_rate,
1132             maximum_transmit_rate, plr_target=1e-7, tdpt=0.1,
1133             initial_count=50, timeout=1800.0, trace_enabled=False,
1134             traffic_directions=2, latency=False):
1135         """Setup initialized TG, perform soak search, return avg and stdev.
1136
1137         :param frame_size: Frame size identifier or value [B].
1138         :param traffic_profile: Module name as a traffic profile identifier.
1139             See GPL/traffic_profiles/trex for implemented modules.
1140         :param minimum_transmit_rate: Minimal uni-directional
1141             target transmit rate [pps].
1142         :param maximum_transmit_rate: Maximal uni-directional
1143             target transmit rate [pps].
1144         :param plr_target: Fraction of packets lost to achieve [1].
1145         :param tdpt: Trial duration per trial.
1146             The algorithm linearly increases trial duration with trial number,
1147             this is the increment between succesive trials, in seconds.
1148         :param initial_count: Offset to apply before the first trial.
1149             For example initial_count=50 makes first trial to be 51*tdpt long.
1150             This is needed because initial "search" phase of integrator
1151             takes significant time even without any trial results.
1152         :param timeout: The search will stop after this overall time [s].
1153         :param trace_enabled: True if trace enabled else False.
1154         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1155             Default: 2
1156         :param latency: Whether to measure latency during the trial.
1157             Default: False.
1158         :type frame_size: str or int
1159         :type traffic_profile: str
1160         :type minimum_transmit_rate: float
1161         :type maximum_transmit_rate: float
1162         :type plr_target: float
1163         :type initial_count: int
1164         :type timeout: float
1165         :type trace_enabled: bool
1166         :type traffic_directions: int
1167         :type latency: bool
1168         :returns: Average and stdev of estimated aggregate rate giving PLR.
1169         :rtype: 2-tuple of float
1170         """
1171         minimum_transmit_rate *= traffic_directions
1172         maximum_transmit_rate *= traffic_directions
1173         tg_instance = BuiltIn().get_library_instance(
1174             u"resources.libraries.python.TrafficGenerator"
1175         )
1176         tg_instance.set_rate_provider_defaults(
1177             frame_size,
1178             traffic_profile,
1179             traffic_directions=traffic_directions,
1180             negative_loss=False,
1181             latency=latency
1182         )
1183         algorithm = PLRsearch(
1184             measurer=tg_instance, trial_duration_per_trial=tdpt,
1185             packet_loss_ratio_target=plr_target,
1186             trial_number_offset=initial_count, timeout=timeout,
1187             trace_enabled=trace_enabled
1188         )
1189         result = algorithm.search(minimum_transmit_rate, maximum_transmit_rate)
1190         return result