1 # Copyright (c) 2023 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."""
19 from robot.api import logger
20 from robot.libraries.BuiltIn import BuiltIn
22 from .Constants import Constants
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 from .TRexConfigGenerator import TrexInitConfig
34 from .DUTSetup import DUTSetup as DS
36 __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"]
39 def check_subtype(node):
40 """Return supported subtype of given node, or raise an exception.
42 Currently only one subtype is supported,
43 but we want our code to be ready for other ones.
45 :param node: Topology node to check. Can be None.
46 :type node: dict or NoneType
47 :returns: Subtype detected.
49 :raises RuntimeError: If node is not supported, message explains how.
51 if node.get(u"type") is None:
52 msg = u"Node type is not defined"
53 elif node[u"type"] != NodeType.TG:
54 msg = f"Node type is {node[u'type']!r}, not a TG"
55 elif node.get(u"subtype") is None:
56 msg = u"TG subtype is not defined"
57 elif node[u"subtype"] != NodeSubTypeTG.TREX:
58 msg = f"TG subtype {node[u'subtype']!r} is not supported"
60 return NodeSubTypeTG.TREX
61 raise RuntimeError(msg)
64 class TGDropRateSearchImpl(DropRateSearch):
65 """Drop Rate Search implementation."""
68 # super(TGDropRateSearchImpl, self).__init__()
71 self, rate, frame_size, loss_acceptance, loss_acceptance_type,
73 """Runs the traffic and evaluate the measured results.
75 :param rate: Offered traffic load.
76 :param frame_size: Size of frame.
77 :param loss_acceptance: Permitted drop ratio or frames count.
78 :param loss_acceptance_type: Type of permitted loss.
79 :param traffic_profile: Module name as a traffic profile identifier.
80 See GPL/traffic_profiles/trex for implemented modules.
83 :type loss_acceptance: float
84 :type loss_acceptance_type: LossAcceptanceType
85 :type traffic_profile: str
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()
99 tg_instance.trex_stl_start_remote_exec(
100 self.get_duration(), unit_rate, frame_size, traffic_profile
102 loss = tg_instance.get_loss()
103 sent = tg_instance.get_sent()
104 if self.loss_acceptance_type_is_percentage():
105 loss = (float(loss) / float(sent)) * 100
107 f"comparing: {loss} < {loss_acceptance} {loss_acceptance_type}"
109 return float(loss) <= float(loss_acceptance)
112 def get_latency(self):
113 """Returns min/avg/max latency.
115 :returns: Latency stats.
118 tg_instance = BuiltIn().get_library_instance(
119 u"resources.libraries.python.TrafficGenerator"
121 return tg_instance.get_latency_int()
125 """Defines mode of T-Rex traffic generator."""
126 # Advanced stateful mode
132 class TrafficGenerator(AbstractMeasurer):
133 """Traffic Generator."""
135 # Use one instance of TrafficGenerator for all tests in test suite
136 ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
141 # TG interface order mapping
142 self._ifaces_reordered = False
143 # Result holding fields, to be removed.
148 self._received = None
149 self._approximated_rate = None
150 self._approximated_duration = None
152 # Measurement input fields, needed for async stop result.
153 self._start_time = None
154 self._stop_time = None
156 self._target_duration = None
157 self._duration = None
158 # Other input parameters, not knowable from measure() signature.
159 self.frame_size = None
160 self.traffic_profile = None
161 self.traffic_directions = None
162 self.negative_loss = None
163 self.use_latency = None
166 self.transaction_scale = None
167 self.transaction_duration = None
168 self.sleep_till_duration = None
169 self.transaction_type = None
170 self.duration_limit = None
171 self.ramp_up_start = None
172 self.ramp_up_stop = None
173 self.ramp_up_rate = None
174 self.ramp_up_duration = None
175 self.state_timeout = None
176 # Transient data needed for async measurements.
177 self._xstats = (None, None)
183 :returns: Traffic generator node.
189 """Return number of lost packets.
191 :returns: Number of lost packets.
197 """Return number of sent packets.
199 :returns: Number of sent packets.
204 def get_received(self):
205 """Return number of received packets.
207 :returns: Number of received packets.
210 return self._received
212 def get_latency_int(self):
213 """Return rounded min/avg/max latency.
215 :returns: Latency stats.
220 def get_approximated_rate(self):
221 """Return approximated rate computed as ratio of transmitted packets
222 over duration of trial.
224 :returns: Approximated rate.
227 return self._approximated_rate
229 def get_l7_data(self):
232 :returns: Number of received packets.
237 def check_mode(self, expected_mode):
240 :param expected_mode: Expected traffic generator mode.
241 :type expected_mode: object
242 :raises RuntimeError: In case of unexpected TG mode.
244 if self._mode == expected_mode:
247 f"{self._node[u'subtype']} not running in {expected_mode} mode!"
251 def get_tg_type(tg_node):
252 """Log and return the installed traffic generator type.
254 :param tg_node: Node from topology file.
256 :returns: Traffic generator type string.
258 :raises RuntimeError: If command returns nonzero return code.
260 return str(check_subtype(tg_node))
263 def get_tg_version(tg_node):
264 """Log and return the installed traffic generator version.
266 :param tg_node: Node from topology file.
268 :returns: Traffic generator version string.
270 :raises RuntimeError: If command returns nonzero return code.
272 subtype = check_subtype(tg_node)
273 if subtype == NodeSubTypeTG.TREX:
274 command = f"cat {Constants.TREX_INSTALL_DIR}/VERSION"
275 message = u"Get T-Rex version failed!"
276 stdout, _ = exec_cmd_no_error(tg_node, command, message=message)
277 return stdout.strip()
281 def initialize_traffic_generator(
282 self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
283 tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
284 tg_if2_dst_mac=None):
285 """TG initialization.
287 :param tg_node: Traffic generator node.
288 :param tg_if1: TG - name of first interface.
289 :param tg_if2: TG - name of second interface.
290 :param tg_if1_adj_node: TG if1 adjecent node.
291 :param tg_if1_adj_if: TG if1 adjecent interface.
292 :param tg_if2_adj_node: TG if2 adjecent node.
293 :param tg_if2_adj_if: TG if2 adjecent interface.
294 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
295 :param tg_if1_dst_mac: Interface 1 destination MAC address.
296 :param tg_if2_dst_mac: Interface 2 destination MAC address.
300 :type tg_if1_adj_node: dict
301 :type tg_if1_adj_if: str
302 :type tg_if2_adj_node: dict
303 :type tg_if2_adj_if: str
305 :type tg_if1_dst_mac: str
306 :type tg_if2_dst_mac: str
308 :raises RuntimeError: In case of issue during initialization.
310 subtype = check_subtype(tg_node)
311 if subtype == NodeSubTypeTG.TREX:
313 self._mode = TrexMode.ASTF if osi_layer == "L7" else TrexMode.STL
315 if osi_layer == "L2":
316 tg_if1_adj_addr = Topology().get_interface_mac(tg_node, tg_if2)
317 tg_if2_adj_addr = Topology().get_interface_mac(tg_node, tg_if1)
318 elif osi_layer in ("L3", "L7"):
319 tg_if1_adj_addr = Topology().get_interface_mac(
320 tg_if1_adj_node, tg_if1_adj_if
322 tg_if2_adj_addr = Topology().get_interface_mac(
323 tg_if2_adj_node, tg_if2_adj_if
326 raise ValueError("Unknown OSI layer!")
329 tg_topology.append(dict(interface=tg_if1, dst_mac=tg_if1_adj_addr))
330 tg_topology.append(dict(interface=tg_if2, dst_mac=tg_if2_adj_addr))
331 if1_pci = Topology().get_interface_pci_addr(self._node, tg_if1)
332 if2_pci = Topology().get_interface_pci_addr(self._node, tg_if2)
333 if min(if1_pci, if2_pci) != if1_pci:
334 self._ifaces_reordered = True
335 tg_topology.reverse()
337 TrexInitConfig.init_trex_startup_configuration(tg_node, tg_topology)
338 TrafficGenerator.startup_trex(tg_node, osi_layer, subtype=subtype)
341 def startup_trex(tg_node, osi_layer, subtype=None):
342 """Startup sequence for the TRex traffic generator.
344 :param tg_node: Traffic generator node.
345 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
346 :param subtype: Traffic generator sub-type.
349 :type subtype: NodeSubTypeTG
350 :raises RuntimeError: If T-Rex startup failed.
351 :raises ValueError: If OSI layer is not supported.
354 subtype = check_subtype(tg_node)
355 if subtype == NodeSubTypeTG.TREX:
356 for _ in range(0, 3):
357 # Kill TRex only if it is already running.
358 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
360 tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
363 # Prepare interfaces for TRex.
364 tg_port_drv = Constants.TREX_PORT_DRIVER
366 for port in tg_node[u"interfaces"].values():
367 if u"Mellanox" in port.get(u"model"):
368 mlx_driver = port.get(u"driver")
369 pci_addr = port.get(u'pci_address')
370 cur_driver = DS.get_pci_dev_driver(tg_node, pci_addr)
371 if cur_driver == mlx_driver:
374 DS.pci_driver_bind(tg_node, pci_addr, mlx_driver)
376 DS.pci_driver_unbind(tg_node, pci_addr)
377 DS.pci_driver_bind(tg_node, pci_addr, mlx_driver)
379 pci_addr = port.get(u'pci_address')
380 cur_driver = DS.get_pci_dev_driver(tg_node, pci_addr)
382 DS.pci_driver_unbind(tg_node, pci_addr)
383 DS.pci_driver_bind(tg_node, pci_addr, tg_port_drv)
386 cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
387 trex_cmd = OptionString([u"nohup", u"./t-rex-64"])
389 trex_cmd.add(u"--prefix $(hostname)")
390 trex_cmd.add(u"--hdrh")
391 trex_cmd.add(u"--no-scapy-server")
392 trex_cmd.add_if(u"--astf", osi_layer == u"L7")
393 # OptionString does not create double space if extra is empty.
394 trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}")
395 inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &"
396 cmd = f"sh -c \"{inner_command}\" > /dev/null"
398 exec_cmd_no_error(tg_node, cmd, sudo=True)
400 cmd = u"sh -c \"cat /tmp/trex.log\""
402 tg_node, cmd, sudo=True,
403 message=u"Get TRex logs failed!"
405 raise RuntimeError(u"Start TRex failed!")
407 # Test T-Rex API responsiveness.
408 cmd = f"python3 {Constants.REMOTE_FW_DIR}/GPL/tools/trex/"
409 if osi_layer in (u"L2", u"L3"):
410 cmd += u"trex_stl_assert.py"
411 elif osi_layer == u"L7":
412 cmd += u"trex_astf_assert.py"
414 raise ValueError(u"Unknown OSI layer!")
417 tg_node, cmd, sudo=True,
418 message=u"T-Rex API is not responding!", retries=20
423 # After max retries TRex is still not responding to API critical
425 exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True)
426 raise RuntimeError(u"Start T-Rex failed after multiple retries!")
429 def is_trex_running(node):
430 """Check if T-Rex is running using pidof.
432 :param node: Traffic generator node.
434 :returns: True if T-Rex is running otherwise False.
437 ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True)
438 return bool(int(ret) == 0)
441 def teardown_traffic_generator(node):
444 :param node: Traffic generator node.
447 :raises RuntimeError: If node type is not a TG,
448 or if T-Rex teardown fails.
450 subtype = check_subtype(node)
451 if subtype == NodeSubTypeTG.TREX:
455 u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"",
457 message=u"T-Rex kill failed!"
460 def trex_astf_stop_remote_exec(self, node):
461 """Execute T-Rex ASTF script on remote node over ssh to stop running
464 Internal state is updated with measurement results.
466 :param node: T-Rex generator node.
468 :raises RuntimeError: If stop traffic script fails.
470 command_line = OptionString().add(u"python3")
471 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
472 command_line.add(f"'{dirname}/trex_astf_stop.py'")
473 command_line.change_prefix(u"--")
474 for index, value in enumerate(self._xstats):
475 if value is not None:
476 value = value.replace(u"'", u"\"")
477 command_line.add_equals(f"xstat{index}", f"'{value}'")
478 stdout, _ = exec_cmd_no_error(
480 message=u"T-Rex ASTF runtime error!"
482 self._parse_traffic_results(stdout)
484 def trex_stl_stop_remote_exec(self, node):
485 """Execute T-Rex STL script on remote node over ssh to stop running
488 Internal state is updated with measurement results.
490 :param node: T-Rex generator node.
492 :raises RuntimeError: If stop traffic script fails.
494 command_line = OptionString().add(u"python3")
495 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
496 command_line.add(f"'{dirname}/trex_stl_stop.py'")
497 command_line.change_prefix(u"--")
498 for index, value in enumerate(self._xstats):
499 if value is not None:
500 value = value.replace(u"'", u"\"")
501 command_line.add_equals(f"xstat{index}", f"'{value}'")
502 stdout, _ = exec_cmd_no_error(
504 message=u"T-Rex STL runtime error!"
506 self._parse_traffic_results(stdout)
508 def stop_traffic_on_tg(self):
509 """Stop all traffic on TG.
511 :returns: Structure containing the result of the measurement.
512 :rtype: ReceiveRateMeasurement
513 :raises ValueError: If TG traffic profile is not supported.
515 subtype = check_subtype(self._node)
516 if subtype != NodeSubTypeTG.TREX:
517 raise ValueError(f"Unsupported TG subtype: {subtype!r}")
518 if u"trex-astf" in self.traffic_profile:
519 self.trex_astf_stop_remote_exec(self._node)
520 elif u"trex-stl" in self.traffic_profile:
521 self.trex_stl_stop_remote_exec(self._node)
523 raise ValueError(u"Unsupported T-Rex traffic profile!")
524 self._stop_time = time.monotonic()
526 return self._get_measurement_result()
528 def _compute_duration(self, duration, multiplier):
529 """Compute duration for profile driver.
531 The final result is influenced by transaction scale and duration limit.
532 It is assumed a higher level function has already set those to self.
533 The duration argument is the target value from search point of view,
534 before the overrides are applied here.
536 Minus one (signalling async traffic start) is kept.
538 Completeness flag is also included. Duration limited or async trials
539 are not considered complete for ramp-up purposes.
541 :param duration: Time expressed in seconds for how long to send traffic.
542 :param multiplier: Traffic rate in transactions per second.
543 :type duration: float
544 :type multiplier: float
545 :returns: New duration and whether it was a complete ramp-up candidate.
550 return duration, False
551 computed_duration = duration
552 if self.transaction_scale:
553 computed_duration = self.transaction_scale / multiplier
554 # Log the computed duration,
555 # so we can compare with what telemetry suggests
556 # the real duration was.
557 logger.debug(f"Expected duration {computed_duration}")
558 if not self.duration_limit:
559 return computed_duration, True
560 limited_duration = min(computed_duration, self.duration_limit)
561 return limited_duration, (limited_duration == computed_duration)
563 def trex_astf_start_remote_exec(
564 self, duration, multiplier, async_call=False):
565 """Execute T-Rex ASTF script on remote node over ssh to start running
568 In sync mode, measurement results are stored internally.
569 In async mode, initial data including xstats are stored internally.
571 This method contains the logic to compute duration as maximum time
572 if transaction_scale is nonzero.
573 The transaction_scale argument defines (limits) how many transactions
574 will be started in total. As that amount of transaction can take
575 considerable time (sometimes due to explicit delays in the profile),
576 the real time a trial needs to finish is computed here. For now,
577 in that case the duration argument is ignored, assuming it comes
578 from ASTF-unaware search algorithm. The overall time a single
579 transaction needs is given in parameter transaction_duration,
580 it includes both explicit delays and implicit time it takes
581 to transfer data (or whatever the transaction does).
583 Currently it is observed TRex does not start the ASTF traffic
584 immediately, an ad-hoc constant is added to the computed duration
585 to compensate for that.
587 If transaction_scale is zero, duration is not recomputed.
588 It is assumed the subsequent result parsing gets the real duration
589 if the traffic stops sooner for any reason.
591 Currently, it is assumed traffic profile defines a single transaction.
592 To avoid heavy logic here, the input rate is expected to be in
593 transactions per second, as that directly translates to TRex multiplier,
594 (assuming the profile does not override the default cps value of one).
596 :param duration: Time expressed in seconds for how long to send traffic.
597 :param multiplier: Traffic rate in transactions per second.
598 :param async_call: If enabled then don't wait for all incoming traffic.
599 :type duration: float
600 :type multiplier: int
601 :type async_call: bool
602 :raises RuntimeError: In case of T-Rex driver issue.
604 self.check_mode(TrexMode.ASTF)
605 p_0, p_1 = (1, 0) if self._ifaces_reordered else (0, 1)
606 if not isinstance(duration, (float, int)):
607 duration = float(duration)
609 computed_duration, _ = self._compute_duration(duration, multiplier)
611 command_line = OptionString().add(u"python3")
612 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
613 command_line.add(f"'{dirname}/trex_astf_profile.py'")
614 command_line.change_prefix(u"--")
615 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
616 command_line.add_with_value(
617 u"profile", f"'{dirname}/{self.traffic_profile}.py'"
619 command_line.add_with_value(u"duration", f"{computed_duration!r}")
620 command_line.add_with_value(u"frame_size", self.frame_size)
621 command_line.add_with_value(
622 u"n_data_frames", Constants.ASTF_N_DATA_FRAMES
624 command_line.add_with_value(u"multiplier", multiplier)
625 command_line.add_with_value(u"port_0", p_0)
626 command_line.add_with_value(u"port_1", p_1)
627 command_line.add_with_value(
628 u"traffic_directions", self.traffic_directions
630 command_line.add_if(u"async_start", async_call)
631 command_line.add_if(u"latency", self.use_latency)
632 command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
633 command_line.add_with_value(
634 u"delay", Constants.PERF_TRIAL_ASTF_DELAY
637 self._start_time = time.monotonic()
638 self._rate = multiplier
639 stdout, _ = exec_cmd_no_error(
640 self._node, command_line, timeout=computed_duration + 10.0,
641 message=u"T-Rex ASTF runtime error!"
646 self._target_duration = None
647 self._duration = None
648 self._received = None
652 xstats = [None, None]
653 self._l7_data = dict()
654 self._l7_data[u"client"] = dict()
655 self._l7_data[u"client"][u"active_flows"] = None
656 self._l7_data[u"client"][u"established_flows"] = None
657 self._l7_data[u"client"][u"traffic_duration"] = None
658 self._l7_data[u"server"] = dict()
659 self._l7_data[u"server"][u"active_flows"] = None
660 self._l7_data[u"server"][u"established_flows"] = None
661 self._l7_data[u"server"][u"traffic_duration"] = None
662 if u"udp" in self.traffic_profile:
663 self._l7_data[u"client"][u"udp"] = dict()
664 self._l7_data[u"client"][u"udp"][u"connects"] = None
665 self._l7_data[u"client"][u"udp"][u"closed_flows"] = None
666 self._l7_data[u"client"][u"udp"][u"err_cwf"] = None
667 self._l7_data[u"server"][u"udp"] = dict()
668 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = None
669 self._l7_data[u"server"][u"udp"][u"closed_flows"] = None
670 elif u"tcp" in self.traffic_profile:
671 self._l7_data[u"client"][u"tcp"] = dict()
672 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = None
673 self._l7_data[u"client"][u"tcp"][u"connects"] = None
674 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = None
675 self._l7_data[u"client"][u"tcp"][u"connattempt"] = None
676 self._l7_data[u"server"][u"tcp"] = dict()
677 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = None
678 self._l7_data[u"server"][u"tcp"][u"connects"] = None
679 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = None
681 logger.warn(u"Unsupported T-Rex ASTF traffic profile!")
683 for line in stdout.splitlines():
684 if f"Xstats snapshot {index}: " in line:
685 xstats[index] = line[19:]
689 self._xstats = tuple(xstats)
691 self._target_duration = duration
692 self._duration = computed_duration
693 self._parse_traffic_results(stdout)
695 def trex_stl_start_remote_exec(self, duration, rate, async_call=False):
696 """Execute T-Rex STL script on remote node over ssh to start running
699 In sync mode, measurement results are stored internally.
700 In async mode, initial data including xstats are stored internally.
702 Mode-unaware code (e.g. in search algorithms) works with transactions.
703 To keep the logic simple, multiplier is set to that value.
704 As bidirectional traffic profiles send packets in both directions,
705 they are treated as transactions with two packets (one per direction).
707 :param duration: Time expressed in seconds for how long to send traffic.
708 :param rate: Traffic rate in transactions per second.
709 :param async_call: If enabled then don't wait for all incoming traffic.
710 :type duration: float
712 :type async_call: bool
713 :raises RuntimeError: In case of T-Rex driver issue.
715 self.check_mode(TrexMode.STL)
716 p_0, p_1 = (1, 0) if self._ifaces_reordered else (0, 1)
717 if not isinstance(duration, (float, int)):
718 duration = float(duration)
720 duration, _ = self._compute_duration(duration=duration, multiplier=rate)
722 command_line = OptionString().add(u"python3")
723 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
724 command_line.add(f"'{dirname}/trex_stl_profile.py'")
725 command_line.change_prefix(u"--")
726 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
727 command_line.add_with_value(
728 u"profile", f"'{dirname}/{self.traffic_profile}.py'"
730 command_line.add_with_value(u"duration", f"{duration!r}")
731 command_line.add_with_value(u"frame_size", self.frame_size)
732 command_line.add_with_value(u"rate", f"{rate!r}")
733 command_line.add_with_value(u"port_0", p_0)
734 command_line.add_with_value(u"port_1", p_1)
735 command_line.add_with_value(
736 u"traffic_directions", self.traffic_directions
738 command_line.add_if(u"async_start", async_call)
739 command_line.add_if(u"latency", self.use_latency)
740 command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
741 command_line.add_with_value(u"delay", Constants.PERF_TRIAL_STL_DELAY)
743 self._start_time = time.monotonic()
744 self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
745 stdout, _ = exec_cmd_no_error(
746 self._node, command_line, timeout=int(duration) + 60,
747 message=u"T-Rex STL runtime error"
752 self._target_duration = None
753 self._duration = None
754 self._received = None
759 xstats = [None, None]
761 for line in stdout.splitlines():
762 if f"Xstats snapshot {index}: " in line:
763 xstats[index] = line[19:]
767 self._xstats = tuple(xstats)
769 self._target_duration = duration
770 self._duration = duration
771 self._parse_traffic_results(stdout)
773 def send_traffic_on_tg(
781 traffic_directions=2,
782 transaction_duration=0.0,
784 transaction_type=u"packet",
788 ramp_up_duration=None,
792 """Send traffic from all configured interfaces on TG.
794 In async mode, xstats is stored internally,
795 to enable getting correct result when stopping the traffic.
796 In both modes, stdout is returned,
797 but _parse_traffic_results only works in sync output.
799 Note that traffic generator uses DPDK driver which might
800 reorder port numbers based on wiring and PCI numbering.
801 This method handles that, so argument values are invariant,
802 but you can see swapped valued in debug logs.
804 When transaction_scale is specified, the duration value is ignored
805 and the needed time is computed. For cases where this results in
806 to too long measurement (e.g. teardown trial with small rate),
807 duration_limit is applied (of non-zero), so the trial is stopped sooner.
809 Bidirectional STL profiles are treated as transactions with two packets.
811 The return value is None for async.
813 :param duration: Duration of test traffic generation in seconds.
814 :param rate: Traffic rate in transactions per second.
815 :param frame_size: Frame size (L2) in Bytes.
816 :param traffic_profile: Module name as a traffic profile identifier.
817 See GPL/traffic_profiles/trex for implemented modules.
818 :param async_call: Async mode.
819 :param ppta: Packets per transaction, aggregated over directions.
820 Needed for udp_pps which does not have a good transaction counter,
821 so we need to compute expected number of packets.
823 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
825 :param transaction_duration: Total expected time to close transaction.
826 :param transaction_scale: Number of transactions to perform.
827 0 (default) means unlimited.
828 :param transaction_type: An identifier specifying which counters
829 and formulas to use when computing attempted and failed
830 transactions. Default: "packet".
831 :param duration_limit: Zero or maximum limit for computed (or given)
833 :param use_latency: Whether to measure latency during the trial.
835 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
836 :param ramp_up_duration: Duration of ramp-up trials [s].
837 :param state_timeout: Time of life of DUT state [s].
838 :param ramp_up_only: If true, do not perform main trial measurement.
839 :type duration: float
841 :type frame_size: str
842 :type traffic_profile: str
843 :type async_call: bool
845 :type traffic_directions: int
846 :type transaction_duration: float
847 :type transaction_scale: int
848 :type transaction_type: str
849 :type duration_limit: float
850 :type use_latency: bool
851 :type ramp_up_rate: float
852 :type ramp_up_duration: float
853 :type state_timeout: float
854 :type ramp_up_only: bool
855 :returns: TG results.
856 :rtype: ReceiveRateMeasurement or None
857 :raises ValueError: If TG traffic profile is not supported.
859 self.set_rate_provider_defaults(
860 frame_size=frame_size,
861 traffic_profile=traffic_profile,
863 traffic_directions=traffic_directions,
864 transaction_duration=transaction_duration,
865 transaction_scale=transaction_scale,
866 transaction_type=transaction_type,
867 duration_limit=duration_limit,
868 use_latency=use_latency,
869 ramp_up_rate=ramp_up_rate,
870 ramp_up_duration=ramp_up_duration,
871 state_timeout=state_timeout,
873 return self._send_traffic_on_tg_with_ramp_up(
876 async_call=async_call,
877 ramp_up_only=ramp_up_only,
880 def _send_traffic_on_tg_internal(
881 self, duration, rate, async_call=False):
882 """Send traffic from all configured interfaces on TG.
884 This is an internal function, it assumes set_rate_provider_defaults
885 has been called to remember most values.
886 The reason why need to remember various values is that
887 the traffic can be asynchronous, and parsing needs those values.
888 The reason why this is is a separate function from the one
889 which calls set_rate_provider_defaults is that some search algorithms
890 need to specify their own values, and we do not want the measure call
891 to overwrite them with defaults.
893 This function is used both for automated ramp-up trials
894 and for explicitly called trials.
896 :param duration: Duration of test traffic generation in seconds.
897 :param rate: Traffic rate in transactions per second.
898 :param async_call: Async mode.
899 :type duration: float
901 :type async_call: bool
902 :returns: TG results.
903 :rtype: ReceiveRateMeasurement or None
904 :raises ValueError: If TG traffic profile is not supported.
906 subtype = check_subtype(self._node)
907 if subtype == NodeSubTypeTG.TREX:
908 if u"trex-astf" in self.traffic_profile:
909 self.trex_astf_start_remote_exec(
910 duration, float(rate), async_call
912 elif u"trex-stl" in self.traffic_profile:
913 unit_rate_str = str(rate) + u"pps"
914 self.trex_stl_start_remote_exec(
915 duration, unit_rate_str, async_call
918 raise ValueError(u"Unsupported T-Rex traffic profile!")
920 return None if async_call else self._get_measurement_result()
922 def _send_traffic_on_tg_with_ramp_up(
923 self, duration, rate, async_call=False, ramp_up_only=False):
924 """Send traffic from all interfaces on TG, maybe after ramp-up.
926 This is an internal function, it assumes set_rate_provider_defaults
927 has been called to remember most values.
928 The reason why need to remember various values is that
929 the traffic can be asynchronous, and parsing needs those values.
930 The reason why this is a separate function from the one
931 which calls set_rate_provider_defaults is that some search algorithms
932 need to specify their own values, and we do not want the measure call
933 to overwrite them with defaults.
935 If ramp-up tracking is detected, a computation is performed,
936 and if state timeout is near, trial at ramp-up rate and duration
937 is inserted before the main trial measurement.
939 The ramp_up_only parameter forces a ramp-up without immediate
940 trial measurement, which is useful in case self remembers
941 a previous ramp-up trial that belongs to a different test (phase).
943 Return None if trial is async or ramp-up only.
945 :param duration: Duration of test traffic generation in seconds.
946 :param rate: Traffic rate in transactions per second.
947 :param async_call: Async mode.
948 :param ramp_up_only: If true, do not perform main trial measurement.
949 :type duration: float
951 :type async_call: bool
952 :type ramp_up_only: bool
953 :returns: TG results.
954 :rtype: ReceiveRateMeasurement or None
955 :raises ValueError: If TG traffic profile is not supported.
958 if self.ramp_up_rate:
959 # Figure out whether we need to insert a ramp-up trial.
960 if ramp_up_only or self.ramp_up_start is None:
961 # We never ramped up yet (at least not in this test case).
962 ramp_up_needed = True
964 # We ramped up before, but maybe it was too long ago.
965 # Adding a constant overhead to be safe.
966 time_now = time.monotonic() + 1.0
967 computed_duration, complete = self._compute_duration(
971 # There are two conditions for inserting ramp-up.
972 # If early sessions are expiring already,
973 # or if late sessions are to expire before measurement is over.
974 ramp_up_start_delay = time_now - self.ramp_up_start
975 ramp_up_stop_delay = time_now - self.ramp_up_stop
976 ramp_up_stop_delay += computed_duration
977 bigger_delay = max(ramp_up_start_delay, ramp_up_stop_delay)
978 # Final boolean decision.
979 ramp_up_needed = (bigger_delay >= self.state_timeout)
982 u"State may time out during next real trial, "
983 u"inserting a ramp-up trial."
985 self.ramp_up_start = time.monotonic()
986 self._send_traffic_on_tg_internal(
987 duration=self.ramp_up_duration,
988 rate=self.ramp_up_rate,
989 async_call=async_call,
991 self.ramp_up_stop = time.monotonic()
992 logger.debug(u"Ramp-up done.")
995 u"State will probably not time out during next real trial, "
996 u"no ramp-up trial needed just yet."
1000 trial_start = time.monotonic()
1001 result = self._send_traffic_on_tg_internal(
1004 async_call=async_call,
1006 trial_end = time.monotonic()
1007 if self.ramp_up_rate:
1008 # Optimization: No loss acts as a good ramp-up, if it was complete.
1009 if complete and result is not None and result.loss_count == 0:
1010 logger.debug(u"Good trial acts as a ramp-up")
1011 self.ramp_up_start = trial_start
1012 self.ramp_up_stop = trial_end
1014 logger.debug(u"Loss or incomplete, does not act as a ramp-up.")
1017 def no_traffic_loss_occurred(self):
1018 """Fail if loss occurred in traffic run.
1021 :raises Exception: If loss occured.
1023 if self._loss is None:
1024 raise RuntimeError(u"The traffic generation has not been issued")
1025 if self._loss != u"0":
1026 raise RuntimeError(f"Traffic loss occurred: {self._loss}")
1028 def fail_if_no_traffic_forwarded(self):
1029 """Fail if no traffic forwarded.
1032 :raises Exception: If no traffic forwarded.
1034 if self._received is None:
1035 raise RuntimeError(u"The traffic generation has not been issued")
1036 if self._received == 0:
1037 raise RuntimeError(u"No traffic forwarded")
1039 def partial_traffic_loss_accepted(
1040 self, loss_acceptance, loss_acceptance_type):
1041 """Fail if loss is higher then accepted in traffic run.
1043 :param loss_acceptance: Permitted drop ratio or frames count.
1044 :param loss_acceptance_type: Type of permitted loss.
1045 :type loss_acceptance: float
1046 :type loss_acceptance_type: LossAcceptanceType
1048 :raises Exception: If loss is above acceptance criteria.
1050 if self._loss is None:
1051 raise Exception(u"The traffic generation has not been issued")
1053 if loss_acceptance_type == u"percentage":
1054 loss = (float(self._loss) / float(self._sent)) * 100
1055 elif loss_acceptance_type == u"frames":
1056 loss = float(self._loss)
1058 raise Exception(u"Loss acceptance type not supported")
1060 if loss > float(loss_acceptance):
1062 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
1065 def _parse_traffic_results(self, stdout):
1066 """Parse stdout of scripts into fields of self.
1068 Block of code to reuse, by sync start, or stop after async.
1070 :param stdout: Text containing the standard output.
1073 subtype = check_subtype(self._node)
1074 if subtype == NodeSubTypeTG.TREX:
1075 # Last line from console output
1076 line = stdout.splitlines()[-1]
1077 results = line.split(u";")
1078 if results[-1] in (u" ", u""):
1080 self._result = dict()
1081 for result in results:
1082 key, value = result.split(u"=", maxsplit=1)
1083 self._result[key.strip()] = value
1084 logger.info(f"TrafficGen results:\n{self._result}")
1085 self._received = int(self._result.get(u"total_received"), 0)
1086 self._sent = int(self._result.get(u"total_sent", 0))
1087 self._loss = int(self._result.get(u"frame_loss", 0))
1088 self._approximated_duration = \
1089 self._result.get(u"approximated_duration", 0.0)
1090 if u"manual" not in str(self._approximated_duration):
1091 self._approximated_duration = float(self._approximated_duration)
1092 self._latency = list()
1093 self._latency.append(self._result.get(u"latency_stream_0(usec)"))
1094 self._latency.append(self._result.get(u"latency_stream_1(usec)"))
1095 if self._mode == TrexMode.ASTF:
1096 self._l7_data = dict()
1097 self._l7_data[u"client"] = dict()
1098 self._l7_data[u"client"][u"sent"] = \
1099 int(self._result.get(u"client_sent", 0))
1100 self._l7_data[u"client"][u"received"] = \
1101 int(self._result.get(u"client_received", 0))
1102 self._l7_data[u"client"][u"active_flows"] = \
1103 int(self._result.get(u"client_active_flows", 0))
1104 self._l7_data[u"client"][u"established_flows"] = \
1105 int(self._result.get(u"client_established_flows", 0))
1106 self._l7_data[u"client"][u"traffic_duration"] = \
1107 float(self._result.get(u"client_traffic_duration", 0.0))
1108 self._l7_data[u"client"][u"err_rx_throttled"] = \
1109 int(self._result.get(u"client_err_rx_throttled", 0))
1110 self._l7_data[u"client"][u"err_c_nf_throttled"] = \
1111 int(self._result.get(u"client_err_nf_throttled", 0))
1112 self._l7_data[u"client"][u"err_flow_overflow"] = \
1113 int(self._result.get(u"client_err_flow_overflow", 0))
1114 self._l7_data[u"server"] = dict()
1115 self._l7_data[u"server"][u"active_flows"] = \
1116 int(self._result.get(u"server_active_flows", 0))
1117 self._l7_data[u"server"][u"established_flows"] = \
1118 int(self._result.get(u"server_established_flows", 0))
1119 self._l7_data[u"server"][u"traffic_duration"] = \
1120 float(self._result.get(u"server_traffic_duration", 0.0))
1121 self._l7_data[u"server"][u"err_rx_throttled"] = \
1122 int(self._result.get(u"client_err_rx_throttled", 0))
1123 if u"udp" in self.traffic_profile:
1124 self._l7_data[u"client"][u"udp"] = dict()
1125 self._l7_data[u"client"][u"udp"][u"connects"] = \
1126 int(self._result.get(u"client_udp_connects", 0))
1127 self._l7_data[u"client"][u"udp"][u"closed_flows"] = \
1128 int(self._result.get(u"client_udp_closed", 0))
1129 self._l7_data[u"client"][u"udp"][u"tx_bytes"] = \
1130 int(self._result.get(u"client_udp_tx_bytes", 0))
1131 self._l7_data[u"client"][u"udp"][u"rx_bytes"] = \
1132 int(self._result.get(u"client_udp_rx_bytes", 0))
1133 self._l7_data[u"client"][u"udp"][u"tx_packets"] = \
1134 int(self._result.get(u"client_udp_tx_packets", 0))
1135 self._l7_data[u"client"][u"udp"][u"rx_packets"] = \
1136 int(self._result.get(u"client_udp_rx_packets", 0))
1137 self._l7_data[u"client"][u"udp"][u"keep_drops"] = \
1138 int(self._result.get(u"client_udp_keep_drops", 0))
1139 self._l7_data[u"client"][u"udp"][u"err_cwf"] = \
1140 int(self._result.get(u"client_err_cwf", 0))
1141 self._l7_data[u"server"][u"udp"] = dict()
1142 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = \
1143 int(self._result.get(u"server_udp_accepts", 0))
1144 self._l7_data[u"server"][u"udp"][u"closed_flows"] = \
1145 int(self._result.get(u"server_udp_closed", 0))
1146 self._l7_data[u"server"][u"udp"][u"tx_bytes"] = \
1147 int(self._result.get(u"server_udp_tx_bytes", 0))
1148 self._l7_data[u"server"][u"udp"][u"rx_bytes"] = \
1149 int(self._result.get(u"server_udp_rx_bytes", 0))
1150 self._l7_data[u"server"][u"udp"][u"tx_packets"] = \
1151 int(self._result.get(u"server_udp_tx_packets", 0))
1152 self._l7_data[u"server"][u"udp"][u"rx_packets"] = \
1153 int(self._result.get(u"server_udp_rx_packets", 0))
1154 elif u"tcp" in self.traffic_profile:
1155 self._l7_data[u"client"][u"tcp"] = dict()
1156 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = \
1157 int(self._result.get(u"client_tcp_connect_inits", 0))
1158 self._l7_data[u"client"][u"tcp"][u"connects"] = \
1159 int(self._result.get(u"client_tcp_connects", 0))
1160 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = \
1161 int(self._result.get(u"client_tcp_closed", 0))
1162 self._l7_data[u"client"][u"tcp"][u"connattempt"] = \
1163 int(self._result.get(u"client_tcp_connattempt", 0))
1164 self._l7_data[u"client"][u"tcp"][u"tx_bytes"] = \
1165 int(self._result.get(u"client_tcp_tx_bytes", 0))
1166 self._l7_data[u"client"][u"tcp"][u"rx_bytes"] = \
1167 int(self._result.get(u"client_tcp_rx_bytes", 0))
1168 self._l7_data[u"server"][u"tcp"] = dict()
1169 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = \
1170 int(self._result.get(u"server_tcp_accepts", 0))
1171 self._l7_data[u"server"][u"tcp"][u"connects"] = \
1172 int(self._result.get(u"server_tcp_connects", 0))
1173 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = \
1174 int(self._result.get(u"server_tcp_closed", 0))
1175 self._l7_data[u"server"][u"tcp"][u"tx_bytes"] = \
1176 int(self._result.get(u"server_tcp_tx_bytes", 0))
1177 self._l7_data[u"server"][u"tcp"][u"rx_bytes"] = \
1178 int(self._result.get(u"server_tcp_rx_bytes", 0))
1180 def _get_measurement_result(self):
1181 """Return the result of last measurement as ReceiveRateMeasurement.
1183 Separate function, as measurements can end either by time
1184 or by explicit call, this is the common block at the end.
1186 The target_tr field of ReceiveRateMeasurement is in
1187 transactions per second. Transmit count and loss count units
1188 depend on the transaction type. Usually they are in transactions
1189 per second, or aggregated packets per second.
1191 :returns: Structure containing the result of the measurement.
1192 :rtype: ReceiveRateMeasurement
1195 # Client duration seems to include a setup period
1196 # where TRex does not send any packets yet.
1197 # Server duration does not include it.
1198 server_data = self._l7_data[u"server"]
1199 approximated_duration = float(server_data[u"traffic_duration"])
1200 except (KeyError, AttributeError, ValueError, TypeError):
1201 approximated_duration = None
1203 if not approximated_duration:
1204 approximated_duration = float(self._approximated_duration)
1205 except ValueError: # "manual"
1206 approximated_duration = None
1207 if not approximated_duration:
1208 if self._duration and self._duration > 0:
1209 # Known recomputed or target duration.
1210 approximated_duration = self._duration
1212 # It was an explicit stop.
1213 if not self._stop_time:
1214 raise RuntimeError(u"Unable to determine duration.")
1215 approximated_duration = self._stop_time - self._start_time
1216 target_duration = self._target_duration
1217 if not target_duration:
1218 target_duration = approximated_duration
1219 transmit_rate = self._rate
1221 if self.transaction_type == u"packet":
1222 partial_attempt_count = self._sent
1223 packet_rate = transmit_rate * self.ppta
1224 # We have a float. TRex way of rounding it is not obvious.
1225 # The biggest source of mismatch is Inter Stream Gap.
1226 # So the code tolerates 10 usec of missing packets.
1227 expected_attempt_count = (target_duration - 1e-5) * packet_rate
1228 expected_attempt_count = math.ceil(expected_attempt_count)
1229 # TRex can send more.
1230 expected_attempt_count = max(expected_attempt_count, self._sent)
1231 unsent = expected_attempt_count - self._sent
1232 pass_count = self._received
1233 fail_count = expected_attempt_count - pass_count
1234 elif self.transaction_type == u"udp_cps":
1235 if not self.transaction_scale:
1236 raise RuntimeError(u"Add support for no-limit udp_cps.")
1237 partial_attempt_count = self._l7_data[u"client"][u"sent"]
1238 # We do not care whether TG is slow, it should have attempted all.
1239 expected_attempt_count = self.transaction_scale
1240 unsent = expected_attempt_count - partial_attempt_count
1241 pass_count = self._l7_data[u"client"][u"received"]
1242 fail_count = expected_attempt_count - pass_count
1243 elif self.transaction_type == u"tcp_cps":
1244 if not self.transaction_scale:
1245 raise RuntimeError(u"Add support for no-limit tcp_cps.")
1246 ctca = self._l7_data[u"client"][u"tcp"][u"connattempt"]
1247 partial_attempt_count = ctca
1248 # We do not care whether TG is slow, it should have attempted all.
1249 expected_attempt_count = self.transaction_scale
1250 unsent = expected_attempt_count - partial_attempt_count
1251 # From TCP point of view, server/connects counts full connections,
1252 # but we are testing NAT session so client/connects counts that
1253 # (half connections from TCP point of view).
1254 pass_count = self._l7_data[u"client"][u"tcp"][u"connects"]
1255 fail_count = expected_attempt_count - pass_count
1256 elif self.transaction_type == u"udp_pps":
1257 if not self.transaction_scale:
1258 raise RuntimeError(u"Add support for no-limit udp_pps.")
1259 partial_attempt_count = self._sent
1260 expected_attempt_count = self.transaction_scale * self.ppta
1261 unsent = expected_attempt_count - self._sent
1262 fail_count = self._loss + unsent
1263 elif self.transaction_type == u"tcp_pps":
1264 if not self.transaction_scale:
1265 raise RuntimeError(u"Add support for no-limit tcp_pps.")
1266 partial_attempt_count = self._sent
1267 expected_attempt_count = self.transaction_scale * self.ppta
1268 # One loss-like scenario happens when TRex receives all packets
1269 # on L2 level, but is not fast enough to process them all
1270 # at L7 level, which leads to retransmissions.
1271 # Those manifest as opackets larger than expected.
1272 # A simple workaround is to add absolute difference.
1273 # Probability of retransmissions exactly cancelling
1274 # packets unsent due to duration stretching is quite low.
1275 unsent = abs(expected_attempt_count - self._sent)
1276 fail_count = self._loss + unsent
1278 raise RuntimeError(f"Unknown parsing {self.transaction_type!r}")
1279 if unsent and isinstance(self._approximated_duration, float):
1280 # Do not report unsent for "manual".
1281 logger.debug(f"Unsent packets/transactions: {unsent}")
1282 if fail_count < 0 and not self.negative_loss:
1284 measurement = ReceiveRateMeasurement(
1285 duration=target_duration,
1286 target_tr=transmit_rate,
1287 transmit_count=expected_attempt_count,
1288 loss_count=fail_count,
1289 approximated_duration=approximated_duration,
1290 partial_transmit_count=partial_attempt_count,
1292 measurement.latency = self.get_latency_int()
1295 def measure(self, duration, transmit_rate):
1296 """Run trial measurement, parse and return results.
1298 The input rate is for transactions. Stateles bidirectional traffic
1299 is understood as sequence of (asynchronous) transactions,
1302 The result units depend on test type, generally
1303 the count either transactions or packets (aggregated over directions).
1305 Optionally, this method sleeps if measurement finished before
1306 the time specified as duration.
1308 :param duration: Trial duration [s].
1309 :param transmit_rate: Target rate in transactions per second.
1310 :type duration: float
1311 :type transmit_rate: float
1312 :returns: Structure containing the result of the measurement.
1313 :rtype: ReceiveRateMeasurement
1314 :raises RuntimeError: If TG is not set or if node is not TG
1315 or if subtype is not specified.
1316 :raises NotImplementedError: If TG is not supported.
1318 duration = float(duration)
1319 time_start = time.monotonic()
1320 time_stop = time_start + duration
1323 result = self._send_traffic_on_tg_with_ramp_up(
1328 logger.debug(f"trial measurement result: {result!r}")
1329 # In PLRsearch, computation needs the specified time to complete.
1330 if self.sleep_till_duration:
1331 sleeptime = time_stop - time.monotonic()
1333 time.sleep(sleeptime)
1336 def set_rate_provider_defaults(
1342 traffic_directions=2,
1343 transaction_duration=0.0,
1344 transaction_scale=0,
1345 transaction_type=u"packet",
1348 sleep_till_duration=False,
1351 ramp_up_duration=None,
1352 state_timeout=240.0,
1354 """Store values accessed by measure().
1356 :param frame_size: Frame size identifier or value [B].
1357 :param traffic_profile: Module name as a traffic profile identifier.
1358 See GPL/traffic_profiles/trex for implemented modules.
1359 :param ppta: Packets per transaction, aggregated over directions.
1360 Needed for udp_pps which does not have a good transaction counter,
1361 so we need to compute expected number of packets.
1363 :param resetter: Callable to reset DUT state for repeated trials.
1364 :param traffic_directions: Traffic from packet counting point of view
1365 is bi- (2) or uni- (1) directional.
1367 :param transaction_duration: Total expected time to close transaction.
1368 :param transaction_scale: Number of transactions to perform.
1369 0 (default) means unlimited.
1370 :param transaction_type: An identifier specifying which counters
1371 and formulas to use when computing attempted and failed
1372 transactions. Default: "packet".
1373 :param duration_limit: Zero or maximum limit for computed (or given)
1375 :param negative_loss: If false, negative loss is reported as zero loss.
1376 :param sleep_till_duration: If true and measurement returned faster,
1377 sleep until it matches duration. Needed for PLRsearch.
1378 :param use_latency: Whether to measure latency during the trial.
1380 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1381 :param ramp_up_duration: Duration of ramp-up trials [s].
1382 :param state_timeout: Time of life of DUT state [s].
1383 :type frame_size: str or int
1384 :type traffic_profile: str
1386 :type resetter: Optional[Callable[[], None]]
1387 :type traffic_directions: int
1388 :type transaction_duration: float
1389 :type transaction_scale: int
1390 :type transaction_type: str
1391 :type duration_limit: float
1392 :type negative_loss: bool
1393 :type sleep_till_duration: bool
1394 :type use_latency: bool
1395 :type ramp_up_rate: float
1396 :type ramp_up_duration: float
1397 :type state_timeout: float
1399 self.frame_size = frame_size
1400 self.traffic_profile = str(traffic_profile)
1401 self.resetter = resetter
1403 self.traffic_directions = int(traffic_directions)
1404 self.transaction_duration = float(transaction_duration)
1405 self.transaction_scale = int(transaction_scale)
1406 self.transaction_type = str(transaction_type)
1407 self.duration_limit = float(duration_limit)
1408 self.negative_loss = bool(negative_loss)
1409 self.sleep_till_duration = bool(sleep_till_duration)
1410 self.use_latency = bool(use_latency)
1411 self.ramp_up_rate = float(ramp_up_rate)
1412 self.ramp_up_duration = float(ramp_up_duration)
1413 self.state_timeout = float(state_timeout)
1416 class OptimizedSearch:
1417 """Class to be imported as Robot Library, containing search keywords.
1419 Aside of setting up measurer and forwarding arguments,
1420 the main business is to translate min/max rate from unidir to aggregated.
1424 def perform_optimized_ndrpdr_search(
1427 minimum_transmit_rate,
1428 maximum_transmit_rate,
1429 packet_loss_ratio=0.005,
1430 final_relative_width=0.005,
1431 final_trial_duration=30.0,
1432 initial_trial_duration=1.0,
1433 number_of_intermediate_phases=2,
1437 traffic_directions=2,
1438 transaction_duration=0.0,
1439 transaction_scale=0,
1440 transaction_type=u"packet",
1443 ramp_up_duration=None,
1444 state_timeout=240.0,
1445 expansion_coefficient=4.0,
1447 """Setup initialized TG, perform optimized search, return intervals.
1449 If transaction_scale is nonzero, all init and non-init trial durations
1450 are set to 1.0 (as they do not affect the real trial duration)
1451 and zero intermediate phases are used.
1452 This way no re-measurement happens.
1453 Warmup has to be handled via resetter or ramp-up mechanisms.
1455 :param frame_size: Frame size identifier or value [B].
1456 :param traffic_profile: Module name as a traffic profile identifier.
1457 See GPL/traffic_profiles/trex for implemented modules.
1458 :param minimum_transmit_rate: Minimal load in transactions per second.
1459 :param maximum_transmit_rate: Maximal load in transactions per second.
1460 :param packet_loss_ratio: Ratio of packets lost, for PDR [1].
1461 :param final_relative_width: Final lower bound transmit rate
1462 cannot be more distant that this multiple of upper bound [1].
1463 :param final_trial_duration: Trial duration for the final phase [s].
1464 :param initial_trial_duration: Trial duration for the initial phase
1465 and also for the first intermediate phase [s].
1466 :param number_of_intermediate_phases: Number of intermediate phases
1467 to perform before the final phase [1].
1468 :param timeout: The search will fail itself when not finished
1469 before this overall time [s].
1470 :param ppta: Packets per transaction, aggregated over directions.
1471 Needed for udp_pps which does not have a good transaction counter,
1472 so we need to compute expected number of packets.
1474 :param resetter: Callable to reset DUT state for repeated trials.
1475 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1477 :param transaction_duration: Total expected time to close transaction.
1478 :param transaction_scale: Number of transactions to perform.
1479 0 (default) means unlimited.
1480 :param transaction_type: An identifier specifying which counters
1481 and formulas to use when computing attempted and failed
1482 transactions. Default: "packet".
1483 :param use_latency: Whether to measure latency during the trial.
1485 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1486 :param ramp_up_duration: Duration of ramp-up trials [s].
1487 :param state_timeout: Time of life of DUT state [s].
1488 :param expansion_coefficient: In external search multiply width by this.
1489 :type frame_size: str or int
1490 :type traffic_profile: str
1491 :type minimum_transmit_rate: float
1492 :type maximum_transmit_rate: float
1493 :type packet_loss_ratio: float
1494 :type final_relative_width: float
1495 :type final_trial_duration: float
1496 :type initial_trial_duration: float
1497 :type number_of_intermediate_phases: int
1498 :type timeout: float
1500 :type resetter: Optional[Callable[[], None]]
1501 :type traffic_directions: int
1502 :type transaction_duration: float
1503 :type transaction_scale: int
1504 :type transaction_type: str
1505 :type use_latency: bool
1506 :type ramp_up_rate: float
1507 :type ramp_up_duration: float
1508 :type state_timeout: float
1509 :type expansion_coefficient: float
1510 :returns: Structure containing narrowed down NDR and PDR intervals
1511 and their measurements.
1512 :rtype: List[Receiverateinterval]
1513 :raises RuntimeError: If total duration is larger than timeout.
1515 # we need instance of TrafficGenerator instantiated by Robot Framework
1516 # to be able to use trex_stl-*()
1517 tg_instance = BuiltIn().get_library_instance(
1518 u"resources.libraries.python.TrafficGenerator"
1520 # Overrides for fixed transaction amount.
1521 if transaction_scale:
1522 initial_trial_duration = 1.0
1523 final_trial_duration = 1.0
1524 number_of_intermediate_phases = 0
1525 timeout += transaction_scale * 3e-4
1526 tg_instance.set_rate_provider_defaults(
1527 frame_size=frame_size,
1528 traffic_profile=traffic_profile,
1529 sleep_till_duration=False,
1532 traffic_directions=traffic_directions,
1533 transaction_duration=transaction_duration,
1534 transaction_scale=transaction_scale,
1535 transaction_type=transaction_type,
1536 use_latency=use_latency,
1537 ramp_up_rate=ramp_up_rate,
1538 ramp_up_duration=ramp_up_duration,
1539 state_timeout=state_timeout,
1541 algorithm = MultipleLossRatioSearch(
1542 measurer=tg_instance,
1543 final_trial_duration=final_trial_duration,
1544 final_relative_width=final_relative_width,
1545 number_of_intermediate_phases=number_of_intermediate_phases,
1546 initial_trial_duration=initial_trial_duration,
1549 expansion_coefficient=expansion_coefficient,
1551 if packet_loss_ratio:
1552 packet_loss_ratios = [0.0, packet_loss_ratio]
1554 # Happens in reconf tests.
1555 packet_loss_ratios = [packet_loss_ratio]
1556 results = algorithm.narrow_down_intervals(
1557 min_rate=minimum_transmit_rate,
1558 max_rate=maximum_transmit_rate,
1559 packet_loss_ratios=packet_loss_ratios,
1564 def perform_soak_search(
1567 minimum_transmit_rate,
1568 maximum_transmit_rate,
1575 trace_enabled=False,
1576 traffic_directions=2,
1577 transaction_duration=0.0,
1578 transaction_scale=0,
1579 transaction_type=u"packet",
1582 ramp_up_duration=None,
1583 state_timeout=240.0,
1585 """Setup initialized TG, perform soak search, return avg and stdev.
1587 :param frame_size: Frame size identifier or value [B].
1588 :param traffic_profile: Module name as a traffic profile identifier.
1589 See GPL/traffic_profiles/trex for implemented modules.
1590 :param minimum_transmit_rate: Minimal load in transactions per second.
1591 :param maximum_transmit_rate: Maximal load in transactions per second.
1592 :param plr_target: Ratio of packets lost to achieve [1].
1593 :param tdpt: Trial duration per trial.
1594 The algorithm linearly increases trial duration with trial number,
1595 this is the increment between succesive trials, in seconds.
1596 :param initial_count: Offset to apply before the first trial.
1597 For example initial_count=50 makes first trial to be 51*tdpt long.
1598 This is needed because initial "search" phase of integrator
1599 takes significant time even without any trial results.
1600 :param timeout: The search will stop after this overall time [s].
1601 :param ppta: Packets per transaction, aggregated over directions.
1602 Needed for udp_pps which does not have a good transaction counter,
1603 so we need to compute expected number of packets.
1605 :param resetter: Callable to reset DUT state for repeated trials.
1606 :param trace_enabled: True if trace enabled else False.
1607 This is very verbose tracing on numeric computations,
1608 do not use in production.
1610 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1612 :param transaction_duration: Total expected time to close transaction.
1613 :param transaction_scale: Number of transactions to perform.
1614 0 (default) means unlimited.
1615 :param transaction_type: An identifier specifying which counters
1616 and formulas to use when computing attempted and failed
1617 transactions. Default: "packet".
1618 :param use_latency: Whether to measure latency during the trial.
1620 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1621 :param ramp_up_duration: Duration of ramp-up trials [s].
1622 :param state_timeout: Time of life of DUT state [s].
1623 :type frame_size: str or int
1624 :type traffic_profile: str
1625 :type minimum_transmit_rate: float
1626 :type maximum_transmit_rate: float
1627 :type plr_target: float
1628 :type initial_count: int
1629 :type timeout: float
1631 :type resetter: Optional[Callable[[], None]]
1632 :type trace_enabled: bool
1633 :type traffic_directions: int
1634 :type transaction_duration: float
1635 :type transaction_scale: int
1636 :type transaction_type: str
1637 :type use_latency: bool
1638 :type ramp_up_rate: float
1639 :type ramp_up_duration: float
1640 :type state_timeout: float
1641 :returns: Average and stdev of estimated aggregated rate giving PLR.
1642 :rtype: 2-tuple of float
1644 tg_instance = BuiltIn().get_library_instance(
1645 u"resources.libraries.python.TrafficGenerator"
1647 # Overrides for fixed transaction amount.
1648 if transaction_scale:
1650 tg_instance.set_rate_provider_defaults(
1651 frame_size=frame_size,
1652 traffic_profile=traffic_profile,
1653 negative_loss=False,
1654 sleep_till_duration=True,
1657 traffic_directions=traffic_directions,
1658 transaction_duration=transaction_duration,
1659 transaction_scale=transaction_scale,
1660 transaction_type=transaction_type,
1661 use_latency=use_latency,
1662 ramp_up_rate=ramp_up_rate,
1663 ramp_up_duration=ramp_up_duration,
1664 state_timeout=state_timeout,
1666 algorithm = PLRsearch(
1667 measurer=tg_instance,
1668 trial_duration_per_trial=tdpt,
1669 packet_loss_ratio_target=plr_target,
1670 trial_number_offset=initial_count,
1672 trace_enabled=trace_enabled,
1674 result = algorithm.search(
1675 min_rate=minimum_transmit_rate,
1676 max_rate=maximum_transmit_rate,