Perf: NAT44 endpoint-dependent mode - udp, part I
[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     # A fix is developed in https://gerrit.fd.io/r/c/csit/+/22221
248     def initialize_traffic_generator(
249             self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
250             tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
251             tg_if2_dst_mac=None):
252         """TG initialization.
253
254         TODO: Document why do we need (and how do we use) _ifaces_reordered.
255
256         :param tg_node: Traffic generator node.
257         :param tg_if1: TG - name of first interface.
258         :param tg_if2: TG - name of second interface.
259         :param tg_if1_adj_node: TG if1 adjecent node.
260         :param tg_if1_adj_if: TG if1 adjecent interface.
261         :param tg_if2_adj_node: TG if2 adjecent node.
262         :param tg_if2_adj_if: TG if2 adjecent interface.
263         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
264         :param tg_if1_dst_mac: Interface 1 destination MAC address.
265         :param tg_if2_dst_mac: Interface 2 destination MAC address.
266         :type tg_node: dict
267         :type tg_if1: str
268         :type tg_if2: str
269         :type tg_if1_adj_node: dict
270         :type tg_if1_adj_if: str
271         :type tg_if2_adj_node: dict
272         :type tg_if2_adj_if: str
273         :type osi_layer: str
274         :type tg_if1_dst_mac: str
275         :type tg_if2_dst_mac: str
276         :returns: nothing
277         :raises RuntimeError: In case of issue during initialization.
278         """
279         subtype = check_subtype(tg_node)
280         if subtype == NodeSubTypeTG.TREX:
281             self._node = tg_node
282             self._mode = TrexMode.ASTF if osi_layer == u"L7" else TrexMode.STL
283             if1 = dict()
284             if2 = dict()
285             if1[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if1)
286             if2[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if2)
287             if1[u"addr"] = Topology().get_interface_mac(self._node, tg_if1)
288             if2[u"addr"] = Topology().get_interface_mac(self._node, tg_if2)
289
290             if osi_layer == u"L2":
291                 if1[u"adj_addr"] = if2[u"addr"]
292                 if2[u"adj_addr"] = if1[u"addr"]
293             elif osi_layer in (u"L3", u"L7"):
294                 if1[u"adj_addr"] = Topology().get_interface_mac(
295                     tg_if1_adj_node, tg_if1_adj_if
296                 )
297                 if2[u"adj_addr"] = Topology().get_interface_mac(
298                     tg_if2_adj_node, tg_if2_adj_if
299                 )
300             else:
301                 raise ValueError(u"Unknown OSI layer!")
302
303             # in case of switched environment we can override MAC addresses
304             if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
305                 if1[u"adj_addr"] = tg_if1_dst_mac
306                 if2[u"adj_addr"] = tg_if2_dst_mac
307
308             if min(if1[u"pci"], if2[u"pci"]) != if1[u"pci"]:
309                 if1, if2 = if2, if1
310                 self._ifaces_reordered = True
311
312             master_thread_id, latency_thread_id, socket, threads = \
313                 CpuUtils.get_affinity_trex(
314                     self._node, tg_if1, tg_if2,
315                     tg_dtc=Constants.TREX_CORE_COUNT)
316
317             if osi_layer in (u"L2", u"L3", u"L7"):
318                 exec_cmd_no_error(
319                     self._node,
320                     f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
321                     f"- version: 2\n"
322                     f"  c: {len(threads)}\n"
323                     f"  limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
324                     f"  interfaces: [\"{if1[u'pci']}\",\"{if2[u'pci']}\"]\n"
325                     f"  port_info:\n"
326                     f"      - dest_mac: \'{if1[u'adj_addr']}\'\n"
327                     f"        src_mac: \'{if1[u'addr']}\'\n"
328                     f"      - dest_mac: \'{if2[u'adj_addr']}\'\n"
329                     f"        src_mac: \'{if2[u'addr']}\'\n"
330                     f"  platform :\n"
331                     f"      master_thread_id: {master_thread_id}\n"
332                     f"      latency_thread_id: {latency_thread_id}\n"
333                     f"      dual_if:\n"
334                     f"          - socket: {socket}\n"
335                     f"            threads: {threads}\n"
336                     f"EOF'",
337                     sudo=True, message=u"T-Rex config generation!"
338                 )
339             else:
340                 raise ValueError(u"Unknown OSI layer!")
341
342             TrafficGenerator.startup_trex(
343                 self._node, osi_layer, subtype=subtype
344             )
345
346     @staticmethod
347     def startup_trex(tg_node, osi_layer, subtype=None):
348         """Startup sequence for the TRex traffic generator.
349
350         :param tg_node: Traffic generator node.
351         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
352         :param subtype: Traffic generator sub-type.
353         :type tg_node: dict
354         :type osi_layer: str
355         :type subtype: NodeSubTypeTG
356         :raises RuntimeError: If T-Rex startup failed.
357         :raises ValueError: If OSI layer is not supported.
358         """
359         if not subtype:
360             subtype = check_subtype(tg_node)
361         if subtype == NodeSubTypeTG.TREX:
362             for _ in range(0, 3):
363                 # Kill TRex only if it is already running.
364                 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
365                 exec_cmd_no_error(
366                     tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
367                 )
368
369                 # Configure TRex.
370                 ports = ''
371                 for port in tg_node[u"interfaces"].values():
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-sl" 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             # TODO: rename all t-rex stateless profiles to use 'trex-stl'
896             elif u"trex-sl" in self.traffic_profile:
897                 unit_rate_str = str(rate) + u"pps"
898                 self.trex_stl_start_remote_exec(
899                     duration, unit_rate_str, frame_size, self.traffic_profile,
900                     async_call, latency, warmup_time, traffic_directions,
901                     tx_port, rx_port
902                 )
903             else:
904                 raise ValueError(u"Unsupported T-Rex traffic profile!")
905
906         return self._result
907
908     def no_traffic_loss_occurred(self):
909         """Fail if loss occurred in traffic run.
910
911         :returns: nothing
912         :raises Exception: If loss occured.
913         """
914         if self._loss is None:
915             raise RuntimeError(u"The traffic generation has not been issued")
916         if self._loss != u"0":
917             raise RuntimeError(f"Traffic loss occurred: {self._loss}")
918
919     def fail_if_no_traffic_forwarded(self):
920         """Fail if no traffic forwarded.
921
922         :returns: nothing
923         :raises Exception: If no traffic forwarded.
924         """
925         if self._received is None:
926             raise RuntimeError(u"The traffic generation has not been issued")
927         if self._received == u"0":
928             raise RuntimeError(u"No traffic forwarded")
929
930     def partial_traffic_loss_accepted(
931             self, loss_acceptance, loss_acceptance_type):
932         """Fail if loss is higher then accepted in traffic run.
933
934         :param loss_acceptance: Permitted drop ratio or frames count.
935         :param loss_acceptance_type: Type of permitted loss.
936         :type loss_acceptance: float
937         :type loss_acceptance_type: LossAcceptanceType
938         :returns: nothing
939         :raises Exception: If loss is above acceptance criteria.
940         """
941         if self._loss is None:
942             raise Exception(u"The traffic generation has not been issued")
943
944         if loss_acceptance_type == u"percentage":
945             loss = (float(self._loss) / float(self._sent)) * 100
946         elif loss_acceptance_type == u"frames":
947             loss = float(self._loss)
948         else:
949             raise Exception(u"Loss acceptance type not supported")
950
951         if loss > float(loss_acceptance):
952             raise Exception(
953                 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
954             )
955
956     def set_rate_provider_defaults(
957             self, frame_size, traffic_profile, warmup_time=0.0,
958             traffic_directions=2, negative_loss=True, latency=False):
959         """Store values accessed by measure().
960
961         :param frame_size: Frame size identifier or value [B].
962         :param traffic_profile: Module name as a traffic profile identifier.
963             See GPL/traffic_profiles/trex for implemented modules.
964         :param warmup_time: Traffic duration before measurement starts [s].
965         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
966             Default: 2
967         :param negative_loss: If false, negative loss is reported as zero loss.
968         :param latency: Whether to measure latency during the trial.
969             Default: False.
970         :type frame_size: str or int
971         :type traffic_profile: str
972         :type warmup_time: float
973         :type traffic_directions: int
974         :type negative_loss: bool
975         :type latency: bool
976         """
977         self.frame_size = frame_size
978         self.traffic_profile = str(traffic_profile)
979         self.warmup_time = float(warmup_time)
980         self.traffic_directions = traffic_directions
981         self.negative_loss = negative_loss
982         self.use_latency = latency
983
984     def get_measurement_result(self, duration=None, transmit_rate=None):
985         """Return the result of last measurement as ReceiveRateMeasurement.
986
987         Separate function, as measurements can end either by time
988         or by explicit call, this is the common block at the end.
989
990         TODO: Fail on running or already reported measurement.
991
992         :param duration: Measurement duration [s] if known beforehand.
993             For explicitly stopped measurement it is estimated.
994         :param transmit_rate: Target aggregate transmit rate [pps].
995             If not given, computed assuming it was bidirectional.
996         :type duration: float or NoneType
997         :type transmit_rate: float or NoneType
998         :returns: Structure containing the result of the measurement.
999         :rtype: ReceiveRateMeasurement
1000         """
1001         if duration is None:
1002             duration = time.time() - self._start_time
1003             self._start_time = None
1004         if transmit_rate is None:
1005             transmit_rate = self._rate * self.traffic_directions
1006         transmit_count = int(self.get_sent())
1007         loss_count = int(self.get_loss())
1008         if loss_count < 0 and not self.negative_loss:
1009             loss_count = 0
1010         measurement = ReceiveRateMeasurement(
1011             duration, transmit_rate, transmit_count, loss_count
1012         )
1013         measurement.latency = self.get_latency_int()
1014         return measurement
1015
1016     def measure(self, duration, transmit_rate):
1017         """Run trial measurement, parse and return aggregate results.
1018
1019         Aggregate means sum over traffic directions.
1020
1021         :param duration: Trial duration [s].
1022         :param transmit_rate: Target aggregate transmit rate [pps] / Connections
1023         per second (CPS) for UDP/TCP flows.
1024         :type duration: float
1025         :type transmit_rate: float
1026         :returns: Structure containing the result of the measurement.
1027         :rtype: ReceiveRateMeasurement
1028         :raises RuntimeError: If TG is not set or if node is not TG
1029             or if subtype is not specified.
1030         :raises NotImplementedError: If TG is not supported.
1031         """
1032         duration = float(duration)
1033         # TG needs target Tr per stream, but reports aggregate Tx and Dx.
1034         unit_rate_int = transmit_rate / float(self.traffic_directions)
1035         self.send_traffic_on_tg(
1036             duration,
1037             unit_rate_int,
1038             self.frame_size,
1039             self.traffic_profile,
1040             warmup_time=self.warmup_time,
1041             latency=self.use_latency,
1042             traffic_directions=self.traffic_directions
1043         )
1044         return self.get_measurement_result(duration, transmit_rate)
1045
1046
1047 class OptimizedSearch:
1048     """Class to be imported as Robot Library, containing search keywords.
1049
1050     Aside of setting up measurer and forwarding arguments,
1051     the main business is to translate min/max rate from unidir to aggregate.
1052     """
1053
1054     @staticmethod
1055     def perform_optimized_ndrpdr_search(
1056             frame_size, traffic_profile, minimum_transmit_rate,
1057             maximum_transmit_rate, packet_loss_ratio=0.005,
1058             final_relative_width=0.005, final_trial_duration=30.0,
1059             initial_trial_duration=1.0, number_of_intermediate_phases=2,
1060             timeout=720.0, doublings=1, traffic_directions=2, latency=False):
1061         """Setup initialized TG, perform optimized search, return intervals.
1062
1063         :param frame_size: Frame size identifier or value [B].
1064         :param traffic_profile: Module name as a traffic profile identifier.
1065             See GPL/traffic_profiles/trex for implemented modules.
1066         :param minimum_transmit_rate: Minimal uni-directional
1067             target transmit rate [pps].
1068         :param maximum_transmit_rate: Maximal uni-directional
1069             target transmit rate [pps].
1070         :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
1071         :param final_relative_width: Final lower bound transmit rate
1072             cannot be more distant that this multiple of upper bound [1].
1073         :param final_trial_duration: Trial duration for the final phase [s].
1074         :param initial_trial_duration: Trial duration for the initial phase
1075             and also for the first intermediate phase [s].
1076         :param number_of_intermediate_phases: Number of intermediate phases
1077             to perform before the final phase [1].
1078         :param timeout: The search will fail itself when not finished
1079             before this overall time [s].
1080         :param doublings: How many doublings to do in external search step.
1081             Default 1 is suitable for fairly stable tests,
1082             less stable tests might get better overal duration with 2 or more.
1083         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1084             Default: 2
1085         :param latency: Whether to measure latency during the trial.
1086             Default: False.
1087         :type frame_size: str or int
1088         :type traffic_profile: str
1089         :type minimum_transmit_rate: float
1090         :type maximum_transmit_rate: float
1091         :type packet_loss_ratio: float
1092         :type final_relative_width: float
1093         :type final_trial_duration: float
1094         :type initial_trial_duration: float
1095         :type number_of_intermediate_phases: int
1096         :type timeout: float
1097         :type doublings: int
1098         :type traffic_directions: int
1099         :type latency: bool
1100         :returns: Structure containing narrowed down NDR and PDR intervals
1101             and their measurements.
1102         :rtype: NdrPdrResult
1103         :raises RuntimeError: If total duration is larger than timeout.
1104         """
1105         minimum_transmit_rate *= traffic_directions
1106         maximum_transmit_rate *= traffic_directions
1107         # we need instance of TrafficGenerator instantiated by Robot Framework
1108         # to be able to use trex_stl-*()
1109         tg_instance = BuiltIn().get_library_instance(
1110             u"resources.libraries.python.TrafficGenerator"
1111         )
1112         tg_instance.set_rate_provider_defaults(
1113             frame_size,
1114             traffic_profile,
1115             traffic_directions=traffic_directions,
1116             latency=latency
1117         )
1118         algorithm = MultipleLossRatioSearch(
1119             measurer=tg_instance, final_trial_duration=final_trial_duration,
1120             final_relative_width=final_relative_width,
1121             number_of_intermediate_phases=number_of_intermediate_phases,
1122             initial_trial_duration=initial_trial_duration, timeout=timeout,
1123             doublings=doublings
1124         )
1125         result = algorithm.narrow_down_ndr_and_pdr(
1126             minimum_transmit_rate, maximum_transmit_rate, packet_loss_ratio
1127         )
1128         return result
1129
1130     @staticmethod
1131     def perform_soak_search(
1132             frame_size, traffic_profile, minimum_transmit_rate,
1133             maximum_transmit_rate, plr_target=1e-7, tdpt=0.1,
1134             initial_count=50, timeout=1800.0, trace_enabled=False,
1135             traffic_directions=2, latency=False):
1136         """Setup initialized TG, perform soak search, return avg and stdev.
1137
1138         :param frame_size: Frame size identifier or value [B].
1139         :param traffic_profile: Module name as a traffic profile identifier.
1140             See GPL/traffic_profiles/trex for implemented modules.
1141         :param minimum_transmit_rate: Minimal uni-directional
1142             target transmit rate [pps].
1143         :param maximum_transmit_rate: Maximal uni-directional
1144             target transmit rate [pps].
1145         :param plr_target: Fraction of packets lost to achieve [1].
1146         :param tdpt: Trial duration per trial.
1147             The algorithm linearly increases trial duration with trial number,
1148             this is the increment between succesive trials, in seconds.
1149         :param initial_count: Offset to apply before the first trial.
1150             For example initial_count=50 makes first trial to be 51*tdpt long.
1151             This is needed because initial "search" phase of integrator
1152             takes significant time even without any trial results.
1153         :param timeout: The search will stop after this overall time [s].
1154         :param trace_enabled: True if trace enabled else False.
1155         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1156             Default: 2
1157         :param latency: Whether to measure latency during the trial.
1158             Default: False.
1159         :type frame_size: str or int
1160         :type traffic_profile: str
1161         :type minimum_transmit_rate: float
1162         :type maximum_transmit_rate: float
1163         :type plr_target: float
1164         :type initial_count: int
1165         :type timeout: float
1166         :type trace_enabled: bool
1167         :type traffic_directions: int
1168         :type latency: bool
1169         :returns: Average and stdev of estimated aggregate rate giving PLR.
1170         :rtype: 2-tuple of float
1171         """
1172         minimum_transmit_rate *= traffic_directions
1173         maximum_transmit_rate *= traffic_directions
1174         tg_instance = BuiltIn().get_library_instance(
1175             u"resources.libraries.python.TrafficGenerator"
1176         )
1177         tg_instance.set_rate_provider_defaults(
1178             frame_size,
1179             traffic_profile,
1180             traffic_directions=traffic_directions,
1181             negative_loss=False,
1182             latency=latency
1183         )
1184         algorithm = PLRsearch(
1185             measurer=tg_instance, trial_duration_per_trial=tdpt,
1186             packet_loss_ratio_target=plr_target,
1187             trial_number_offset=initial_count, timeout=timeout,
1188             trace_enabled=trace_enabled
1189         )
1190         result = algorithm.search(minimum_transmit_rate, maximum_transmit_rate)
1191         return result