Tolerate failures when setting MTU
[csit.git] / resources / libraries / python / TrafficGenerator.py
1 # Copyright (c) 2019 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 from robot.api import logger
17 from robot.libraries.BuiltIn import BuiltIn
18
19 from .DropRateSearch import DropRateSearch
20 from .Constants import Constants
21 from .ssh import SSH, exec_cmd_no_error
22 from .topology import NodeType
23 from .topology import NodeSubTypeTG
24 from .topology import Topology
25 from .MLRsearch.AbstractMeasurer import AbstractMeasurer
26 from .MLRsearch.MultipleLossRatioSearch import MultipleLossRatioSearch
27 from .MLRsearch.ReceiveRateMeasurement import ReceiveRateMeasurement
28 from .PLRsearch.PLRsearch import PLRsearch
29
30 __all__ = ['TGDropRateSearchImpl', 'TrafficGenerator', 'OptimizedSearch']
31
32
33 class TGDropRateSearchImpl(DropRateSearch):
34     """Drop Rate Search implementation."""
35
36     def __init__(self):
37         super(TGDropRateSearchImpl, self).__init__()
38
39     def measure_loss(self, rate, frame_size, loss_acceptance,
40                      loss_acceptance_type, traffic_profile, skip_warmup=False):
41         """Runs the traffic and evaluate the measured results.
42
43         :param rate: Offered traffic load.
44         :param frame_size: Size of frame.
45         :param loss_acceptance: Permitted drop ratio or frames count.
46         :param loss_acceptance_type: Type of permitted loss.
47         :param traffic_profile: Module name as a traffic profile identifier.
48             See resources/traffic_profiles/trex for implemented modules.
49         :param skip_warmup: Start TRex without warmup traffic if true.
50         :type rate: float
51         :type frame_size: str
52         :type loss_acceptance: float
53         :type loss_acceptance_type: LossAcceptanceType
54         :type traffic_profile: str
55         :type skip_warmup: bool
56         :returns: Drop threshold exceeded? (True/False)
57         :rtype: bool
58         :raises NotImplementedError: If TG is not supported.
59         :raises RuntimeError: If TG is not specified.
60         """
61         # we need instance of TrafficGenerator instantiated by Robot Framework
62         # to be able to use trex_stl-*()
63         tg_instance = BuiltIn().get_library_instance(
64             'resources.libraries.python.TrafficGenerator')
65
66         if tg_instance.node['subtype'] is None:
67             raise RuntimeError('TG subtype not defined')
68         elif tg_instance.node['subtype'] == NodeSubTypeTG.TREX:
69             unit_rate = str(rate) + self.get_rate_type_str()
70             if skip_warmup:
71                 tg_instance.trex_stl_start_remote_exec(self.get_duration(),
72                                                        unit_rate, frame_size,
73                                                        traffic_profile,
74                                                        warmup_time=0.0)
75             else:
76                 tg_instance.trex_stl_start_remote_exec(self.get_duration(),
77                                                        unit_rate, frame_size,
78                                                        traffic_profile)
79             loss = tg_instance.get_loss()
80             sent = tg_instance.get_sent()
81             if self.loss_acceptance_type_is_percentage():
82                 loss = (float(loss) / float(sent)) * 100
83
84             logger.trace("comparing: {} < {} {}".format(loss,
85                                                         loss_acceptance,
86                                                         loss_acceptance_type))
87             if float(loss) > float(loss_acceptance):
88                 return False
89             else:
90                 return True
91         else:
92             raise NotImplementedError("TG subtype not supported")
93
94     def get_latency(self):
95         """Returns min/avg/max latency.
96
97         :returns: Latency stats.
98         :rtype: list
99         """
100         tg_instance = BuiltIn().get_library_instance(
101             'resources.libraries.python.TrafficGenerator')
102         return tg_instance.get_latency_int()
103
104
105 class TrafficGenerator(AbstractMeasurer):
106     """Traffic Generator.
107
108     FIXME: Describe API."""
109
110     # TODO: Decrease friction between various search and rate provider APIs.
111     # TODO: Remove "trex" from lines which could work with other TGs.
112
113     # Use one instance of TrafficGenerator for all tests in test suite
114     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
115
116     def __init__(self):
117         self._result = None
118         self._loss = None
119         self._sent = None
120         self._latency = None
121         self._received = None
122         self._node = None
123         # T-REX interface order mapping
124         self._ifaces_reordered = False
125         # Parameters not given by measure().
126         self.frame_size = None
127         self.traffic_profile = None
128         self.warmup_time = None
129
130     @property
131     def node(self):
132         """Getter.
133
134         :returns: Traffic generator node.
135         :rtype: dict
136         """
137         return self._node
138
139     def get_loss(self):
140         """Return number of lost packets.
141
142         :returns: Number of lost packets.
143         :rtype: str
144         """
145         return self._loss
146
147     def get_sent(self):
148         """Return number of sent packets.
149
150         :returns: Number of sent packets.
151         :rtype: str
152         """
153         return self._sent
154
155     def get_received(self):
156         """Return number of received packets.
157
158         :returns: Number of received packets.
159         :rtype: str
160         """
161         return self._received
162
163     def get_latency_int(self):
164         """Return rounded min/avg/max latency.
165
166         :returns: Latency stats.
167         :rtype: list
168         """
169         return self._latency
170
171     def initialize_traffic_generator(
172             self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
173             tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
174             tg_if2_dst_mac=None):
175         """TG initialization.
176
177         TODO: Document why do we need (and how do we use) _ifaces_reordered.
178
179         :param tg_node: Traffic generator node.
180         :param tg_if1: TG - name of first interface.
181         :param tg_if2: TG - name of second interface.
182         :param tg_if1_adj_node: TG if1 adjecent node.
183         :param tg_if1_adj_if: TG if1 adjecent interface.
184         :param tg_if2_adj_node: TG if2 adjecent node.
185         :param tg_if2_adj_if: TG if2 adjecent interface.
186         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
187         :param tg_if1_dst_mac: Interface 1 destination MAC address.
188         :param tg_if2_dst_mac: Interface 2 destination MAC address.
189         :type tg_node: dict
190         :type tg_if1: str
191         :type tg_if2: str
192         :type tg_if1_adj_node: dict
193         :type tg_if1_adj_if: str
194         :type tg_if2_adj_node: dict
195         :type tg_if2_adj_if: str
196         :type osi_layer: str
197         :type tg_if1_dst_mac: str
198         :type tg_if2_dst_mac: str
199         :returns: nothing
200         :raises RuntimeError: In case of issue during initialization.
201         """
202         if tg_node['type'] != NodeType.TG:
203             raise RuntimeError('Node type is not a TG')
204         self._node = tg_node
205
206         if self._node['subtype'] == NodeSubTypeTG.TREX:
207             ssh = SSH()
208             ssh.connect(self._node)
209
210             (ret, _, _) = ssh.exec_command(
211                 "sudo -E sh -c '{0}/resources/tools/trex/"
212                 "trex_installer.sh {1}'".format(Constants.REMOTE_FW_DIR,
213                                                 Constants.TREX_INSTALL_VERSION),
214                 timeout=1800)
215             if int(ret) != 0:
216                 raise RuntimeError('TRex installation failed.')
217
218             if1_pci = Topology().get_interface_pci_addr(self._node, tg_if1)
219             if2_pci = Topology().get_interface_pci_addr(self._node, tg_if2)
220             if1_addr = Topology().get_interface_mac(self._node, tg_if1)
221             if2_addr = Topology().get_interface_mac(self._node, tg_if2)
222
223             if osi_layer == 'L2':
224                 if1_adj_addr = if2_addr
225                 if2_adj_addr = if1_addr
226             elif osi_layer == 'L3':
227                 if1_adj_addr = Topology().get_interface_mac(tg_if1_adj_node,
228                                                             tg_if1_adj_if)
229                 if2_adj_addr = Topology().get_interface_mac(tg_if2_adj_node,
230                                                             tg_if2_adj_if)
231             elif osi_layer == 'L7':
232                 if1_addr = Topology().get_interface_ip4(self._node, tg_if1)
233                 if2_addr = Topology().get_interface_ip4(self._node, tg_if2)
234                 if1_adj_addr = Topology().get_interface_ip4(tg_if1_adj_node,
235                                                             tg_if1_adj_if)
236                 if2_adj_addr = Topology().get_interface_ip4(tg_if2_adj_node,
237                                                             tg_if2_adj_if)
238             else:
239                 raise ValueError("Unknown Test Type")
240
241             # in case of switched environment we can override MAC addresses
242             if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
243                 if1_adj_addr = tg_if1_dst_mac
244                 if2_adj_addr = tg_if2_dst_mac
245
246             if min(if1_pci, if2_pci) != if1_pci:
247                 if1_pci, if2_pci = if2_pci, if1_pci
248                 if1_addr, if2_addr = if2_addr, if1_addr
249                 if1_adj_addr, if2_adj_addr = if2_adj_addr, if1_adj_addr
250                 self._ifaces_reordered = True
251
252             if osi_layer == 'L2' or osi_layer == 'L3':
253                 (ret, _, _) = ssh.exec_command(
254                     "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
255                     "- version: 2\n"
256                     "  interfaces: [\"{0}\",\"{1}\"]\n"
257                     "  port_info:\n"
258                     "      - dest_mac: [{2}]\n"
259                     "        src_mac: [{3}]\n"
260                     "      - dest_mac: [{4}]\n"
261                     "        src_mac: [{5}]\n"
262                     "EOF'"\
263                     .format(if1_pci, if2_pci,
264                             "0x"+if1_adj_addr.replace(":", ",0x"),
265                             "0x"+if1_addr.replace(":", ",0x"),
266                             "0x"+if2_adj_addr.replace(":", ",0x"),
267                             "0x"+if2_addr.replace(":", ",0x")))
268             elif osi_layer == 'L7':
269                 (ret, _, _) = ssh.exec_command(
270                     "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
271                     "- version: 2\n"
272                     "  interfaces: [\"{0}\",\"{1}\"]\n"
273                     "  port_info:\n"
274                     "      - ip: [{2}]\n"
275                     "        default_gw: [{3}]\n"
276                     "      - ip: [{4}]\n"
277                     "        default_gw: [{5}]\n"
278                     "EOF'"\
279                     .format(if1_pci, if2_pci,
280                             if1_addr, if1_adj_addr,
281                             if2_addr, if2_adj_addr))
282             else:
283                 raise ValueError("Unknown Test Type")
284             if int(ret) != 0:
285                 raise RuntimeError('TRex config generation error')
286
287             self._startup_trex(osi_layer)
288
289     def _startup_trex(self, osi_layer):
290         """Startup sequence for the TRex traffic generator.
291
292         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
293         :type osi_layer: str
294         :raises RuntimeError: If node subtype is not a TREX or startup failed.
295         """
296         if self._node['subtype'] != NodeSubTypeTG.TREX:
297             raise RuntimeError('Node subtype is not a TREX!')
298
299         for _ in range(0, 3):
300             # Kill TRex only if it is already running.
301             cmd = "sh -c 'pgrep t-rex && pkill t-rex && sleep 3 || true'"
302             exec_cmd_no_error(
303                 self._node, cmd, sudo=True, message='Kill TRex failed!')
304
305             # Configure TRex.
306             ports = ''
307             for port in self._node['interfaces'].values():
308                 ports += ' {pci}'.format(pci=port.get('pci_address'))
309
310             cmd = ("sh -c 'cd {dir}/scripts/ && "
311                    "./dpdk_nic_bind.py -u {ports} || true'"
312                    .format(dir=Constants.TREX_INSTALL_DIR, ports=ports))
313             exec_cmd_no_error(
314                 self._node, cmd, sudo=True,
315                 message='Unbind PCI ports from driver failed!')
316
317             cmd = ("sh -c 'cd {dir}/scripts/ && ./trex-cfg'"
318                    .format(dir=Constants.TREX_INSTALL_DIR))
319             exec_cmd_no_error(
320                 self._node, cmd, sudo=True, message='Config TRex failed!')
321
322             # Start TRex.
323             cmd = ("sh -c 'cd {dir}/scripts/ && "
324                    "nohup ./t-rex-64 {mode} -i -c 7 > "
325                    "/tmp/trex.log 2>&1 &' > /dev/null"
326                    .format(dir=Constants.TREX_INSTALL_DIR,
327                            mode='--astf' if osi_layer == 'L7' else ''))
328             try:
329                 exec_cmd_no_error(self._node, cmd, sudo=True)
330             except RuntimeError:
331                 cmd = "sh -c 'cat /tmp/trex.log'"
332                 exec_cmd_no_error(self._node, cmd, sudo=True,
333                                   message='Get TRex logs failed!')
334                 raise RuntimeError('Start TRex failed!')
335
336             # Test if TRex starts successfuly.
337             cmd = ("sh -c '{dir}/resources/tools/trex/trex_server_info.py'"
338                    .format(dir=Constants.REMOTE_FW_DIR))
339             try:
340                 exec_cmd_no_error(
341                     self._node, cmd, sudo=True, message='Test TRex failed!',
342                     retries=20)
343             except RuntimeError:
344                 continue
345             return
346         # After max retries TRex is still not responding to API critical error
347         # occurred.
348         raise RuntimeError('Start TRex failed after multiple retries!')
349
350     @staticmethod
351     def is_trex_running(node):
352         """Check if TRex is running using pidof.
353
354         :param node: Traffic generator node.
355         :type node: dict
356         :returns: True if TRex is running otherwise False.
357         :rtype: bool
358         :raises RuntimeError: If node type is not a TG.
359         """
360         if node['type'] != NodeType.TG:
361             raise RuntimeError('Node type is not a TG')
362
363         ssh = SSH()
364         ssh.connect(node)
365         ret, _, _ = ssh.exec_command_sudo("pidof t-rex")
366         return bool(int(ret) == 0)
367
368     @staticmethod
369     def teardown_traffic_generator(node):
370         """TG teardown.
371
372         :param node: Traffic generator node.
373         :type node: dict
374         :returns: nothing
375         :raises RuntimeError: If node type is not a TG,
376             or if TRex teardown fails.
377         """
378         if node['type'] != NodeType.TG:
379             raise RuntimeError('Node type is not a TG')
380         if node['subtype'] == NodeSubTypeTG.TREX:
381             ssh = SSH()
382             ssh.connect(node)
383             (ret, _, _) = ssh.exec_command(
384                 "sh -c 'sudo pkill t-rex && sleep 3'")
385             if int(ret) != 0:
386                 raise RuntimeError('pkill t-rex failed')
387
388     @staticmethod
389     def trex_stl_stop_remote_exec(node):
390         """Execute script on remote node over ssh to stop running traffic.
391
392         :param node: TRex generator node.
393         :type node: dict
394         :returns: Nothing
395         :raises RuntimeError: If stop traffic script fails.
396         """
397         ssh = SSH()
398         ssh.connect(node)
399
400         (ret, _, _) = ssh.exec_command(
401             "sh -c '{}/resources/tools/trex/"
402             "trex_stateless_stop.py'".format(Constants.REMOTE_FW_DIR))
403
404         if int(ret) != 0:
405             raise RuntimeError('TRex stateless runtime error')
406
407     def trex_stl_start_remote_exec(
408             self, duration, rate, frame_size, traffic_profile, async_call=False,
409             latency=True, warmup_time=5.0, unidirection=False, tx_port=0,
410             rx_port=1):
411         """Execute script on remote node over ssh to start traffic.
412
413         :param duration: Time expresed in seconds for how long to send traffic.
414         :param rate: Traffic rate expressed with units (pps, %)
415         :param frame_size: L2 frame size to send (without padding and IPG).
416         :param traffic_profile: Module name as a traffic profile identifier.
417             See resources/traffic_profiles/trex for implemented modules.
418         :param async_call: If enabled then don't wait for all incomming trafic.
419         :param latency: With latency measurement.
420         :param warmup_time: Warmup time period.
421         :param unidirection: Traffic is unidirectional. Default: False
422         :param tx_port: Traffic generator transmit port for first flow.
423             Default: 0
424         :param rx_port: Traffic generator receive port for first flow.
425             Default: 1
426         :type duration: float
427         :type rate: str
428         :type frame_size: str
429         :type traffic_profile: str
430         :type async_call: bool
431         :type latency: bool
432         :type warmup_time: float
433         :type unidirection: bool
434         :type tx_port: int
435         :type rx_port: int
436         :raises RuntimeError: In case of TG driver issue.
437         """
438         ssh = SSH()
439         ssh.connect(self._node)
440         reorder = self._ifaces_reordered  # Just to make the next line fit.
441         p_0, p_1 = (rx_port, tx_port) if reorder else (tx_port, rx_port)
442         # Values from Robot can introduce type unicode,
443         # we need to encode them, so that repr() does not lead with 'u'.
444         if isinstance(rate, unicode):
445             rate = rate.encode("utf-8")
446         if isinstance(duration, unicode):
447             duration = duration.encode("utf-8")
448         if isinstance(warmup_time, unicode):
449             warmup_time = warmup_time.encode("utf-8")
450         command = (
451             "sh -c '{tool}/resources/tools/trex/trex_stateless_profile.py"
452             " --profile {prof}/resources/traffic_profiles/trex/{traffic}.py"
453             " --duration {duration!r} --frame_size {frame_size} --rate {rate!r}"
454             " --warmup_time {warmup!r} --port_0 {p_0} --port_1 {p_1}").format(
455                 tool=Constants.REMOTE_FW_DIR, prof=Constants.REMOTE_FW_DIR,
456                 traffic=traffic_profile, duration=duration,
457                 frame_size=frame_size, rate=rate, warmup=warmup_time, p_0=p_0,
458                 p_1=p_1)
459         if async_call:
460             command += " --async"
461         if latency:
462             command += " --latency"
463         if unidirection:
464             command += " --unidirection"
465         command += "'"
466
467         (ret, stdout, _) = ssh.exec_command(
468             command, timeout=float(duration) + 60)
469
470         if int(ret) != 0:
471             raise RuntimeError('TRex stateless runtime error')
472         elif async_call:
473             #no result
474             self._received = None
475             self._sent = None
476             self._loss = None
477             self._latency = None
478         else:
479             # last line from console output
480             line = stdout.splitlines()[-1]
481             self._result = line
482             logger.info('TrafficGen result: {0}'.format(self._result))
483             self._received = self._result.split(', ')[1].split('=')[1]
484             self._sent = self._result.split(', ')[2].split('=')[1]
485             self._loss = self._result.split(', ')[3].split('=')[1]
486             self._latency = []
487             self._latency.append(self._result.split(', ')[4].split('=')[1])
488             self._latency.append(self._result.split(', ')[5].split('=')[1])
489
490     def stop_traffic_on_tg(self):
491         """Stop all traffic on TG.
492
493         :returns: Nothing
494         :raises RuntimeError: If TG is not set.
495         """
496         if self._node is None:
497             raise RuntimeError("TG is not set")
498         if self._node['subtype'] == NodeSubTypeTG.TREX:
499             self.trex_stl_stop_remote_exec(self._node)
500
501     def send_traffic_on_tg(
502             self, duration, rate, frame_size, traffic_profile, warmup_time=5,
503             async_call=False, latency=True, unidirection=False, tx_port=0,
504             rx_port=1):
505         """Send traffic from all configured interfaces on TG.
506
507         Note that bidirectional traffic also contains flows
508         transmitted from rx_port and received in tx_port.
509         But some tests use asymmetric traffic, so those arguments are relevant.
510
511         Also note that traffic generator uses DPDK driver which might
512         reorder port numbers based on wiring and PCI numbering.
513         This method handles that, so argument values are invariant,
514         but you can see swapped valued in debug logs.
515
516         TODO: Is it better to have less descriptive argument names
517         just to make them less probable to be viewed as misleading or confusing?
518         See https://gerrit.fd.io/r/#/c/17625/11/resources/libraries/python\
519         /TrafficGenerator.py@406
520
521         :param duration: Duration of test traffic generation in seconds.
522         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
523         :param frame_size: Frame size (L2) in Bytes.
524         :param traffic_profile: Module name as a traffic profile identifier.
525             See resources/traffic_profiles/trex for implemented modules.
526         :param warmup_time: Warmup phase in seconds.
527         :param async_call: Async mode.
528         :param latency: With latency measurement.
529         :param unidirection: Traffic is unidirectional. Default: False
530         :param tx_port: Traffic generator transmit port for first flow.
531             Default: 0
532         :param rx_port: Traffic generator receive port for first flow.
533             Default: 1
534         :type duration: str
535         :type rate: str
536         :type frame_size: str
537         :type traffic_profile: str
538         :type warmup_time: float
539         :type async_call: bool
540         :type latency: bool
541         :type unidirection: bool
542         :type tx_port: int
543         :type rx_port: int
544         :returns: TG output.
545         :rtype: str
546         :raises RuntimeError: If TG is not set, or if node is not TG,
547             or if subtype is not specified.
548         :raises NotImplementedError: If TG is not supported.
549         """
550
551         node = self._node
552         if node is None:
553             raise RuntimeError("TG is not set")
554
555         if node['type'] != NodeType.TG:
556             raise RuntimeError('Node type is not a TG')
557
558         if node['subtype'] is None:
559             raise RuntimeError('TG subtype not defined')
560         elif node['subtype'] == NodeSubTypeTG.TREX:
561             self.trex_stl_start_remote_exec(
562                 duration, rate, frame_size, traffic_profile, async_call,
563                 latency, warmup_time, unidirection, tx_port, rx_port)
564         else:
565             raise NotImplementedError("TG subtype not supported")
566
567         return self._result
568
569     def no_traffic_loss_occurred(self):
570         """Fail if loss occurred in traffic run.
571
572         :returns: nothing
573         :raises Exception: If loss occured.
574         """
575         if self._loss is None:
576             raise RuntimeError('The traffic generation has not been issued')
577         if self._loss != '0':
578             raise RuntimeError('Traffic loss occurred: {0}'.format(self._loss))
579
580     def fail_if_no_traffic_forwarded(self):
581         """Fail if no traffic forwarded.
582
583         :returns: nothing
584         :raises Exception: If no traffic forwarded.
585         """
586         if self._received is None:
587             raise RuntimeError('The traffic generation has not been issued')
588         if self._received == '0':
589             raise RuntimeError('No traffic forwarded')
590
591     def partial_traffic_loss_accepted(self, loss_acceptance,
592                                       loss_acceptance_type):
593         """Fail if loss is higher then accepted in traffic run.
594
595         :param loss_acceptance: Permitted drop ratio or frames count.
596         :param loss_acceptance_type: Type of permitted loss.
597         :type loss_acceptance: float
598         :type loss_acceptance_type: LossAcceptanceType
599         :returns: nothing
600         :raises Exception: If loss is above acceptance criteria.
601         """
602         if self._loss is None:
603             raise Exception('The traffic generation has not been issued')
604
605         if loss_acceptance_type == 'percentage':
606             loss = (float(self._loss) / float(self._sent)) * 100
607         elif loss_acceptance_type == 'frames':
608             loss = float(self._loss)
609         else:
610             raise Exception('Loss acceptance type not supported')
611
612         if loss > float(loss_acceptance):
613             raise Exception("Traffic loss {} above loss acceptance: {}".format(
614                 loss, loss_acceptance))
615
616     def set_rate_provider_defaults(self, frame_size, traffic_profile,
617                                    warmup_time=0.0):
618         """Store values accessed by measure().
619
620         :param frame_size: Frame size identifier or value [B].
621         :param traffic_profile: Module name as a traffic profile identifier.
622             See resources/traffic_profiles/trex for implemented modules.
623         :param warmup_time: Traffic duration before measurement starts [s].
624         :type frame_size: str or int
625         :type traffic_profile: str
626         :type warmup_time: float
627         """
628         self.frame_size = frame_size
629         self.traffic_profile = str(traffic_profile)
630         self.warmup_time = float(warmup_time)
631
632     def measure(self, duration, transmit_rate):
633         """Run bi-directional measurement, parse and return results.
634
635         :param duration: Trial duration [s].
636         :param transmit_rate: Target bidirectional transmit rate [pps].
637         :type duration: float
638         :type transmit_rate: float
639         :returns: Structure containing the result of the measurement.
640         :rtype: ReceiveRateMeasurement
641         :raises RuntimeError: If TG is not set, or if node is not TG,
642             or if subtype is not specified.
643         :raises NotImplementedError: If TG is not supported.
644         """
645         duration = float(duration)
646         transmit_rate = float(transmit_rate)
647         # Trex needs target Tr per stream, but reports aggregate Tx and Dx.
648         unit_rate = str(transmit_rate / 2.0) + "pps"
649         self.send_traffic_on_tg(
650             duration, unit_rate, self.frame_size, self.traffic_profile,
651             warmup_time=self.warmup_time, latency=True)
652         transmit_count = int(self.get_sent())
653         loss_count = int(self.get_loss())
654         measurement = ReceiveRateMeasurement(
655             duration, transmit_rate, transmit_count, loss_count)
656         measurement.latency = self.get_latency_int()
657         return measurement
658
659
660 class OptimizedSearch(object):
661     """Class to be imported as Robot Library, containing a single keyword."""
662
663     @staticmethod
664     def perform_optimized_ndrpdr_search(
665             frame_size, traffic_profile, minimum_transmit_rate,
666             maximum_transmit_rate, packet_loss_ratio=0.005,
667             final_relative_width=0.005, final_trial_duration=30.0,
668             initial_trial_duration=1.0, number_of_intermediate_phases=2,
669             timeout=720.0, doublings=1):
670         """Setup initialized TG, perform optimized search, return intervals.
671
672         :param frame_size: Frame size identifier or value [B].
673         :param traffic_profile: Module name as a traffic profile identifier.
674             See resources/traffic_profiles/trex for implemented modules.
675         :param minimum_transmit_rate: Minimal bidirectional
676             target transmit rate [pps].
677         :param maximum_transmit_rate: Maximal bidirectional
678             target transmit rate [pps].
679         :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
680         :param final_relative_width: Final lower bound transmit rate
681             cannot be more distant that this multiple of upper bound [1].
682         :param final_trial_duration: Trial duration for the final phase [s].
683         :param initial_trial_duration: Trial duration for the initial phase
684             and also for the first intermediate phase [s].
685         :param number_of_intermediate_phases: Number of intermediate phases
686             to perform before the final phase [1].
687         :param timeout: The search will fail itself when not finished
688             before this overall time [s].
689         :param doublings: How many doublings to do in external search step.
690             Default 1 is suitable for fairly stable tests,
691             less stable tests might get better overal duration with 2 or more.
692         :type frame_size: str or int
693         :type traffic_profile: str
694         :type minimum_transmit_rate: float
695         :type maximum_transmit_rate: float
696         :type packet_loss_ratio: float
697         :type final_relative_width: float
698         :type final_trial_duration: float
699         :type initial_trial_duration: float
700         :type number_of_intermediate_phases: int
701         :type timeout: float
702         :type doublings: int
703         :returns: Structure containing narrowed down NDR and PDR intervals
704             and their measurements.
705         :rtype: NdrPdrResult
706         :raises RuntimeError: If total duration is larger than timeout.
707         """
708         # we need instance of TrafficGenerator instantiated by Robot Framework
709         # to be able to use trex_stl-*()
710         tg_instance = BuiltIn().get_library_instance(
711             'resources.libraries.python.TrafficGenerator')
712         tg_instance.set_rate_provider_defaults(frame_size, traffic_profile)
713         algorithm = MultipleLossRatioSearch(
714             measurer=tg_instance, final_trial_duration=final_trial_duration,
715             final_relative_width=final_relative_width,
716             number_of_intermediate_phases=number_of_intermediate_phases,
717             initial_trial_duration=initial_trial_duration, timeout=timeout,
718             doublings=doublings)
719         result = algorithm.narrow_down_ndr_and_pdr(
720             minimum_transmit_rate, maximum_transmit_rate, packet_loss_ratio)
721         return result
722
723     @staticmethod
724     def perform_soak_search(
725             frame_size, traffic_profile, minimum_transmit_rate,
726             maximum_transmit_rate, plr_target=1e-7, tdpt=0.1,
727             initial_count=50, timeout=1800.0, trace_enabled=False):
728         """Setup initialized TG, perform soak search, return avg and stdev.
729
730         :param frame_size: Frame size identifier or value [B].
731         :param traffic_profile: Module name as a traffic profile identifier.
732             See resources/traffic_profiles/trex for implemented modules.
733         :param minimum_transmit_rate: Minimal bidirectional
734             target transmit rate [pps].
735         :param maximum_transmit_rate: Maximal bidirectional
736             target transmit rate [pps].
737         :param plr_target: Fraction of packets lost to achieve [1].
738         :param tdpt: Trial duration per trial.
739             The algorithm linearly increases trial duration with trial number,
740             this is the increment between succesive trials, in seconds.
741         :param initial_count: Offset to apply before the first trial.
742             For example initial_count=50 makes first trial to be 51*tdpt long.
743             This is needed because initial "search" phase of integrator
744             takes significant time even without any trial results.
745         :param timeout: The search will stop after this overall time [s].
746         :type frame_size: str or int
747         :type traffic_profile: str
748         :type minimum_transmit_rate: float
749         :type maximum_transmit_rate: float
750         :type plr_target: float
751         :type initial_count: int
752         :type timeout: float
753         :returns: Average and stdev of estimated bidirectional rate giving PLR.
754         :rtype: 2-tuple of float
755         """
756         tg_instance = BuiltIn().get_library_instance(
757             'resources.libraries.python.TrafficGenerator')
758         tg_instance.set_rate_provider_defaults(frame_size, traffic_profile)
759         algorithm = PLRsearch(
760             measurer=tg_instance, trial_duration_per_trial=tdpt,
761             packet_loss_ratio_target=plr_target,
762             trial_number_offset=initial_count, timeout=timeout,
763             trace_enabled=trace_enabled)
764         result = algorithm.search(minimum_transmit_rate, maximum_transmit_rate)
765         return result