FIX: check if t-rex is running at test setup of all perf tests
[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['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 resources/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 # TODO: Pylint says too-many-instance-attributes.
131 # A fix is developed in https://gerrit.fd.io/r/c/csit/+/22221
132 class TrafficGenerator(AbstractMeasurer):
133     """Traffic Generator.
134
135     FIXME: Describe API."""
136
137     # TODO: Decrease friction between various search and rate provider APIs.
138     # TODO: Remove "trex" from lines which could work with other TGs.
139
140     # Use one instance of TrafficGenerator for all tests in test suite
141     ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
142
143     def __init__(self):
144         # TODO: Number of fields will be reduced with CSIT-1378.
145         self._node = None
146         # T-REX interface order mapping
147         self._ifaces_reordered = False
148         # Result holding fields, to be removed.
149         self._result = None
150         self._loss = None
151         self._sent = None
152         self._latency = None
153         self._received = None
154         # Measurement input fields, needed for async stop result.
155         self._start_time = None
156         self._rate = None
157         # Other input parameters, not knowable from measure() signature.
158         self.frame_size = None
159         self.traffic_profile = None
160         self.warmup_time = None
161         self.traffic_directions = None
162         # Transient data needed for async measurements.
163         self._xstats = (None, None)
164         # TODO: Rename "xstats" to something opaque, so TRex is not privileged?
165
166     @property
167     def node(self):
168         """Getter.
169
170         :returns: Traffic generator node.
171         :rtype: dict
172         """
173         return self._node
174
175     def get_loss(self):
176         """Return number of lost packets.
177
178         :returns: Number of lost packets.
179         :rtype: str
180         """
181         return self._loss
182
183     def get_sent(self):
184         """Return number of sent packets.
185
186         :returns: Number of sent packets.
187         :rtype: str
188         """
189         return self._sent
190
191     def get_received(self):
192         """Return number of received packets.
193
194         :returns: Number of received packets.
195         :rtype: str
196         """
197         return self._received
198
199     def get_latency_int(self):
200         """Return rounded min/avg/max latency.
201
202         :returns: Latency stats.
203         :rtype: list
204         """
205         return self._latency
206
207     # TODO: pylint says disable=too-many-locals.
208     # A fix is developed in https://gerrit.fd.io/r/c/csit/+/22221
209     def initialize_traffic_generator(
210             self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
211             tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
212             tg_if2_dst_mac=None):
213         """TG initialization.
214
215         TODO: Document why do we need (and how do we use) _ifaces_reordered.
216
217         :param tg_node: Traffic generator node.
218         :param tg_if1: TG - name of first interface.
219         :param tg_if2: TG - name of second interface.
220         :param tg_if1_adj_node: TG if1 adjecent node.
221         :param tg_if1_adj_if: TG if1 adjecent interface.
222         :param tg_if2_adj_node: TG if2 adjecent node.
223         :param tg_if2_adj_if: TG if2 adjecent interface.
224         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
225         :param tg_if1_dst_mac: Interface 1 destination MAC address.
226         :param tg_if2_dst_mac: Interface 2 destination MAC address.
227         :type tg_node: dict
228         :type tg_if1: str
229         :type tg_if2: str
230         :type tg_if1_adj_node: dict
231         :type tg_if1_adj_if: str
232         :type tg_if2_adj_node: dict
233         :type tg_if2_adj_if: str
234         :type osi_layer: str
235         :type tg_if1_dst_mac: str
236         :type tg_if2_dst_mac: str
237         :returns: nothing
238         :raises RuntimeError: In case of issue during initialization.
239         """
240         subtype = check_subtype(tg_node)
241         if subtype == NodeSubTypeTG.TREX:
242             self._node = tg_node
243
244             if1_pci = Topology().get_interface_pci_addr(self._node, tg_if1)
245             if2_pci = Topology().get_interface_pci_addr(self._node, tg_if2)
246             if1_addr = Topology().get_interface_mac(self._node, tg_if1)
247             if2_addr = Topology().get_interface_mac(self._node, tg_if2)
248
249             if osi_layer == u"L2":
250                 if1_adj_addr = if2_addr
251                 if2_adj_addr = if1_addr
252             elif osi_layer == u"L3":
253                 if1_adj_addr = Topology().get_interface_mac(
254                     tg_if1_adj_node, tg_if1_adj_if
255                 )
256                 if2_adj_addr = Topology().get_interface_mac(
257                     tg_if2_adj_node, tg_if2_adj_if
258                 )
259             elif osi_layer == u"L7":
260                 if1_addr = Topology().get_interface_ip4(self._node, tg_if1)
261                 if2_addr = Topology().get_interface_ip4(self._node, tg_if2)
262                 if1_adj_addr = Topology().get_interface_ip4(
263                     tg_if1_adj_node, tg_if1_adj_if
264                 )
265                 if2_adj_addr = Topology().get_interface_ip4(
266                     tg_if2_adj_node, tg_if2_adj_if
267                 )
268             else:
269                 raise ValueError(u"Unknown Test Type")
270
271             # in case of switched environment we can override MAC addresses
272             if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
273                 if1_adj_addr = tg_if1_dst_mac
274                 if2_adj_addr = tg_if2_dst_mac
275
276             if min(if1_pci, if2_pci) != if1_pci:
277                 if1_pci, if2_pci = if2_pci, if1_pci
278                 if1_addr, if2_addr = if2_addr, if1_addr
279                 if1_adj_addr, if2_adj_addr = if2_adj_addr, if1_adj_addr
280                 self._ifaces_reordered = True
281
282             master_thread_id, latency_thread_id, socket, threads = \
283                 CpuUtils.get_affinity_trex(
284                     self._node, tg_if1, tg_if2,
285                     tg_dtc=Constants.TREX_CORE_COUNT)
286
287             if osi_layer in (u"L2", u"L3"):
288                 dst_mac0 = f"0x{if1_adj_addr.replace(u':', u',0x')}"
289                 src_mac0 = f"0x{if1_addr.replace(u':', u',0x')}"
290                 dst_mac1 = f"0x{if2_adj_addr.replace(u':', u',0x')}"
291                 src_mac1 = f"0x{if2_addr.replace(u':', u',0x')}"
292                 exec_cmd_no_error(
293                     self._node,
294                     f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
295                     f"- version: 2\n"
296                     f"  c: {len(threads)}\n"
297                     f"  limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
298                     f"  interfaces: [\"{if1_pci}\",\"{if2_pci}\"]\n"
299                     f"  port_info:\n"
300                     f"      - dest_mac: [{dst_mac0}]\n"
301                     f"        src_mac: [{src_mac0}]\n"
302                     f"      - dest_mac: [{dst_mac1}]\n"
303                     f"        src_mac: [{src_mac1}]\n"
304                     f"  platform :\n"
305                     f"      master_thread_id: {master_thread_id}\n"
306                     f"      latency_thread_id: {latency_thread_id}\n"
307                     f"      dual_if:\n"
308                     f"          - socket: {socket}\n"
309                     f"            threads: {threads}\n"
310                     f"EOF'",
311                     sudo=True, message=u"TRex config generation!"
312                 )
313             elif osi_layer == u"L7":
314                 exec_cmd_no_error(
315                     self._node,
316                     f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
317                     f"- version: 2\n"
318                     f"  c: {len(threads)}\n"
319                     f"  limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
320                     f"  interfaces: [\"{if1_pci}\",\"{if2_pci}\"]\n"
321                     f"  port_info:\n"
322                     f"      - ip: [{if1_addr}]\n"
323                     f"        default_gw: [{if1_adj_addr}]\n"
324                     f"      - ip: [{if2_addr}]\n"
325                     f"        default_gw: [{if2_adj_addr}]\n"
326                     f"  platform :\n"
327                     f"      master_thread_id: {master_thread_id}\n"
328                     f"      latency_thread_id: {latency_thread_id}\n"
329                     f"      dual_if:\n"
330                     f"          - socket: {socket}\n"
331                     f"            threads: {threads}\n"
332                     f"EOF'",
333                     sudo=True, message=u"TRex config generation!"
334                 )
335             else:
336                 raise ValueError(u"Unknown Test Type!")
337
338             TrafficGenerator.startup_trex(
339                 self._node, osi_layer, subtype=subtype
340             )
341
342     @staticmethod
343     def startup_trex(tg_node, osi_layer, subtype=None):
344         """Startup sequence for the TRex traffic generator.
345
346         :param tg_node: Traffic generator node.
347         :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
348         :param subtype: Traffic generator sub-type.
349         :type tg_node: dict
350         :type osi_layer: str
351         :type subtype: NodeSubTypeTG
352         :raises RuntimeError: If node subtype is not a TREX or startup failed.
353         """
354         if not subtype:
355             subtype = check_subtype(tg_node)
356         if subtype == NodeSubTypeTG.TREX:
357             for _ in range(0, 3):
358                 # Kill TRex only if it is already running.
359                 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
360                 exec_cmd_no_error(
361                     tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
362                 )
363
364                 # Configure TRex.
365                 ports = ''
366                 for port in tg_node[u"interfaces"].values():
367                     ports += f" {port.get(u'pci_address')}"
368
369                 cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \
370                     f"./dpdk_nic_bind.py -u {ports} || true\""
371                 exec_cmd_no_error(
372                     tg_node, cmd, sudo=True,
373                     message=u"Unbind PCI ports from driver failed!"
374                 )
375
376                 # Start TRex.
377                 cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
378                 trex_cmd = OptionString([u"nohup", u"./t-rex-64"])
379                 trex_cmd.add(u"-i")
380                 trex_cmd.add(u"--prefix $(hostname)")
381                 trex_cmd.add(u"--hdrh")
382                 trex_cmd.add(u"--no-scapy-server")
383                 trex_cmd.add_if(u"--astf", osi_layer == u"L7")
384                 # OptionString does not create double space if extra is empty.
385                 trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}")
386                 inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &"
387                 cmd = f"sh -c \"{inner_command}\" > /dev/null"
388                 try:
389                     exec_cmd_no_error(tg_node, cmd, sudo=True)
390                 except RuntimeError:
391                     cmd = u"sh -c \"cat /tmp/trex.log\""
392                     exec_cmd_no_error(
393                         tg_node, cmd, sudo=True,
394                         message=u"Get TRex logs failed!"
395                     )
396                     raise RuntimeError(u"Start TRex failed!")
397
398                 # Test if TRex starts successfully.
399                 cmd = f"sh -c \"{Constants.REMOTE_FW_DIR}/resources/tools/" \
400                     f"trex/trex_server_info.py\""
401                 try:
402                     exec_cmd_no_error(
403                         tg_node, cmd, sudo=True,
404                         message=u"Test TRex failed!", retries=20
405                     )
406                 except RuntimeError:
407                     continue
408                 return
409             # After max retries TRex is still not responding to API critical
410             # error occurred.
411             exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True)
412             raise RuntimeError(u"Start TRex failed after multiple retries!")
413
414     @staticmethod
415     def is_trex_running(node):
416         """Check if TRex is running using pidof.
417
418         :param node: Traffic generator node.
419         :type node: dict
420         :returns: True if TRex is running otherwise False.
421         :rtype: bool
422         :raises RuntimeError: If node type is not a TG.
423         """
424         ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True)
425         return bool(int(ret) == 0)
426
427     @staticmethod
428     def teardown_traffic_generator(node):
429         """TG teardown.
430
431         :param node: Traffic generator node.
432         :type node: dict
433         :returns: nothing
434         :raises RuntimeError: If node type is not a TG,
435             or if TRex teardown fails.
436         """
437         subtype = check_subtype(node)
438         if subtype == NodeSubTypeTG.TREX:
439             exec_cmd_no_error(
440                 node,
441                 u"sh -c "
442                 u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"",
443                 sudo=False,
444                 message=u"pkill t-rex failed"
445             )
446
447     def _parse_traffic_results(self, stdout):
448         """Parse stdout of scripts into fields of self.
449
450         Block of code to reuse, by sync start, or stop after async.
451         TODO: Is the output TG subtype dependent?
452
453         :param stdout: Text containing the standard output.
454         :type stdout: str
455         """
456         # last line from console output
457         line = stdout.splitlines()[-1]
458         self._result = line
459         logger.info(f"TrafficGen result: {self._result}")
460         self._received = self._result.split(u", ")[1].split(u"=", 1)[1]
461         self._sent = self._result.split(u", ")[2].split(u"=", 1)[1]
462         self._loss = self._result.split(u", ")[3].split(u"=", 1)[1]
463         self._latency = list()
464         self._latency.append(self._result.split(u", ")[4].split(u"=", 1)[1])
465         self._latency.append(self._result.split(u", ")[5].split(u"=", 1)[1])
466
467     def trex_stl_stop_remote_exec(self, node):
468         """Execute script on remote node over ssh to stop running traffic.
469
470         Internal state is updated with measurement results.
471
472         :param node: TRex generator node.
473         :type node: dict
474         :raises RuntimeError: If stop traffic script fails.
475         """
476         # No need to check subtype, we know it is TREX.
477         x_args = u""
478         for index, value in enumerate(self._xstats):
479             if value is not None:
480                 # Nested quoting is fun.
481                 value = value.replace(u"'", u"\"")
482                 x_args += f" --xstat{index}='\"'\"'{value}'\"'\"'"
483         stdout, _ = exec_cmd_no_error(
484             node, f"sh -c '{Constants.REMOTE_FW_DIR}/resources/tools/trex/"
485             f"trex_stateless_stop.py{x_args}'",
486             message=u"TRex stateless runtime error"
487         )
488         self._parse_traffic_results(stdout)
489
490     def trex_stl_start_remote_exec(
491             self, duration, rate, frame_size, traffic_profile, async_call=False,
492             latency=True, warmup_time=5.0, traffic_directions=2, tx_port=0,
493             rx_port=1):
494         """Execute script on remote node over ssh to start traffic.
495
496         In sync mode, measurement results are stored internally.
497         In async mode, initial data including xstats are stored internally.
498
499         :param duration: Time expresed in seconds for how long to send traffic.
500         :param rate: Traffic rate expressed with units (pps, %)
501         :param frame_size: L2 frame size to send (without padding and IPG).
502         :param traffic_profile: Module name as a traffic profile identifier.
503             See resources/traffic_profiles/trex for implemented modules.
504         :param async_call: If enabled then don't wait for all incomming trafic.
505         :param latency: With latency measurement.
506         :param warmup_time: Warmup time period.
507         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
508             Default: 2
509         :param tx_port: Traffic generator transmit port for first flow.
510             Default: 0
511         :param rx_port: Traffic generator receive port for first flow.
512             Default: 1
513         :type duration: float
514         :type rate: str
515         :type frame_size: str
516         :type traffic_profile: str
517         :type async_call: bool
518         :type latency: bool
519         :type warmup_time: float
520         :type traffic_directions: int
521         :type tx_port: int
522         :type rx_port: int
523         :raises RuntimeError: In case of TG driver issue.
524         """
525         # No need to check subtype, we know it is TREX.
526         reorder = self._ifaces_reordered  # Just to make the next line fit.
527         p_0, p_1 = (rx_port, tx_port) if reorder else (tx_port, rx_port)
528
529         if not isinstance(duration, (float, int)):
530             duration = float(duration)
531         if not isinstance(warmup_time, (float, int)):
532             warmup_time = float(warmup_time)
533         command = f"sh -c \"" \
534             f"{Constants.REMOTE_FW_DIR}/resources/tools/trex/" \
535             f"trex_stateless_profile.py " \
536             f"--profile {Constants.REMOTE_FW_DIR}/resources/" \
537             f"traffic_profiles/trex/{traffic_profile}.py " \
538             f"--duration {duration!r} --frame_size {frame_size} " \
539             f"--rate {rate!r} --warmup_time {warmup_time!r} " \
540             f"--port_0 {p_0} --port_1 {p_1} " \
541             f"--traffic_directions {traffic_directions}"
542         if async_call:
543             command += u" --async_start"
544         if latency:
545             command += u" --latency"
546         if Constants.TREX_SEND_FORCE:
547             command += u" --force"
548         command += u"\""
549
550         stdout, _ = exec_cmd_no_error(
551             self._node, command, timeout=float(duration) + 60,
552             message=u"TRex stateless runtime error"
553         )
554
555         self.traffic_directions = traffic_directions
556         if async_call:
557             # no result
558             self._start_time = time.time()
559             self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
560             self._received = None
561             self._sent = None
562             self._loss = None
563             self._latency = None
564             xstats = [None, None]
565             index = 0
566             for line in stdout.splitlines():
567                 if f"Xstats snapshot {index}: " in line:
568                     xstats[index] = line[19:]
569                     index += 1
570                 if index == 2:
571                     break
572             self._xstats = tuple(xstats)
573         else:
574             self._parse_traffic_results(stdout)
575             self._start_time = None
576             self._rate = None
577
578     def stop_traffic_on_tg(self):
579         """Stop all traffic on TG.
580
581         :returns: Structure containing the result of the measurement.
582         :rtype: ReceiveRateMeasurement
583         :raises RuntimeError: If TG is not set.
584         """
585         subtype = check_subtype(self._node)
586         if subtype == NodeSubTypeTG.TREX:
587             self.trex_stl_stop_remote_exec(self._node)
588         return self.get_measurement_result()
589
590     def send_traffic_on_tg(
591             self, duration, rate, frame_size, traffic_profile, warmup_time=5,
592             async_call=False, latency=True, traffic_directions=2, tx_port=0,
593             rx_port=1):
594         """Send traffic from all configured interfaces on TG.
595
596         In async mode, xstats is stored internally,
597         to enable getting correct result when stopping the traffic.
598         In both modes, stdout is returned,
599         but _parse_traffic_results only works in sync output.
600
601         Note that bidirectional traffic also contains flows
602         transmitted from rx_port and received in tx_port.
603         But some tests use asymmetric traffic, so those arguments are relevant.
604
605         Also note that traffic generator uses DPDK driver which might
606         reorder port numbers based on wiring and PCI numbering.
607         This method handles that, so argument values are invariant,
608         but you can see swapped valued in debug logs.
609
610         TODO: Is it better to have less descriptive argument names
611         just to make them less probable to be viewed as misleading or confusing?
612         See https://gerrit.fd.io/r/#/c/17625/11/resources/libraries/python\
613         /TrafficGenerator.py@406
614
615         :param duration: Duration of test traffic generation in seconds.
616         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
617         :param frame_size: Frame size (L2) in Bytes.
618         :param traffic_profile: Module name as a traffic profile identifier.
619             See resources/traffic_profiles/trex for implemented modules.
620         :param warmup_time: Warmup phase in seconds.
621         :param async_call: Async mode.
622         :param latency: With latency measurement.
623         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
624             Default: 2
625         :param tx_port: Traffic generator transmit port for first flow.
626             Default: 0
627         :param rx_port: Traffic generator receive port for first flow.
628             Default: 1
629         :type duration: str
630         :type rate: str
631         :type frame_size: str
632         :type traffic_profile: str
633         :type warmup_time: float
634         :type async_call: bool
635         :type latency: bool
636         :type traffic_directions: int
637         :type tx_port: int
638         :type rx_port: int
639         :returns: TG output.
640         :rtype: str
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         subtype = check_subtype(self._node)
646         if subtype == NodeSubTypeTG.TREX:
647             self.trex_stl_start_remote_exec(
648                 duration, rate, frame_size, traffic_profile, async_call,
649                 latency, warmup_time, traffic_directions, tx_port, rx_port
650             )
651
652         return self._result
653
654     def no_traffic_loss_occurred(self):
655         """Fail if loss occurred in traffic run.
656
657         :returns: nothing
658         :raises Exception: If loss occured.
659         """
660         if self._loss is None:
661             raise RuntimeError(u"The traffic generation has not been issued")
662         if self._loss != u"0":
663             raise RuntimeError(f"Traffic loss occurred: {self._loss}")
664
665     def fail_if_no_traffic_forwarded(self):
666         """Fail if no traffic forwarded.
667
668         :returns: nothing
669         :raises Exception: If no traffic forwarded.
670         """
671         if self._received is None:
672             raise RuntimeError(u"The traffic generation has not been issued")
673         if self._received == u"0":
674             raise RuntimeError(u"No traffic forwarded")
675
676     def partial_traffic_loss_accepted(
677             self, loss_acceptance, loss_acceptance_type):
678         """Fail if loss is higher then accepted in traffic run.
679
680         :param loss_acceptance: Permitted drop ratio or frames count.
681         :param loss_acceptance_type: Type of permitted loss.
682         :type loss_acceptance: float
683         :type loss_acceptance_type: LossAcceptanceType
684         :returns: nothing
685         :raises Exception: If loss is above acceptance criteria.
686         """
687         if self._loss is None:
688             raise Exception(u"The traffic generation has not been issued")
689
690         if loss_acceptance_type == u"percentage":
691             loss = (float(self._loss) / float(self._sent)) * 100
692         elif loss_acceptance_type == u"frames":
693             loss = float(self._loss)
694         else:
695             raise Exception(u"Loss acceptance type not supported")
696
697         if loss > float(loss_acceptance):
698             raise Exception(
699                 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
700             )
701
702     def set_rate_provider_defaults(
703             self, frame_size, traffic_profile, warmup_time=0.0,
704             traffic_directions=2):
705         """Store values accessed by measure().
706
707         :param frame_size: Frame size identifier or value [B].
708         :param traffic_profile: Module name as a traffic profile identifier.
709             See resources/traffic_profiles/trex for implemented modules.
710         :param warmup_time: Traffic duration before measurement starts [s].
711         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
712             Default: 2
713         :type frame_size: str or int
714         :type traffic_profile: str
715         :type warmup_time: float
716         :type traffic_directions: int
717         """
718         self.frame_size = frame_size
719         self.traffic_profile = str(traffic_profile)
720         self.warmup_time = float(warmup_time)
721         self.traffic_directions = traffic_directions
722
723     def get_measurement_result(self, duration=None, transmit_rate=None):
724         """Return the result of last measurement as ReceiveRateMeasurement.
725
726         Separate function, as measurements can end either by time
727         or by explicit call, this is the common block at the end.
728
729         TODO: Fail on running or already reported measurement.
730
731         :param duration: Measurement duration [s] if known beforehand.
732             For explicitly stopped measurement it is estimated.
733         :param transmit_rate: Target aggregate transmit rate [pps].
734             If not given, computed assuming it was bidirectional.
735         :type duration: float or NoneType
736         :type transmit_rate: float or NoneType
737         :returns: Structure containing the result of the measurement.
738         :rtype: ReceiveRateMeasurement
739         """
740         if duration is None:
741             duration = time.time() - self._start_time
742             self._start_time = None
743         if transmit_rate is None:
744             transmit_rate = self._rate * self.traffic_directions
745         transmit_count = int(self.get_sent())
746         loss_count = int(self.get_loss())
747         measurement = ReceiveRateMeasurement(
748             duration, transmit_rate, transmit_count, loss_count
749         )
750         measurement.latency = self.get_latency_int()
751         return measurement
752
753     def measure(self, duration, transmit_rate):
754         """Run trial measurement, parse and return aggregate results.
755
756         Aggregate means sum over traffic directions.
757
758         :param duration: Trial duration [s].
759         :param transmit_rate: Target aggregate transmit rate [pps].
760         :type duration: float
761         :type transmit_rate: float
762         :returns: Structure containing the result of the measurement.
763         :rtype: ReceiveRateMeasurement
764         :raises RuntimeError: If TG is not set, or if node is not TG,
765             or if subtype is not specified.
766         :raises NotImplementedError: If TG is not supported.
767         """
768         duration = float(duration)
769         transmit_rate = float(transmit_rate)
770         # TG needs target Tr per stream, but reports aggregate Tx and Dx.
771         unit_rate_int = transmit_rate / float(self.traffic_directions)
772         unit_rate_str = str(unit_rate_int) + u"pps"
773         self.send_traffic_on_tg(
774             duration, unit_rate_str, self.frame_size, self.traffic_profile,
775             warmup_time=self.warmup_time, latency=True,
776             traffic_directions=self.traffic_directions
777         )
778         return self.get_measurement_result(duration, transmit_rate)
779
780
781 class OptimizedSearch:
782     """Class to be imported as Robot Library, containing search keywords.
783
784     Aside of setting up measurer and forwarding arguments,
785     the main business is to translate min/max rate from unidir to aggregate.
786     """
787
788     @staticmethod
789     def perform_optimized_ndrpdr_search(
790             frame_size, traffic_profile, minimum_transmit_rate,
791             maximum_transmit_rate, packet_loss_ratio=0.005,
792             final_relative_width=0.005, final_trial_duration=30.0,
793             initial_trial_duration=1.0, number_of_intermediate_phases=2,
794             timeout=720.0, doublings=1, traffic_directions=2):
795         """Setup initialized TG, perform optimized search, return intervals.
796
797         :param frame_size: Frame size identifier or value [B].
798         :param traffic_profile: Module name as a traffic profile identifier.
799             See resources/traffic_profiles/trex for implemented modules.
800         :param minimum_transmit_rate: Minimal uni-directional
801             target transmit rate [pps].
802         :param maximum_transmit_rate: Maximal uni-directional
803             target transmit rate [pps].
804         :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
805         :param final_relative_width: Final lower bound transmit rate
806             cannot be more distant that this multiple of upper bound [1].
807         :param final_trial_duration: Trial duration for the final phase [s].
808         :param initial_trial_duration: Trial duration for the initial phase
809             and also for the first intermediate phase [s].
810         :param number_of_intermediate_phases: Number of intermediate phases
811             to perform before the final phase [1].
812         :param timeout: The search will fail itself when not finished
813             before this overall time [s].
814         :param doublings: How many doublings to do in external search step.
815             Default 1 is suitable for fairly stable tests,
816             less stable tests might get better overal duration with 2 or more.
817         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
818             Default: 2
819         :type frame_size: str or int
820         :type traffic_profile: str
821         :type minimum_transmit_rate: float
822         :type maximum_transmit_rate: float
823         :type packet_loss_ratio: float
824         :type final_relative_width: float
825         :type final_trial_duration: float
826         :type initial_trial_duration: float
827         :type number_of_intermediate_phases: int
828         :type timeout: float
829         :type doublings: int
830         :type traffic_directions: int
831         :returns: Structure containing narrowed down NDR and PDR intervals
832             and their measurements.
833         :rtype: NdrPdrResult
834         :raises RuntimeError: If total duration is larger than timeout.
835         """
836         minimum_transmit_rate *= traffic_directions
837         maximum_transmit_rate *= traffic_directions
838         # we need instance of TrafficGenerator instantiated by Robot Framework
839         # to be able to use trex_stl-*()
840         tg_instance = BuiltIn().get_library_instance(
841             u"resources.libraries.python.TrafficGenerator"
842         )
843         tg_instance.set_rate_provider_defaults(
844             frame_size, traffic_profile, traffic_directions=traffic_directions)
845         algorithm = MultipleLossRatioSearch(
846             measurer=tg_instance, final_trial_duration=final_trial_duration,
847             final_relative_width=final_relative_width,
848             number_of_intermediate_phases=number_of_intermediate_phases,
849             initial_trial_duration=initial_trial_duration, timeout=timeout,
850             doublings=doublings
851         )
852         result = algorithm.narrow_down_ndr_and_pdr(
853             minimum_transmit_rate, maximum_transmit_rate, packet_loss_ratio
854         )
855         return result
856
857     @staticmethod
858     def perform_soak_search(
859             frame_size, traffic_profile, minimum_transmit_rate,
860             maximum_transmit_rate, plr_target=1e-7, tdpt=0.1,
861             initial_count=50, timeout=1800.0, trace_enabled=False,
862             traffic_directions=2):
863         """Setup initialized TG, perform soak search, return avg and stdev.
864
865         :param frame_size: Frame size identifier or value [B].
866         :param traffic_profile: Module name as a traffic profile identifier.
867             See resources/traffic_profiles/trex for implemented modules.
868         :param minimum_transmit_rate: Minimal uni-directional
869             target transmit rate [pps].
870         :param maximum_transmit_rate: Maximal uni-directional
871             target transmit rate [pps].
872         :param plr_target: Fraction of packets lost to achieve [1].
873         :param tdpt: Trial duration per trial.
874             The algorithm linearly increases trial duration with trial number,
875             this is the increment between succesive trials, in seconds.
876         :param initial_count: Offset to apply before the first trial.
877             For example initial_count=50 makes first trial to be 51*tdpt long.
878             This is needed because initial "search" phase of integrator
879             takes significant time even without any trial results.
880         :param timeout: The search will stop after this overall time [s].
881         :param trace_enabled: True if trace enabled else False.
882         :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
883             Default: 2
884         :type frame_size: str or int
885         :type traffic_profile: str
886         :type minimum_transmit_rate: float
887         :type maximum_transmit_rate: float
888         :type plr_target: float
889         :type initial_count: int
890         :type timeout: float
891         :type trace_enabled: bool
892         :type traffic_directions: int
893         :returns: Average and stdev of estimated aggregate rate giving PLR.
894         :rtype: 2-tuple of float
895         """
896         minimum_transmit_rate *= traffic_directions
897         maximum_transmit_rate *= traffic_directions
898         tg_instance = BuiltIn().get_library_instance(
899             u"resources.libraries.python.TrafficGenerator"
900         )
901         tg_instance.set_rate_provider_defaults(
902             frame_size, traffic_profile, traffic_directions=traffic_directions)
903         algorithm = PLRsearch(
904             measurer=tg_instance, trial_duration_per_trial=tdpt,
905             packet_loss_ratio_target=plr_target,
906             trial_number_offset=initial_count, timeout=timeout,
907             trace_enabled=trace_enabled
908         )
909         result = algorithm.search(minimum_transmit_rate, maximum_transmit_rate)
910         return result