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:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Performance testing traffic generator library."""
18 from robot.api import logger
19 from robot.libraries.BuiltIn import BuiltIn
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
34 __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"]
37 def check_subtype(node):
38 """Return supported subtype of given node, or raise an exception.
40 Currently only one subtype is supported,
41 but we want our code to be ready for other ones.
43 :param node: Topology node to check. Can be None.
44 :type node: dict or NoneType
45 :returns: Subtype detected.
47 :raises RuntimeError: If node is not supported, message explains how.
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"
58 return NodeSubTypeTG.TREX
59 raise RuntimeError(msg)
62 class TGDropRateSearchImpl(DropRateSearch):
63 """Drop Rate Search implementation."""
66 # super(TGDropRateSearchImpl, self).__init__()
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.
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.
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)
88 :raises NotImplementedError: If TG is not supported.
89 :raises RuntimeError: If TG is not specified.
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"
96 subtype = check_subtype(tg_instance.node)
97 if subtype == NodeSubTypeTG.TREX:
98 unit_rate = str(rate) + self.get_rate_type_str()
100 tg_instance.trex_stl_start_remote_exec(
101 self.get_duration(), unit_rate, frame_size, traffic_profile,
105 tg_instance.trex_stl_start_remote_exec(
106 self.get_duration(), unit_rate, frame_size, traffic_profile
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
113 f"comparing: {loss} < {loss_acceptance} {loss_acceptance_type}"
115 return float(loss) <= float(loss_acceptance)
118 def get_latency(self):
119 """Returns min/avg/max latency.
121 :returns: Latency stats.
124 tg_instance = BuiltIn().get_library_instance(
125 u"resources.libraries.python.TrafficGenerator"
127 return tg_instance.get_latency_int()
131 """Defines mode of T-Rex traffic generator."""
132 # Advanced stateful mode
138 # TODO: Pylint says too-many-instance-attributes.
139 class TrafficGenerator(AbstractMeasurer):
140 """Traffic Generator."""
142 # TODO: Remove "trex" from lines which could work with other TGs.
144 # Use one instance of TrafficGenerator for all tests in test suite
145 ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
150 # TG interface order mapping
151 self._ifaces_reordered = False
152 # Result holding fields, to be removed.
157 self._received = None
158 self._approximated_rate = None
159 self._approximated_duration = None
161 # Measurement input fields, needed for async stop result.
162 self._start_time = 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?
179 :returns: Traffic generator node.
185 """Return number of lost packets.
187 :returns: Number of lost packets.
193 """Return number of sent packets.
195 :returns: Number of sent packets.
200 def get_received(self):
201 """Return number of received packets.
203 :returns: Number of received packets.
206 return self._received
208 def get_latency_int(self):
209 """Return rounded min/avg/max latency.
211 :returns: Latency stats.
216 def get_approximated_rate(self):
217 """Return approximated rate computed as ratio of transmitted packets
218 over duration of trial.
220 :returns: Approximated rate.
223 return self._approximated_rate
225 def get_l7_data(self):
228 :returns: Number of received packets.
233 def check_mode(self, expected_mode):
236 :param expected_mode: Expected traffic generator mode.
237 :type expected_mode: object
238 :raises RuntimeError: In case of unexpected TG mode.
240 if self._mode == expected_mode:
243 f"{self._node[u'subtype']} not running in {expected_mode} mode!"
246 # TODO: pylint says disable=too-many-locals.
247 def initialize_traffic_generator(
248 self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
249 tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
250 tg_if2_dst_mac=None):
251 """TG initialization.
253 TODO: Document why do we need (and how do we use) _ifaces_reordered.
255 :param tg_node: Traffic generator node.
256 :param tg_if1: TG - name of first interface.
257 :param tg_if2: TG - name of second interface.
258 :param tg_if1_adj_node: TG if1 adjecent node.
259 :param tg_if1_adj_if: TG if1 adjecent interface.
260 :param tg_if2_adj_node: TG if2 adjecent node.
261 :param tg_if2_adj_if: TG if2 adjecent interface.
262 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
263 :param tg_if1_dst_mac: Interface 1 destination MAC address.
264 :param tg_if2_dst_mac: Interface 2 destination MAC address.
268 :type tg_if1_adj_node: dict
269 :type tg_if1_adj_if: str
270 :type tg_if2_adj_node: dict
271 :type tg_if2_adj_if: str
273 :type tg_if1_dst_mac: str
274 :type tg_if2_dst_mac: str
276 :raises RuntimeError: In case of issue during initialization.
278 subtype = check_subtype(tg_node)
279 if subtype == NodeSubTypeTG.TREX:
281 self._mode = TrexMode.ASTF if osi_layer == u"L7" else TrexMode.STL
284 if1[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if1)
285 if2[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if2)
286 if1[u"addr"] = Topology().get_interface_mac(self._node, tg_if1)
287 if2[u"addr"] = Topology().get_interface_mac(self._node, tg_if2)
289 if osi_layer == u"L2":
290 if1[u"adj_addr"] = if2[u"addr"]
291 if2[u"adj_addr"] = if1[u"addr"]
292 elif osi_layer in (u"L3", u"L7"):
293 if1[u"adj_addr"] = Topology().get_interface_mac(
294 tg_if1_adj_node, tg_if1_adj_if
296 if2[u"adj_addr"] = Topology().get_interface_mac(
297 tg_if2_adj_node, tg_if2_adj_if
300 raise ValueError(u"Unknown OSI layer!")
302 # in case of switched environment we can override MAC addresses
303 if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
304 if1[u"adj_addr"] = tg_if1_dst_mac
305 if2[u"adj_addr"] = tg_if2_dst_mac
307 if min(if1[u"pci"], if2[u"pci"]) != if1[u"pci"]:
309 self._ifaces_reordered = True
311 master_thread_id, latency_thread_id, socket, threads = \
312 CpuUtils.get_affinity_trex(
313 self._node, tg_if1, tg_if2,
314 tg_dtc=Constants.TREX_CORE_COUNT)
316 if osi_layer in (u"L2", u"L3", u"L7"):
319 f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
321 f" c: {len(threads)}\n"
322 f" limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
323 f" interfaces: [\"{if1[u'pci']}\",\"{if2[u'pci']}\"]\n"
325 f" - dest_mac: \'{if1[u'adj_addr']}\'\n"
326 f" src_mac: \'{if1[u'addr']}\'\n"
327 f" - dest_mac: \'{if2[u'adj_addr']}\'\n"
328 f" src_mac: \'{if2[u'addr']}\'\n"
330 f" master_thread_id: {master_thread_id}\n"
331 f" latency_thread_id: {latency_thread_id}\n"
333 f" - socket: {socket}\n"
334 f" threads: {threads}\n"
336 sudo=True, message=u"T-Rex config generation!"
339 raise ValueError(u"Unknown OSI layer!")
341 TrafficGenerator.startup_trex(
342 self._node, osi_layer, subtype=subtype
346 def startup_trex(tg_node, osi_layer, subtype=None):
347 """Startup sequence for the TRex traffic generator.
349 :param tg_node: Traffic generator node.
350 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
351 :param subtype: Traffic generator sub-type.
354 :type subtype: NodeSubTypeTG
355 :raises RuntimeError: If T-Rex startup failed.
356 :raises ValueError: If OSI layer is not supported.
359 subtype = check_subtype(tg_node)
360 if subtype == NodeSubTypeTG.TREX:
361 for _ in range(0, 3):
362 # Kill TRex only if it is already running.
363 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
365 tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
370 for port in tg_node[u"interfaces"].values():
371 if u'Mellanox' not in port.get(u'model'):
372 ports += f" {port.get(u'pci_address')}"
374 cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \
375 f"./dpdk_nic_bind.py -u {ports} || true\""
377 tg_node, cmd, sudo=True,
378 message=u"Unbind PCI ports from driver failed!"
382 cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
383 trex_cmd = OptionString([u"nohup", u"./t-rex-64"])
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"
394 exec_cmd_no_error(tg_node, cmd, sudo=True)
396 cmd = u"sh -c \"cat /tmp/trex.log\""
398 tg_node, cmd, sudo=True,
399 message=u"Get TRex logs failed!"
401 raise RuntimeError(u"Start TRex failed!")
403 # Test T-Rex API responsiveness.
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"
411 raise ValueError(u"Unknown OSI layer!")
414 tg_node, cmd, sudo=True,
415 message=u"T-Rex API is not responding!", retries=20
420 # After max retries TRex is still not responding to API critical
422 exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True)
423 raise RuntimeError(u"Start T-Rex failed after multiple retries!")
426 def is_trex_running(node):
427 """Check if T-Rex is running using pidof.
429 :param node: Traffic generator node.
431 :returns: True if T-Rex is running otherwise False.
434 ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True)
435 return bool(int(ret) == 0)
438 def teardown_traffic_generator(node):
441 :param node: Traffic generator node.
444 :raises RuntimeError: If node type is not a TG,
445 or if T-Rex teardown fails.
447 subtype = check_subtype(node)
448 if subtype == NodeSubTypeTG.TREX:
452 u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"",
454 message=u"T-Rex kill failed!"
457 def _parse_traffic_results(self, stdout):
458 """Parse stdout of scripts into fields of self.
460 Block of code to reuse, by sync start, or stop after async.
462 :param stdout: Text containing the standard output.
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""):
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")
559 def trex_astf_stop_remote_exec(self, node):
560 """Execute T-Rex ASTF script on remote node over ssh to stop running
563 Internal state is updated with measurement results.
565 :param node: T-Rex generator node.
567 :raises RuntimeError: If stop traffic script fails.
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(
579 message=u"T-Rex ASTF runtime error!"
581 self._parse_traffic_results(stdout)
583 def trex_stl_stop_remote_exec(self, node):
584 """Execute T-Rex STL script on remote node over ssh to stop running
587 Internal state is updated with measurement results.
589 :param node: T-Rex generator node.
591 :raises RuntimeError: If stop traffic script fails.
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(
603 message=u"T-Rex STL runtime error!"
605 self._parse_traffic_results(stdout)
607 def stop_traffic_on_tg(self):
608 """Stop all traffic on TG.
610 :returns: Structure containing the result of the measurement.
611 :rtype: ReceiveRateMeasurement
612 :raises ValueError: If TG traffic profile is not supported.
614 subtype = check_subtype(self._node)
615 if subtype == NodeSubTypeTG.TREX:
616 if u"trex-astf" in self.traffic_profile:
617 self.trex_astf_stop_remote_exec(self._node)
618 elif u"trex-stl" in self.traffic_profile:
619 self.trex_stl_stop_remote_exec(self._node)
621 raise ValueError(u"Unsupported T-Rex traffic profile!")
623 return self.get_measurement_result()
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,
629 """Execute T-Rex ASTF script on remote node over ssh to start running
632 In sync mode, measurement results are stored internally.
633 In async mode, initial data including xstats are stored internally.
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.
645 :param tx_port: Traffic generator transmit port for first flow.
647 :param rx_port: Traffic generator receive port for first flow.
649 :type duration: float
651 :type frame_size: str
652 :type traffic_profile: str
653 :type async_call: bool
655 :type warmup_time: float
656 :type traffic_directions: int
659 :raises RuntimeError: In case of T-Rex driver issue.
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)
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'"
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)
688 stdout, _ = exec_cmd_no_error(
689 self._node, command_line,
690 timeout=int(duration) + 600 if u"tcp" in self.traffic_profile
692 message=u"T-Rex ASTF runtime error!"
695 self.traffic_directions = traffic_directions
698 self._start_time = time.time()
699 self._rate = float(mult)
700 self._received = 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
729 logger.warn(u"Unsupported T-Rex ASTF traffic profile!")
731 for line in stdout.splitlines():
732 if f"Xstats snapshot {index}: " in line:
733 xstats[index] = line[19:]
737 self._xstats = tuple(xstats)
739 self._parse_traffic_results(stdout)
740 self._start_time = None
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,
747 """Execute T-Rex STL script on remote node over ssh to start running
750 In sync mode, measurement results are stored internally.
751 In async mode, initial data including xstats are stored internally.
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.
763 :param tx_port: Traffic generator transmit port for first flow.
765 :param rx_port: Traffic generator receive port for first flow.
767 :type duration: float
769 :type frame_size: str
770 :type traffic_profile: str
771 :type async_call: bool
773 :type warmup_time: float
774 :type traffic_directions: int
777 :raises RuntimeError: In case of T-Rex driver issue.
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)
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'"
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)
806 stdout, _ = exec_cmd_no_error(
807 self._node, command_line, timeout=int(duration) + 60,
808 message=u"T-Rex STL runtime error"
811 self.traffic_directions = traffic_directions
814 self._start_time = time.time()
815 self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
816 self._received = None
821 xstats = [None, None]
823 for line in stdout.splitlines():
824 if f"Xstats snapshot {index}: " in line:
825 xstats[index] = line[19:]
829 self._xstats = tuple(xstats)
831 self._parse_traffic_results(stdout)
832 self._start_time = None
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,
839 """Send traffic from all configured interfaces on TG.
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.
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.
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.
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.
867 :param tx_port: Traffic generator transmit port for first flow.
869 :param rx_port: Traffic generator receive port for first flow.
871 :type duration: float
873 :type frame_size: str
874 :type traffic_profile: str
875 :type warmup_time: float
876 :type async_call: bool
878 :type traffic_directions: int
881 :returns: TG results.
883 :raises ValueError: If TG traffic profile is not supported.
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,
895 elif u"trex-stl" in self.traffic_profile:
896 unit_rate_str = str(rate) + u"pps"
897 self.trex_stl_start_remote_exec(
898 duration, unit_rate_str, frame_size, self.traffic_profile,
899 async_call, latency, warmup_time, traffic_directions,
903 raise ValueError(u"Unsupported T-Rex traffic profile!")
907 def no_traffic_loss_occurred(self):
908 """Fail if loss occurred in traffic run.
911 :raises Exception: If loss occured.
913 if self._loss is None:
914 raise RuntimeError(u"The traffic generation has not been issued")
915 if self._loss != u"0":
916 raise RuntimeError(f"Traffic loss occurred: {self._loss}")
918 def fail_if_no_traffic_forwarded(self):
919 """Fail if no traffic forwarded.
922 :raises Exception: If no traffic forwarded.
924 if self._received is None:
925 raise RuntimeError(u"The traffic generation has not been issued")
926 if self._received == u"0":
927 raise RuntimeError(u"No traffic forwarded")
929 def partial_traffic_loss_accepted(
930 self, loss_acceptance, loss_acceptance_type):
931 """Fail if loss is higher then accepted in traffic run.
933 :param loss_acceptance: Permitted drop ratio or frames count.
934 :param loss_acceptance_type: Type of permitted loss.
935 :type loss_acceptance: float
936 :type loss_acceptance_type: LossAcceptanceType
938 :raises Exception: If loss is above acceptance criteria.
940 if self._loss is None:
941 raise Exception(u"The traffic generation has not been issued")
943 if loss_acceptance_type == u"percentage":
944 loss = (float(self._loss) / float(self._sent)) * 100
945 elif loss_acceptance_type == u"frames":
946 loss = float(self._loss)
948 raise Exception(u"Loss acceptance type not supported")
950 if loss > float(loss_acceptance):
952 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
955 def set_rate_provider_defaults(
956 self, frame_size, traffic_profile, warmup_time=0.0,
957 traffic_directions=2, negative_loss=True, latency=False):
958 """Store values accessed by measure().
960 :param frame_size: Frame size identifier or value [B].
961 :param traffic_profile: Module name as a traffic profile identifier.
962 See GPL/traffic_profiles/trex for implemented modules.
963 :param warmup_time: Traffic duration before measurement starts [s].
964 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
966 :param negative_loss: If false, negative loss is reported as zero loss.
967 :param latency: Whether to measure latency during the trial.
969 :type frame_size: str or int
970 :type traffic_profile: str
971 :type warmup_time: float
972 :type traffic_directions: int
973 :type negative_loss: bool
976 self.frame_size = frame_size
977 self.traffic_profile = str(traffic_profile)
978 self.warmup_time = float(warmup_time)
979 self.traffic_directions = traffic_directions
980 self.negative_loss = negative_loss
981 self.use_latency = latency
983 def get_measurement_result(self, duration=None, transmit_rate=None):
984 """Return the result of last measurement as ReceiveRateMeasurement.
986 Separate function, as measurements can end either by time
987 or by explicit call, this is the common block at the end.
989 TODO: Fail on running or already reported measurement.
991 :param duration: Measurement duration [s] if known beforehand.
992 For explicitly stopped measurement it is estimated.
993 :param transmit_rate: Target aggregate transmit rate [pps].
994 If not given, computed assuming it was bidirectional.
995 :type duration: float or NoneType
996 :type transmit_rate: float or NoneType
997 :returns: Structure containing the result of the measurement.
998 :rtype: ReceiveRateMeasurement
1000 if duration is None:
1001 duration = time.time() - self._start_time
1002 self._start_time = None
1003 if transmit_rate is None:
1004 transmit_rate = self._rate * self.traffic_directions
1005 transmit_count = int(self.get_sent())
1006 loss_count = int(self.get_loss())
1007 if loss_count < 0 and not self.negative_loss:
1009 measurement = ReceiveRateMeasurement(
1010 duration, transmit_rate, transmit_count, loss_count
1012 measurement.latency = self.get_latency_int()
1015 def measure(self, duration, transmit_rate):
1016 """Run trial measurement, parse and return aggregate results.
1018 Aggregate means sum over traffic directions.
1020 :param duration: Trial duration [s].
1021 :param transmit_rate: Target aggregate transmit rate [pps] / Connections
1022 per second (CPS) for UDP/TCP flows.
1023 :type duration: float
1024 :type transmit_rate: float
1025 :returns: Structure containing the result of the measurement.
1026 :rtype: ReceiveRateMeasurement
1027 :raises RuntimeError: If TG is not set or if node is not TG
1028 or if subtype is not specified.
1029 :raises NotImplementedError: If TG is not supported.
1031 duration = float(duration)
1032 # TG needs target Tr per stream, but reports aggregate Tx and Dx.
1033 unit_rate_int = transmit_rate / float(self.traffic_directions)
1034 self.send_traffic_on_tg(
1038 self.traffic_profile,
1039 warmup_time=self.warmup_time,
1040 latency=self.use_latency,
1041 traffic_directions=self.traffic_directions
1043 return self.get_measurement_result(duration, transmit_rate)
1046 class OptimizedSearch:
1047 """Class to be imported as Robot Library, containing search keywords.
1049 Aside of setting up measurer and forwarding arguments,
1050 the main business is to translate min/max rate from unidir to aggregate.
1054 def perform_optimized_ndrpdr_search(
1055 frame_size, traffic_profile, minimum_transmit_rate,
1056 maximum_transmit_rate, packet_loss_ratio=0.005,
1057 final_relative_width=0.005, final_trial_duration=30.0,
1058 initial_trial_duration=1.0, number_of_intermediate_phases=2,
1059 timeout=720.0, doublings=1, traffic_directions=2, latency=False):
1060 """Setup initialized TG, perform optimized search, return intervals.
1062 :param frame_size: Frame size identifier or value [B].
1063 :param traffic_profile: Module name as a traffic profile identifier.
1064 See GPL/traffic_profiles/trex for implemented modules.
1065 :param minimum_transmit_rate: Minimal uni-directional
1066 target transmit rate [pps].
1067 :param maximum_transmit_rate: Maximal uni-directional
1068 target transmit rate [pps].
1069 :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
1070 :param final_relative_width: Final lower bound transmit rate
1071 cannot be more distant that this multiple of upper bound [1].
1072 :param final_trial_duration: Trial duration for the final phase [s].
1073 :param initial_trial_duration: Trial duration for the initial phase
1074 and also for the first intermediate phase [s].
1075 :param number_of_intermediate_phases: Number of intermediate phases
1076 to perform before the final phase [1].
1077 :param timeout: The search will fail itself when not finished
1078 before this overall time [s].
1079 :param doublings: How many doublings to do in external search step.
1080 Default 1 is suitable for fairly stable tests,
1081 less stable tests might get better overal duration with 2 or more.
1082 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1084 :param latency: Whether to measure latency during the trial.
1086 :type frame_size: str or int
1087 :type traffic_profile: str
1088 :type minimum_transmit_rate: float
1089 :type maximum_transmit_rate: float
1090 :type packet_loss_ratio: float
1091 :type final_relative_width: float
1092 :type final_trial_duration: float
1093 :type initial_trial_duration: float
1094 :type number_of_intermediate_phases: int
1095 :type timeout: float
1096 :type doublings: int
1097 :type traffic_directions: int
1099 :returns: Structure containing narrowed down NDR and PDR intervals
1100 and their measurements.
1101 :rtype: NdrPdrResult
1102 :raises RuntimeError: If total duration is larger than timeout.
1104 minimum_transmit_rate *= traffic_directions
1105 maximum_transmit_rate *= traffic_directions
1106 # we need instance of TrafficGenerator instantiated by Robot Framework
1107 # to be able to use trex_stl-*()
1108 tg_instance = BuiltIn().get_library_instance(
1109 u"resources.libraries.python.TrafficGenerator"
1111 tg_instance.set_rate_provider_defaults(
1114 traffic_directions=traffic_directions,
1117 algorithm = MultipleLossRatioSearch(
1118 measurer=tg_instance, final_trial_duration=final_trial_duration,
1119 final_relative_width=final_relative_width,
1120 number_of_intermediate_phases=number_of_intermediate_phases,
1121 initial_trial_duration=initial_trial_duration, timeout=timeout,
1124 result = algorithm.narrow_down_ndr_and_pdr(
1125 minimum_transmit_rate, maximum_transmit_rate, packet_loss_ratio
1130 def perform_soak_search(
1131 frame_size, traffic_profile, minimum_transmit_rate,
1132 maximum_transmit_rate, plr_target=1e-7, tdpt=0.1,
1133 initial_count=50, timeout=1800.0, trace_enabled=False,
1134 traffic_directions=2, latency=False):
1135 """Setup initialized TG, perform soak search, return avg and stdev.
1137 :param frame_size: Frame size identifier or value [B].
1138 :param traffic_profile: Module name as a traffic profile identifier.
1139 See GPL/traffic_profiles/trex for implemented modules.
1140 :param minimum_transmit_rate: Minimal uni-directional
1141 target transmit rate [pps].
1142 :param maximum_transmit_rate: Maximal uni-directional
1143 target transmit rate [pps].
1144 :param plr_target: Fraction of packets lost to achieve [1].
1145 :param tdpt: Trial duration per trial.
1146 The algorithm linearly increases trial duration with trial number,
1147 this is the increment between succesive trials, in seconds.
1148 :param initial_count: Offset to apply before the first trial.
1149 For example initial_count=50 makes first trial to be 51*tdpt long.
1150 This is needed because initial "search" phase of integrator
1151 takes significant time even without any trial results.
1152 :param timeout: The search will stop after this overall time [s].
1153 :param trace_enabled: True if trace enabled else False.
1154 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1156 :param latency: Whether to measure latency during the trial.
1158 :type frame_size: str or int
1159 :type traffic_profile: str
1160 :type minimum_transmit_rate: float
1161 :type maximum_transmit_rate: float
1162 :type plr_target: float
1163 :type initial_count: int
1164 :type timeout: float
1165 :type trace_enabled: bool
1166 :type traffic_directions: int
1168 :returns: Average and stdev of estimated aggregate rate giving PLR.
1169 :rtype: 2-tuple of float
1171 minimum_transmit_rate *= traffic_directions
1172 maximum_transmit_rate *= traffic_directions
1173 tg_instance = BuiltIn().get_library_instance(
1174 u"resources.libraries.python.TrafficGenerator"
1176 tg_instance.set_rate_provider_defaults(
1179 traffic_directions=traffic_directions,
1180 negative_loss=False,
1183 algorithm = PLRsearch(
1184 measurer=tg_instance, trial_duration_per_trial=tdpt,
1185 packet_loss_ratio_target=plr_target,
1186 trial_number_offset=initial_count, timeout=timeout,
1187 trace_enabled=trace_enabled
1189 result = algorithm.search(minimum_transmit_rate, maximum_transmit_rate)