1 # Copyright (c) 2021 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,
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.
81 :type loss_acceptance: float
82 :type loss_acceptance_type: LossAcceptanceType
83 :type traffic_profile: str
84 :returns: Drop threshold exceeded? (True/False)
86 :raises NotImplementedError: If TG is not supported.
87 :raises RuntimeError: If TG is not specified.
89 # we need instance of TrafficGenerator instantiated by Robot Framework
90 # to be able to use trex_stl-*()
91 tg_instance = BuiltIn().get_library_instance(
92 u"resources.libraries.python.TrafficGenerator"
94 subtype = check_subtype(tg_instance.node)
95 if subtype == NodeSubTypeTG.TREX:
96 unit_rate = str(rate) + self.get_rate_type_str()
97 tg_instance.trex_stl_start_remote_exec(
98 self.get_duration(), unit_rate, frame_size, traffic_profile
100 loss = tg_instance.get_loss()
101 sent = tg_instance.get_sent()
102 if self.loss_acceptance_type_is_percentage():
103 loss = (float(loss) / float(sent)) * 100
105 f"comparing: {loss} < {loss_acceptance} {loss_acceptance_type}"
107 return float(loss) <= float(loss_acceptance)
110 def get_latency(self):
111 """Returns min/avg/max latency.
113 :returns: Latency stats.
116 tg_instance = BuiltIn().get_library_instance(
117 u"resources.libraries.python.TrafficGenerator"
119 return tg_instance.get_latency_int()
123 """Defines mode of T-Rex traffic generator."""
124 # Advanced stateful mode
130 # TODO: Pylint says too-many-instance-attributes.
131 class TrafficGenerator(AbstractMeasurer):
132 """Traffic Generator."""
134 # TODO: Remove "trex" from lines which could work with other TGs.
136 # Use one instance of TrafficGenerator for all tests in test suite
137 ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
140 # TODO: Separate into few dataclasses/dicts.
141 # Pylint dislikes large unstructured state, and it is right.
144 # TG interface order mapping
145 self._ifaces_reordered = False
146 # Result holding fields, to be removed.
151 self._received = None
152 self._approximated_rate = None
153 self._approximated_duration = None
155 # Measurement input fields, needed for async stop result.
156 self._start_time = None
157 self._stop_time = None
159 self._target_duration = None
160 self._duration = None
161 # Other input parameters, not knowable from measure() signature.
162 self.frame_size = None
163 self.traffic_profile = None
164 self.traffic_directions = None
165 self.negative_loss = None
166 self.use_latency = None
169 self.transaction_scale = None
170 self.transaction_duration = None
171 self.sleep_till_duration = None
172 self.transaction_type = None
173 self.duration_limit = None
174 self.ramp_up_start = None
175 self.ramp_up_stop = None
176 self.ramp_up_rate = None
177 self.ramp_up_duration = None
178 self.state_timeout = None
179 # Transient data needed for async measurements.
180 self._xstats = (None, None)
181 # TODO: Rename "xstats" to something opaque, so T-Rex is not privileged?
187 :returns: Traffic generator node.
193 """Return number of lost packets.
195 :returns: Number of lost packets.
201 """Return number of sent packets.
203 :returns: Number of sent packets.
208 def get_received(self):
209 """Return number of received packets.
211 :returns: Number of received packets.
214 return self._received
216 def get_latency_int(self):
217 """Return rounded min/avg/max latency.
219 :returns: Latency stats.
224 def get_approximated_rate(self):
225 """Return approximated rate computed as ratio of transmitted packets
226 over duration of trial.
228 :returns: Approximated rate.
231 return self._approximated_rate
233 def get_l7_data(self):
236 :returns: Number of received packets.
241 def check_mode(self, expected_mode):
244 :param expected_mode: Expected traffic generator mode.
245 :type expected_mode: object
246 :raises RuntimeError: In case of unexpected TG mode.
248 if self._mode == expected_mode:
251 f"{self._node[u'subtype']} not running in {expected_mode} mode!"
254 # TODO: pylint says disable=too-many-locals.
255 def initialize_traffic_generator(
256 self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
257 tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
258 tg_if2_dst_mac=None):
259 """TG initialization.
261 TODO: Document why do we need (and how do we use) _ifaces_reordered.
263 :param tg_node: Traffic generator node.
264 :param tg_if1: TG - name of first interface.
265 :param tg_if2: TG - name of second interface.
266 :param tg_if1_adj_node: TG if1 adjecent node.
267 :param tg_if1_adj_if: TG if1 adjecent interface.
268 :param tg_if2_adj_node: TG if2 adjecent node.
269 :param tg_if2_adj_if: TG if2 adjecent interface.
270 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
271 :param tg_if1_dst_mac: Interface 1 destination MAC address.
272 :param tg_if2_dst_mac: Interface 2 destination MAC address.
276 :type tg_if1_adj_node: dict
277 :type tg_if1_adj_if: str
278 :type tg_if2_adj_node: dict
279 :type tg_if2_adj_if: str
281 :type tg_if1_dst_mac: str
282 :type tg_if2_dst_mac: str
284 :raises RuntimeError: In case of issue during initialization.
286 subtype = check_subtype(tg_node)
287 if subtype == NodeSubTypeTG.TREX:
289 self._mode = TrexMode.ASTF if osi_layer == u"L7" else TrexMode.STL
292 if1[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if1)
293 if2[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if2)
294 if1[u"addr"] = Topology().get_interface_mac(self._node, tg_if1)
295 if2[u"addr"] = Topology().get_interface_mac(self._node, tg_if2)
297 if osi_layer == u"L2":
298 if1[u"adj_addr"] = if2[u"addr"]
299 if2[u"adj_addr"] = if1[u"addr"]
300 elif osi_layer in (u"L3", u"L7"):
301 if1[u"adj_addr"] = Topology().get_interface_mac(
302 tg_if1_adj_node, tg_if1_adj_if
304 if2[u"adj_addr"] = Topology().get_interface_mac(
305 tg_if2_adj_node, tg_if2_adj_if
308 raise ValueError(u"Unknown OSI layer!")
310 # in case of switched environment we can override MAC addresses
311 if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
312 if1[u"adj_addr"] = tg_if1_dst_mac
313 if2[u"adj_addr"] = tg_if2_dst_mac
315 if min(if1[u"pci"], if2[u"pci"]) != if1[u"pci"]:
317 self._ifaces_reordered = True
319 master_thread_id, latency_thread_id, socket, threads = \
320 CpuUtils.get_affinity_trex(
321 self._node, tg_if1, tg_if2,
322 tg_dtc=Constants.TREX_CORE_COUNT)
324 if osi_layer in (u"L2", u"L3", u"L7"):
327 f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
329 f" c: {len(threads)}\n"
330 f" limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
331 f" interfaces: [\"{if1[u'pci']}\",\"{if2[u'pci']}\"]\n"
333 f" - dest_mac: \'{if1[u'adj_addr']}\'\n"
334 f" src_mac: \'{if1[u'addr']}\'\n"
335 f" - dest_mac: \'{if2[u'adj_addr']}\'\n"
336 f" src_mac: \'{if2[u'addr']}\'\n"
338 f" master_thread_id: {master_thread_id}\n"
339 f" latency_thread_id: {latency_thread_id}\n"
341 f" - socket: {socket}\n"
342 f" threads: {threads}\n"
344 sudo=True, message=u"T-Rex config generation!"
347 if Constants.TREX_RX_DESCRIPTORS_COUNT != 0:
350 f"sh -c 'cat << EOF >> /etc/trex_cfg.yaml\n"
351 f" rx_desc: {Constants.TREX_RX_DESCRIPTORS_COUNT}\n"
353 sudo=True, message=u"T-Rex rx_desc modification!"
356 if Constants.TREX_TX_DESCRIPTORS_COUNT != 0:
359 f"sh -c 'cat << EOF >> /etc/trex_cfg.yaml\n"
360 f" tx_desc: {Constants.TREX_TX_DESCRIPTORS_COUNT}\n"
362 sudo=True, message=u"T-Rex tx_desc modification!"
365 raise ValueError(u"Unknown OSI layer!")
367 TrafficGenerator.startup_trex(
368 self._node, osi_layer, subtype=subtype
372 def startup_trex(tg_node, osi_layer, subtype=None):
373 """Startup sequence for the TRex traffic generator.
375 :param tg_node: Traffic generator node.
376 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
377 :param subtype: Traffic generator sub-type.
380 :type subtype: NodeSubTypeTG
381 :raises RuntimeError: If T-Rex startup failed.
382 :raises ValueError: If OSI layer is not supported.
385 subtype = check_subtype(tg_node)
386 if subtype == NodeSubTypeTG.TREX:
387 for _ in range(0, 3):
388 # Kill TRex only if it is already running.
389 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
391 tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
396 for port in tg_node[u"interfaces"].values():
397 if u'Mellanox' not in port.get(u'model'):
398 ports += f" {port.get(u'pci_address')}"
400 cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \
401 f"./dpdk_nic_bind.py -u {ports} || true\""
403 tg_node, cmd, sudo=True,
404 message=u"Unbind PCI ports from driver failed!"
408 cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
409 trex_cmd = OptionString([u"nohup", u"./t-rex-64"])
411 trex_cmd.add(u"--prefix $(hostname)")
412 trex_cmd.add(u"--hdrh")
413 trex_cmd.add(u"--no-scapy-server")
414 trex_cmd.add_if(u"--astf", osi_layer == u"L7")
415 # OptionString does not create double space if extra is empty.
416 trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}")
417 inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &"
418 cmd = f"sh -c \"{inner_command}\" > /dev/null"
420 exec_cmd_no_error(tg_node, cmd, sudo=True)
422 cmd = u"sh -c \"cat /tmp/trex.log\""
424 tg_node, cmd, sudo=True,
425 message=u"Get TRex logs failed!"
427 raise RuntimeError(u"Start TRex failed!")
429 # Test T-Rex API responsiveness.
430 cmd = f"python3 {Constants.REMOTE_FW_DIR}/GPL/tools/trex/"
431 if osi_layer in (u"L2", u"L3"):
432 cmd += u"trex_stl_assert.py"
433 elif osi_layer == u"L7":
434 cmd += u"trex_astf_assert.py"
436 raise ValueError(u"Unknown OSI layer!")
439 tg_node, cmd, sudo=True,
440 message=u"T-Rex API is not responding!", retries=20
445 # After max retries TRex is still not responding to API critical
447 exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True)
448 raise RuntimeError(u"Start T-Rex failed after multiple retries!")
451 def is_trex_running(node):
452 """Check if T-Rex is running using pidof.
454 :param node: Traffic generator node.
456 :returns: True if T-Rex is running otherwise False.
459 ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True)
460 return bool(int(ret) == 0)
463 def teardown_traffic_generator(node):
466 :param node: Traffic generator node.
469 :raises RuntimeError: If node type is not a TG,
470 or if T-Rex teardown fails.
472 subtype = check_subtype(node)
473 if subtype == NodeSubTypeTG.TREX:
477 u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"",
479 message=u"T-Rex kill failed!"
482 def trex_astf_stop_remote_exec(self, node):
483 """Execute T-Rex ASTF script on remote node over ssh to stop running
486 Internal state is updated with measurement results.
488 :param node: T-Rex generator node.
490 :raises RuntimeError: If stop traffic script fails.
492 command_line = OptionString().add(u"python3")
493 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
494 command_line.add(f"'{dirname}/trex_astf_stop.py'")
495 command_line.change_prefix(u"--")
496 for index, value in enumerate(self._xstats):
497 if value is not None:
498 value = value.replace(u"'", u"\"")
499 command_line.add_equals(f"xstat{index}", f"'{value}'")
500 stdout, _ = exec_cmd_no_error(
502 message=u"T-Rex ASTF runtime error!"
504 self._parse_traffic_results(stdout)
506 def trex_stl_stop_remote_exec(self, node):
507 """Execute T-Rex STL script on remote node over ssh to stop running
510 Internal state is updated with measurement results.
512 :param node: T-Rex generator node.
514 :raises RuntimeError: If stop traffic script fails.
516 command_line = OptionString().add(u"python3")
517 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
518 command_line.add(f"'{dirname}/trex_stl_stop.py'")
519 command_line.change_prefix(u"--")
520 for index, value in enumerate(self._xstats):
521 if value is not None:
522 value = value.replace(u"'", u"\"")
523 command_line.add_equals(f"xstat{index}", f"'{value}'")
524 stdout, _ = exec_cmd_no_error(
526 message=u"T-Rex STL runtime error!"
528 self._parse_traffic_results(stdout)
530 def stop_traffic_on_tg(self):
531 """Stop all traffic on TG.
533 :returns: Structure containing the result of the measurement.
534 :rtype: ReceiveRateMeasurement
535 :raises ValueError: If TG traffic profile is not supported.
537 subtype = check_subtype(self._node)
538 if subtype != NodeSubTypeTG.TREX:
539 raise ValueError(f"Unsupported TG subtype: {subtype!r}")
540 if u"trex-astf" in self.traffic_profile:
541 self.trex_astf_stop_remote_exec(self._node)
542 elif u"trex-stl" in self.traffic_profile:
543 self.trex_stl_stop_remote_exec(self._node)
545 raise ValueError(u"Unsupported T-Rex traffic profile!")
546 self._stop_time = time.monotonic()
548 return self._get_measurement_result()
550 def _compute_duration(self, duration, multiplier):
551 """Compute duration for profile driver.
553 The final result is influenced by transaction scale and duration limit.
554 It is assumed a higher level function has already set those to self.
555 The duration argument is the target value from search point of view,
556 before the overrides are applied here.
558 Minus one (signalling async traffic start) is kept.
560 Completeness flag is also included. Duration limited or async trials
561 are not considered complete for ramp-up purposes.
563 :param duration: Time expressed in seconds for how long to send traffic.
564 :param multiplier: Traffic rate in transactions per second.
565 :type duration: float
566 :type multiplier: float
567 :returns: New duration and whether it was a complete ramp-up candidate.
572 return duration, False
573 computed_duration = duration
574 if self.transaction_scale:
575 computed_duration = self.transaction_scale / multiplier
576 # Log the computed duration,
577 # so we can compare with what telemetry suggests
578 # the real duration was.
579 logger.debug(f"Expected duration {computed_duration}")
580 computed_duration += 0.1115
581 if not self.duration_limit:
582 return computed_duration, True
583 limited_duration = min(computed_duration, self.duration_limit)
584 return limited_duration, (limited_duration == computed_duration)
586 def trex_astf_start_remote_exec(
587 self, duration, multiplier, async_call=False):
588 """Execute T-Rex ASTF script on remote node over ssh to start running
591 In sync mode, measurement results are stored internally.
592 In async mode, initial data including xstats are stored internally.
594 This method contains the logic to compute duration as maximum time
595 if transaction_scale is nonzero.
596 The transaction_scale argument defines (limits) how many transactions
597 will be started in total. As that amount of transaction can take
598 considerable time (sometimes due to explicit delays in the profile),
599 the real time a trial needs to finish is computed here. For now,
600 in that case the duration argument is ignored, assuming it comes
601 from ASTF-unaware search algorithm. The overall time a single
602 transaction needs is given in parameter transaction_duration,
603 it includes both explicit delays and implicit time it takes
604 to transfer data (or whatever the transaction does).
606 Currently it is observed TRex does not start the ASTF traffic
607 immediately, an ad-hoc constant is added to the computed duration
608 to compensate for that.
610 If transaction_scale is zero, duration is not recomputed.
611 It is assumed the subsequent result parsing gets the real duration
612 if the traffic stops sooner for any reason.
614 Currently, it is assumed traffic profile defines a single transaction.
615 To avoid heavy logic here, the input rate is expected to be in
616 transactions per second, as that directly translates to TRex multiplier,
617 (assuming the profile does not override the default cps value of one).
619 :param duration: Time expressed in seconds for how long to send traffic.
620 :param multiplier: Traffic rate in transactions per second.
621 :param async_call: If enabled then don't wait for all incoming traffic.
622 :type duration: float
623 :type multiplier: int
624 :type async_call: bool
625 :raises RuntimeError: In case of T-Rex driver issue.
627 self.check_mode(TrexMode.ASTF)
628 p_0, p_1 = (1, 0) if self._ifaces_reordered else (0, 1)
629 if not isinstance(duration, (float, int)):
630 duration = float(duration)
632 # TODO: Refactor the code so duration is computed only once,
633 # and both the initial and the computed durations are logged.
634 computed_duration, _ = self._compute_duration(duration, multiplier)
636 command_line = OptionString().add(u"python3")
637 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
638 command_line.add(f"'{dirname}/trex_astf_profile.py'")
639 command_line.change_prefix(u"--")
640 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
641 command_line.add_with_value(
642 u"profile", f"'{dirname}/{self.traffic_profile}.py'"
644 command_line.add_with_value(u"duration", f"{computed_duration!r}")
645 command_line.add_with_value(u"frame_size", self.frame_size)
646 command_line.add_with_value(u"multiplier", multiplier)
647 command_line.add_with_value(u"port_0", p_0)
648 command_line.add_with_value(u"port_1", p_1)
649 command_line.add_with_value(
650 u"traffic_directions", self.traffic_directions
652 command_line.add_if(u"async_start", async_call)
653 command_line.add_if(u"latency", self.use_latency)
654 command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
656 self._start_time = time.monotonic()
657 self._rate = multiplier
658 stdout, _ = exec_cmd_no_error(
659 self._node, command_line, timeout=computed_duration + 10.0,
660 message=u"T-Rex ASTF runtime error!"
665 self._target_duration = None
666 self._duration = None
667 self._received = None
671 xstats = [None, None]
672 self._l7_data = dict()
673 self._l7_data[u"client"] = dict()
674 self._l7_data[u"client"][u"active_flows"] = None
675 self._l7_data[u"client"][u"established_flows"] = None
676 self._l7_data[u"client"][u"traffic_duration"] = None
677 self._l7_data[u"server"] = dict()
678 self._l7_data[u"server"][u"active_flows"] = None
679 self._l7_data[u"server"][u"established_flows"] = None
680 self._l7_data[u"server"][u"traffic_duration"] = None
681 if u"udp" in self.traffic_profile:
682 self._l7_data[u"client"][u"udp"] = dict()
683 self._l7_data[u"client"][u"udp"][u"connects"] = None
684 self._l7_data[u"client"][u"udp"][u"closed_flows"] = None
685 self._l7_data[u"client"][u"udp"][u"err_cwf"] = None
686 self._l7_data[u"server"][u"udp"] = dict()
687 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = None
688 self._l7_data[u"server"][u"udp"][u"closed_flows"] = None
689 elif u"tcp" in self.traffic_profile:
690 self._l7_data[u"client"][u"tcp"] = dict()
691 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = None
692 self._l7_data[u"client"][u"tcp"][u"connects"] = None
693 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = None
694 self._l7_data[u"client"][u"tcp"][u"connattempt"] = None
695 self._l7_data[u"server"][u"tcp"] = dict()
696 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = None
697 self._l7_data[u"server"][u"tcp"][u"connects"] = None
698 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = None
700 logger.warn(u"Unsupported T-Rex ASTF traffic profile!")
702 for line in stdout.splitlines():
703 if f"Xstats snapshot {index}: " in line:
704 xstats[index] = line[19:]
708 self._xstats = tuple(xstats)
710 self._target_duration = duration
711 self._duration = computed_duration
712 self._parse_traffic_results(stdout)
714 def trex_stl_start_remote_exec(self, duration, rate, async_call=False):
715 """Execute T-Rex STL script on remote node over ssh to start running
718 In sync mode, measurement results are stored internally.
719 In async mode, initial data including xstats are stored internally.
721 Mode-unaware code (e.g. in search algorithms) works with transactions.
722 To keep the logic simple, multiplier is set to that value.
723 As bidirectional traffic profiles send packets in both directions,
724 they are treated as transactions with two packets (one per direction).
726 :param duration: Time expressed in seconds for how long to send traffic.
727 :param rate: Traffic rate in transactions per second.
728 :param async_call: If enabled then don't wait for all incoming traffic.
729 :type duration: float
731 :type async_call: bool
732 :raises RuntimeError: In case of T-Rex driver issue.
734 self.check_mode(TrexMode.STL)
735 p_0, p_1 = (1, 0) if self._ifaces_reordered else (0, 1)
736 if not isinstance(duration, (float, int)):
737 duration = float(duration)
739 # TODO: Refactor the code so duration is computed only once,
740 # and both the initial and the computed durations are logged.
741 duration, _ = self._compute_duration(duration=duration, multiplier=rate)
743 command_line = OptionString().add(u"python3")
744 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
745 command_line.add(f"'{dirname}/trex_stl_profile.py'")
746 command_line.change_prefix(u"--")
747 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
748 command_line.add_with_value(
749 u"profile", f"'{dirname}/{self.traffic_profile}.py'"
751 command_line.add_with_value(u"duration", f"{duration!r}")
752 command_line.add_with_value(u"frame_size", self.frame_size)
753 command_line.add_with_value(u"rate", f"{rate!r}")
754 command_line.add_with_value(u"port_0", p_0)
755 command_line.add_with_value(u"port_1", p_1)
756 command_line.add_with_value(
757 u"traffic_directions", self.traffic_directions
759 command_line.add_if(u"async_start", async_call)
760 command_line.add_if(u"latency", self.use_latency)
761 command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
763 # TODO: This is ugly. Handle parsing better.
764 self._start_time = time.monotonic()
765 self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
766 stdout, _ = exec_cmd_no_error(
767 self._node, command_line, timeout=int(duration) + 60,
768 message=u"T-Rex STL runtime error"
773 self._target_duration = None
774 self._duration = None
775 self._received = None
780 xstats = [None, None]
782 for line in stdout.splitlines():
783 if f"Xstats snapshot {index}: " in line:
784 xstats[index] = line[19:]
788 self._xstats = tuple(xstats)
790 self._target_duration = duration
791 self._duration = duration
792 self._parse_traffic_results(stdout)
794 def send_traffic_on_tg(
802 traffic_directions=2,
803 transaction_duration=0.0,
805 transaction_type=u"packet",
809 ramp_up_duration=None,
813 """Send traffic from all configured interfaces on TG.
815 In async mode, xstats is stored internally,
816 to enable getting correct result when stopping the traffic.
817 In both modes, stdout is returned,
818 but _parse_traffic_results only works in sync output.
820 Note that traffic generator uses DPDK driver which might
821 reorder port numbers based on wiring and PCI numbering.
822 This method handles that, so argument values are invariant,
823 but you can see swapped valued in debug logs.
825 When transaction_scale is specified, the duration value is ignored
826 and the needed time is computed. For cases where this results in
827 to too long measurement (e.g. teardown trial with small rate),
828 duration_limit is applied (of non-zero), so the trial is stopped sooner.
830 Bidirectional STL profiles are treated as transactions with two packets.
832 The return value is None for async.
834 :param duration: Duration of test traffic generation in seconds.
835 :param rate: Traffic rate in transactions per second.
836 :param frame_size: Frame size (L2) in Bytes.
837 :param traffic_profile: Module name as a traffic profile identifier.
838 See GPL/traffic_profiles/trex for implemented modules.
839 :param async_call: Async mode.
840 :param ppta: Packets per transaction, aggregated over directions.
841 Needed for udp_pps which does not have a good transaction counter,
842 so we need to compute expected number of packets.
844 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
846 :param transaction_duration: Total expected time to close transaction.
847 :param transaction_scale: Number of transactions to perform.
848 0 (default) means unlimited.
849 :param transaction_type: An identifier specifying which counters
850 and formulas to use when computing attempted and failed
851 transactions. Default: "packet".
852 :param duration_limit: Zero or maximum limit for computed (or given)
854 :param use_latency: Whether to measure latency during the trial.
856 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
857 :param ramp_up_duration: Duration of ramp-up trials [s].
858 :param state_timeout: Time of life of DUT state [s].
859 :param ramp_up_only: If true, do not perform main trial measurement.
860 :type duration: float
862 :type frame_size: str
863 :type traffic_profile: str
864 :type async_call: bool
866 :type traffic_directions: int
867 :type transaction_duration: float
868 :type transaction_scale: int
869 :type transaction_type: str
870 :type duration_limit: float
871 :type use_latency: bool
872 :type ramp_up_rate: float
873 :type ramp_up_duration: float
874 :type state_timeout: float
875 :type ramp_up_only: bool
876 :returns: TG results.
877 :rtype: ReceiveRateMeasurement or None
878 :raises ValueError: If TG traffic profile is not supported.
880 self.set_rate_provider_defaults(
881 frame_size=frame_size,
882 traffic_profile=traffic_profile,
884 traffic_directions=traffic_directions,
885 transaction_duration=transaction_duration,
886 transaction_scale=transaction_scale,
887 transaction_type=transaction_type,
888 duration_limit=duration_limit,
889 use_latency=use_latency,
890 ramp_up_rate=ramp_up_rate,
891 ramp_up_duration=ramp_up_duration,
892 state_timeout=state_timeout,
894 return self._send_traffic_on_tg_with_ramp_up(
897 async_call=async_call,
898 ramp_up_only=ramp_up_only,
901 def _send_traffic_on_tg_internal(
902 self, duration, rate, async_call=False):
903 """Send traffic from all configured interfaces on TG.
905 This is an internal function, it assumes set_rate_provider_defaults
906 has been called to remember most values.
907 The reason why need to remember various values is that
908 the traffic can be asynchronous, and parsing needs those values.
909 The reason why this is is a separate function from the one
910 which calls set_rate_provider_defaults is that some search algorithms
911 need to specify their own values, and we do not want the measure call
912 to overwrite them with defaults.
914 This function is used both for automated ramp-up trials
915 and for explicitly called trials.
917 :param duration: Duration of test traffic generation in seconds.
918 :param rate: Traffic rate in transactions per second.
919 :param async_call: Async mode.
920 :type duration: float
922 :type async_call: bool
923 :returns: TG results.
924 :rtype: ReceiveRateMeasurement or None
925 :raises ValueError: If TG traffic profile is not supported.
927 subtype = check_subtype(self._node)
928 if subtype == NodeSubTypeTG.TREX:
929 if u"trex-astf" in self.traffic_profile:
930 self.trex_astf_start_remote_exec(
931 duration, float(rate), async_call
933 elif u"trex-stl" in self.traffic_profile:
934 unit_rate_str = str(rate) + u"pps"
935 # TODO: Suport transaction_scale et al?
936 self.trex_stl_start_remote_exec(
937 duration, unit_rate_str, async_call
940 raise ValueError(u"Unsupported T-Rex traffic profile!")
942 return None if async_call else self._get_measurement_result()
944 def _send_traffic_on_tg_with_ramp_up(
945 self, duration, rate, async_call=False, ramp_up_only=False):
946 """Send traffic from all interfaces on TG, maybe after ramp-up.
948 This is an internal function, it assumes set_rate_provider_defaults
949 has been called to remember most values.
950 The reason why need to remember various values is that
951 the traffic can be asynchronous, and parsing needs those values.
952 The reason why this is a separate function from the one
953 which calls set_rate_provider_defaults is that some search algorithms
954 need to specify their own values, and we do not want the measure call
955 to overwrite them with defaults.
957 If ramp-up tracking is detected, a computation is performed,
958 and if state timeout is near, trial at ramp-up rate and duration
959 is inserted before the main trial measurement.
961 The ramp_up_only parameter forces a ramp-up without immediate
962 trial measurement, which is useful in case self remembers
963 a previous ramp-up trial that belongs to a different test (phase).
965 Return None if trial is async or ramp-up only.
967 :param duration: Duration of test traffic generation in seconds.
968 :param rate: Traffic rate in transactions per second.
969 :param async_call: Async mode.
970 :param ramp_up_only: If true, do not perform main trial measurement.
971 :type duration: float
973 :type async_call: bool
974 :type ramp_up_only: bool
975 :returns: TG results.
976 :rtype: ReceiveRateMeasurement or None
977 :raises ValueError: If TG traffic profile is not supported.
980 if self.ramp_up_rate:
981 # Figure out whether we need to insert a ramp-up trial.
982 # TODO: Give up on async_call=True?
983 if ramp_up_only or self.ramp_up_start is None:
984 # We never ramped up yet (at least not in this test case).
985 ramp_up_needed = True
987 # We ramped up before, but maybe it was too long ago.
988 # Adding a constant overhead to be safe.
989 time_now = time.monotonic() + 1.0
990 computed_duration, complete = self._compute_duration(
994 # There are two conditions for inserting ramp-up.
995 # If early sessions are expiring already,
996 # or if late sessions are to expire before measurement is over.
997 ramp_up_start_delay = time_now - self.ramp_up_start
998 ramp_up_stop_delay = time_now - self.ramp_up_stop
999 ramp_up_stop_delay += computed_duration
1000 bigger_delay = max(ramp_up_start_delay, ramp_up_stop_delay)
1001 # Final boolean decision.
1002 ramp_up_needed = (bigger_delay >= self.state_timeout)
1005 u"State may time out during next real trial, "
1006 u"inserting a ramp-up trial."
1008 self.ramp_up_start = time.monotonic()
1009 self._send_traffic_on_tg_internal(
1010 duration=self.ramp_up_duration,
1011 rate=self.ramp_up_rate,
1012 async_call=async_call,
1014 self.ramp_up_stop = time.monotonic()
1015 logger.debug(u"Ramp-up done.")
1018 u"State will probably not time out during next real trial, "
1019 u"no ramp-up trial needed just yet."
1023 trial_start = time.monotonic()
1024 result = self._send_traffic_on_tg_internal(
1027 async_call=async_call,
1029 trial_end = time.monotonic()
1030 if self.ramp_up_rate:
1031 # Optimization: No loss acts as a good ramp-up, if it was complete.
1032 if complete and result is not None and result.loss_count == 0:
1033 logger.debug(u"Good trial acts as a ramp-up")
1034 self.ramp_up_start = trial_start
1035 self.ramp_up_stop = trial_end
1037 logger.debug(u"Loss or incomplete, does not act as a ramp-up.")
1040 def no_traffic_loss_occurred(self):
1041 """Fail if loss occurred in traffic run.
1044 :raises Exception: If loss occured.
1046 if self._loss is None:
1047 raise RuntimeError(u"The traffic generation has not been issued")
1048 if self._loss != u"0":
1049 raise RuntimeError(f"Traffic loss occurred: {self._loss}")
1051 def fail_if_no_traffic_forwarded(self):
1052 """Fail if no traffic forwarded.
1054 TODO: Check number of passed transactions instead.
1057 :raises Exception: If no traffic forwarded.
1059 if self._received is None:
1060 raise RuntimeError(u"The traffic generation has not been issued")
1061 if self._received == 0:
1062 raise RuntimeError(u"No traffic forwarded")
1064 def partial_traffic_loss_accepted(
1065 self, loss_acceptance, loss_acceptance_type):
1066 """Fail if loss is higher then accepted in traffic run.
1068 :param loss_acceptance: Permitted drop ratio or frames count.
1069 :param loss_acceptance_type: Type of permitted loss.
1070 :type loss_acceptance: float
1071 :type loss_acceptance_type: LossAcceptanceType
1073 :raises Exception: If loss is above acceptance criteria.
1075 if self._loss is None:
1076 raise Exception(u"The traffic generation has not been issued")
1078 if loss_acceptance_type == u"percentage":
1079 loss = (float(self._loss) / float(self._sent)) * 100
1080 elif loss_acceptance_type == u"frames":
1081 loss = float(self._loss)
1083 raise Exception(u"Loss acceptance type not supported")
1085 if loss > float(loss_acceptance):
1087 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
1090 def _parse_traffic_results(self, stdout):
1091 """Parse stdout of scripts into fields of self.
1093 Block of code to reuse, by sync start, or stop after async.
1095 :param stdout: Text containing the standard output.
1098 subtype = check_subtype(self._node)
1099 if subtype == NodeSubTypeTG.TREX:
1100 # Last line from console output
1101 line = stdout.splitlines()[-1]
1102 results = line.split(u";")
1103 if results[-1] in (u" ", u""):
1105 self._result = dict()
1106 for result in results:
1107 key, value = result.split(u"=", maxsplit=1)
1108 self._result[key.strip()] = value
1109 logger.info(f"TrafficGen results:\n{self._result}")
1110 self._received = int(self._result.get(u"total_received"), 0)
1111 self._sent = int(self._result.get(u"total_sent", 0))
1112 self._loss = int(self._result.get(u"frame_loss", 0))
1113 self._approximated_duration = \
1114 self._result.get(u"approximated_duration", 0.0)
1115 if u"manual" not in str(self._approximated_duration):
1116 self._approximated_duration = float(self._approximated_duration)
1117 self._latency = list()
1118 self._latency.append(self._result.get(u"latency_stream_0(usec)"))
1119 self._latency.append(self._result.get(u"latency_stream_1(usec)"))
1120 if self._mode == TrexMode.ASTF:
1121 self._l7_data = dict()
1122 self._l7_data[u"client"] = dict()
1123 self._l7_data[u"client"][u"sent"] = \
1124 int(self._result.get(u"client_sent", 0))
1125 self._l7_data[u"client"][u"received"] = \
1126 int(self._result.get(u"client_received", 0))
1127 self._l7_data[u"client"][u"active_flows"] = \
1128 int(self._result.get(u"client_active_flows", 0))
1129 self._l7_data[u"client"][u"established_flows"] = \
1130 int(self._result.get(u"client_established_flows", 0))
1131 self._l7_data[u"client"][u"traffic_duration"] = \
1132 float(self._result.get(u"client_traffic_duration", 0.0))
1133 self._l7_data[u"client"][u"err_rx_throttled"] = \
1134 int(self._result.get(u"client_err_rx_throttled", 0))
1135 self._l7_data[u"client"][u"err_c_nf_throttled"] = \
1136 int(self._result.get(u"client_err_nf_throttled", 0))
1137 self._l7_data[u"client"][u"err_flow_overflow"] = \
1138 int(self._result.get(u"client_err_flow_overflow", 0))
1139 self._l7_data[u"server"] = dict()
1140 self._l7_data[u"server"][u"active_flows"] = \
1141 int(self._result.get(u"server_active_flows", 0))
1142 self._l7_data[u"server"][u"established_flows"] = \
1143 int(self._result.get(u"server_established_flows", 0))
1144 self._l7_data[u"server"][u"traffic_duration"] = \
1145 float(self._result.get(u"server_traffic_duration", 0.0))
1146 self._l7_data[u"server"][u"err_rx_throttled"] = \
1147 int(self._result.get(u"client_err_rx_throttled", 0))
1148 if u"udp" in self.traffic_profile:
1149 self._l7_data[u"client"][u"udp"] = dict()
1150 self._l7_data[u"client"][u"udp"][u"connects"] = \
1151 int(self._result.get(u"client_udp_connects", 0))
1152 self._l7_data[u"client"][u"udp"][u"closed_flows"] = \
1153 int(self._result.get(u"client_udp_closed", 0))
1154 self._l7_data[u"client"][u"udp"][u"tx_bytes"] = \
1155 int(self._result.get(u"client_udp_tx_bytes", 0))
1156 self._l7_data[u"client"][u"udp"][u"rx_bytes"] = \
1157 int(self._result.get(u"client_udp_rx_bytes", 0))
1158 self._l7_data[u"client"][u"udp"][u"tx_packets"] = \
1159 int(self._result.get(u"client_udp_tx_packets", 0))
1160 self._l7_data[u"client"][u"udp"][u"rx_packets"] = \
1161 int(self._result.get(u"client_udp_rx_packets", 0))
1162 self._l7_data[u"client"][u"udp"][u"keep_drops"] = \
1163 int(self._result.get(u"client_udp_keep_drops", 0))
1164 self._l7_data[u"client"][u"udp"][u"err_cwf"] = \
1165 int(self._result.get(u"client_err_cwf", 0))
1166 self._l7_data[u"server"][u"udp"] = dict()
1167 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = \
1168 int(self._result.get(u"server_udp_accepts", 0))
1169 self._l7_data[u"server"][u"udp"][u"closed_flows"] = \
1170 int(self._result.get(u"server_udp_closed", 0))
1171 self._l7_data[u"server"][u"udp"][u"tx_bytes"] = \
1172 int(self._result.get(u"server_udp_tx_bytes", 0))
1173 self._l7_data[u"server"][u"udp"][u"rx_bytes"] = \
1174 int(self._result.get(u"server_udp_rx_bytes", 0))
1175 self._l7_data[u"server"][u"udp"][u"tx_packets"] = \
1176 int(self._result.get(u"server_udp_tx_packets", 0))
1177 self._l7_data[u"server"][u"udp"][u"rx_packets"] = \
1178 int(self._result.get(u"server_udp_rx_packets", 0))
1179 elif u"tcp" in self.traffic_profile:
1180 self._l7_data[u"client"][u"tcp"] = dict()
1181 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = \
1182 int(self._result.get(u"client_tcp_connect_inits", 0))
1183 self._l7_data[u"client"][u"tcp"][u"connects"] = \
1184 int(self._result.get(u"client_tcp_connects", 0))
1185 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = \
1186 int(self._result.get(u"client_tcp_closed", 0))
1187 self._l7_data[u"client"][u"tcp"][u"connattempt"] = \
1188 int(self._result.get(u"client_tcp_connattempt", 0))
1189 self._l7_data[u"client"][u"tcp"][u"tx_bytes"] = \
1190 int(self._result.get(u"client_tcp_tx_bytes", 0))
1191 self._l7_data[u"client"][u"tcp"][u"rx_bytes"] = \
1192 int(self._result.get(u"client_tcp_rx_bytes", 0))
1193 self._l7_data[u"server"][u"tcp"] = dict()
1194 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = \
1195 int(self._result.get(u"server_tcp_accepts", 0))
1196 self._l7_data[u"server"][u"tcp"][u"connects"] = \
1197 int(self._result.get(u"server_tcp_connects", 0))
1198 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = \
1199 int(self._result.get(u"server_tcp_closed", 0))
1200 self._l7_data[u"server"][u"tcp"][u"tx_bytes"] = \
1201 int(self._result.get(u"server_tcp_tx_bytes", 0))
1202 self._l7_data[u"server"][u"tcp"][u"rx_bytes"] = \
1203 int(self._result.get(u"server_tcp_rx_bytes", 0))
1205 def _get_measurement_result(self):
1206 """Return the result of last measurement as ReceiveRateMeasurement.
1208 Separate function, as measurements can end either by time
1209 or by explicit call, this is the common block at the end.
1211 The target_tr field of ReceiveRateMeasurement is in
1212 transactions per second. Transmit count and loss count units
1213 depend on the transaction type. Usually they are in transactions
1214 per second, or aggregate packets per second.
1216 TODO: Fail on running or already reported measurement.
1218 :returns: Structure containing the result of the measurement.
1219 :rtype: ReceiveRateMeasurement
1222 # Client duration seems to include a setup period
1223 # where TRex does not send any packets yet.
1224 # Server duration does not include it.
1225 server_data = self._l7_data[u"server"]
1226 approximated_duration = float(server_data[u"traffic_duration"])
1227 except (KeyError, AttributeError, ValueError, TypeError):
1228 approximated_duration = None
1230 if not approximated_duration:
1231 approximated_duration = float(self._approximated_duration)
1232 except ValueError: # "manual"
1233 approximated_duration = None
1234 if not approximated_duration:
1235 if self._duration and self._duration > 0:
1236 # Known recomputed or target duration.
1237 approximated_duration = self._duration
1239 # It was an explicit stop.
1240 if not self._stop_time:
1241 raise RuntimeError(u"Unable to determine duration.")
1242 approximated_duration = self._stop_time - self._start_time
1243 target_duration = self._target_duration
1244 if not target_duration:
1245 target_duration = approximated_duration
1246 transmit_rate = self._rate
1247 if self.transaction_type == u"packet":
1248 partial_attempt_count = self._sent
1249 expected_attempt_count = self._sent
1250 fail_count = self._loss
1251 elif self.transaction_type == u"udp_cps":
1252 if not self.transaction_scale:
1253 raise RuntimeError(u"Add support for no-limit udp_cps.")
1254 partial_attempt_count = self._l7_data[u"client"][u"sent"]
1255 # We do not care whether TG is slow, it should have attempted all.
1256 expected_attempt_count = self.transaction_scale
1257 pass_count = self._l7_data[u"client"][u"received"]
1258 fail_count = expected_attempt_count - pass_count
1259 elif self.transaction_type == u"tcp_cps":
1260 if not self.transaction_scale:
1261 raise RuntimeError(u"Add support for no-limit tcp_cps.")
1262 ctca = self._l7_data[u"client"][u"tcp"][u"connattempt"]
1263 partial_attempt_count = ctca
1264 # We do not care whether TG is slow, it should have attempted all.
1265 expected_attempt_count = self.transaction_scale
1266 # From TCP point of view, server/connects counts full connections,
1267 # but we are testing NAT session so client/connects counts that
1268 # (half connections from TCP point of view).
1269 pass_count = self._l7_data[u"client"][u"tcp"][u"connects"]
1270 fail_count = expected_attempt_count - pass_count
1271 elif self.transaction_type == u"udp_pps":
1272 if not self.transaction_scale:
1273 raise RuntimeError(u"Add support for no-limit udp_pps.")
1274 partial_attempt_count = self._sent
1275 expected_attempt_count = self.transaction_scale * self.ppta
1276 fail_count = self._loss + (expected_attempt_count - self._sent)
1277 elif self.transaction_type == u"tcp_pps":
1278 if not self.transaction_scale:
1279 raise RuntimeError(u"Add support for no-limit tcp_pps.")
1280 partial_attempt_count = self._sent
1281 expected_attempt_count = self.transaction_scale * self.ppta
1282 # One loss-like scenario happens when TRex receives all packets
1283 # on L2 level, but is not fast enough to process them all
1284 # at L7 level, which leads to retransmissions.
1285 # Those manifest as opackets larger than expected.
1286 # A simple workaround is to add absolute difference.
1287 # Probability of retransmissions exactly cancelling
1288 # packets unsent due to duration stretching is quite low.
1289 fail_count = self._loss + abs(expected_attempt_count - self._sent)
1291 raise RuntimeError(f"Unknown parsing {self.transaction_type!r}")
1292 if fail_count < 0 and not self.negative_loss:
1294 measurement = ReceiveRateMeasurement(
1295 duration=target_duration,
1296 target_tr=transmit_rate,
1297 transmit_count=expected_attempt_count,
1298 loss_count=fail_count,
1299 approximated_duration=approximated_duration,
1300 partial_transmit_count=partial_attempt_count,
1302 measurement.latency = self.get_latency_int()
1305 def measure(self, duration, transmit_rate):
1306 """Run trial measurement, parse and return results.
1308 The input rate is for transactions. Stateles bidirectional traffic
1309 is understood as sequence of (asynchronous) transactions,
1312 The result units depend on test type, generally
1313 the count either transactions or packets (aggregated over directions).
1315 Optionally, this method sleeps if measurement finished before
1316 the time specified as duration.
1318 :param duration: Trial duration [s].
1319 :param transmit_rate: Target rate in transactions per second.
1320 :type duration: float
1321 :type transmit_rate: float
1322 :returns: Structure containing the result of the measurement.
1323 :rtype: ReceiveRateMeasurement
1324 :raises RuntimeError: If TG is not set or if node is not TG
1325 or if subtype is not specified.
1326 :raises NotImplementedError: If TG is not supported.
1328 duration = float(duration)
1329 time_start = time.monotonic()
1330 time_stop = time_start + duration
1333 result = self._send_traffic_on_tg_with_ramp_up(
1338 logger.debug(f"trial measurement result: {result!r}")
1339 # In PLRsearch, computation needs the specified time to complete.
1340 if self.sleep_till_duration:
1341 sleeptime = time_stop - time.monotonic()
1343 # TODO: Sometimes we have time to do additional trials here,
1344 # adapt PLRsearch to accept all the results.
1345 time.sleep(sleeptime)
1348 def set_rate_provider_defaults(
1354 traffic_directions=2,
1355 transaction_duration=0.0,
1356 transaction_scale=0,
1357 transaction_type=u"packet",
1360 sleep_till_duration=False,
1363 ramp_up_duration=None,
1364 state_timeout=300.0,
1366 """Store values accessed by measure().
1368 :param frame_size: Frame size identifier or value [B].
1369 :param traffic_profile: Module name as a traffic profile identifier.
1370 See GPL/traffic_profiles/trex for implemented modules.
1371 :param ppta: Packets per transaction, aggregated over directions.
1372 Needed for udp_pps which does not have a good transaction counter,
1373 so we need to compute expected number of packets.
1375 :param resetter: Callable to reset DUT state for repeated trials.
1376 :param traffic_directions: Traffic from packet counting point of view
1377 is bi- (2) or uni- (1) directional.
1379 :param transaction_duration: Total expected time to close transaction.
1380 :param transaction_scale: Number of transactions to perform.
1381 0 (default) means unlimited.
1382 :param transaction_type: An identifier specifying which counters
1383 and formulas to use when computing attempted and failed
1384 transactions. Default: "packet".
1385 TODO: Does this also specify parsing for the measured duration?
1386 :param duration_limit: Zero or maximum limit for computed (or given)
1388 :param negative_loss: If false, negative loss is reported as zero loss.
1389 :param sleep_till_duration: If true and measurement returned faster,
1390 sleep until it matches duration. Needed for PLRsearch.
1391 :param use_latency: Whether to measure latency during the trial.
1393 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1394 :param ramp_up_duration: Duration of ramp-up trials [s].
1395 :param state_timeout: Time of life of DUT state [s].
1396 :type frame_size: str or int
1397 :type traffic_profile: str
1399 :type resetter: Optional[Callable[[], None]]
1400 :type traffic_directions: int
1401 :type transaction_duration: float
1402 :type transaction_scale: int
1403 :type transaction_type: str
1404 :type duration_limit: float
1405 :type negative_loss: bool
1406 :type sleep_till_duration: bool
1407 :type use_latency: bool
1408 :type ramp_up_rate: float
1409 :type ramp_up_duration: float
1410 :type state_timeout: float
1412 self.frame_size = frame_size
1413 self.traffic_profile = str(traffic_profile)
1414 self.resetter = resetter
1416 self.traffic_directions = int(traffic_directions)
1417 self.transaction_duration = float(transaction_duration)
1418 self.transaction_scale = int(transaction_scale)
1419 self.transaction_type = str(transaction_type)
1420 self.duration_limit = float(duration_limit)
1421 self.negative_loss = bool(negative_loss)
1422 self.sleep_till_duration = bool(sleep_till_duration)
1423 self.use_latency = bool(use_latency)
1424 self.ramp_up_rate = float(ramp_up_rate)
1425 self.ramp_up_duration = float(ramp_up_duration)
1426 self.state_timeout = float(state_timeout)
1429 class OptimizedSearch:
1430 """Class to be imported as Robot Library, containing search keywords.
1432 Aside of setting up measurer and forwarding arguments,
1433 the main business is to translate min/max rate from unidir to aggregate.
1437 def perform_optimized_ndrpdr_search(
1440 minimum_transmit_rate,
1441 maximum_transmit_rate,
1442 packet_loss_ratio=0.005,
1443 final_relative_width=0.005,
1444 final_trial_duration=30.0,
1445 initial_trial_duration=1.0,
1446 number_of_intermediate_phases=2,
1450 traffic_directions=2,
1451 transaction_duration=0.0,
1452 transaction_scale=0,
1453 transaction_type=u"packet",
1456 ramp_up_duration=None,
1457 state_timeout=300.0,
1458 expansion_coefficient=4.0,
1460 """Setup initialized TG, perform optimized search, return intervals.
1462 If transaction_scale is nonzero, all init and non-init trial durations
1463 are set to 1.0 (as they do not affect the real trial duration)
1464 and zero intermediate phases are used.
1465 This way no re-measurement happens.
1466 Warmup has to be handled via resetter or ramp-up mechanisms.
1468 :param frame_size: Frame size identifier or value [B].
1469 :param traffic_profile: Module name as a traffic profile identifier.
1470 See GPL/traffic_profiles/trex for implemented modules.
1471 :param minimum_transmit_rate: Minimal load in transactions per second.
1472 :param maximum_transmit_rate: Maximal load in transactions per second.
1473 :param packet_loss_ratio: Ratio of packets lost, for PDR [1].
1474 :param final_relative_width: Final lower bound transmit rate
1475 cannot be more distant that this multiple of upper bound [1].
1476 :param final_trial_duration: Trial duration for the final phase [s].
1477 :param initial_trial_duration: Trial duration for the initial phase
1478 and also for the first intermediate phase [s].
1479 :param number_of_intermediate_phases: Number of intermediate phases
1480 to perform before the final phase [1].
1481 :param timeout: The search will fail itself when not finished
1482 before this overall time [s].
1483 :param ppta: Packets per transaction, aggregated over directions.
1484 Needed for udp_pps which does not have a good transaction counter,
1485 so we need to compute expected number of packets.
1487 :param resetter: Callable to reset DUT state for repeated trials.
1488 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1490 :param transaction_duration: Total expected time to close transaction.
1491 :param transaction_scale: Number of transactions to perform.
1492 0 (default) means unlimited.
1493 :param transaction_type: An identifier specifying which counters
1494 and formulas to use when computing attempted and failed
1495 transactions. Default: "packet".
1496 :param use_latency: Whether to measure latency during the trial.
1498 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1499 :param ramp_up_duration: Duration of ramp-up trials [s].
1500 :param state_timeout: Time of life of DUT state [s].
1501 :param expansion_coefficient: In external search multiply width by this.
1502 :type frame_size: str or int
1503 :type traffic_profile: str
1504 :type minimum_transmit_rate: float
1505 :type maximum_transmit_rate: float
1506 :type packet_loss_ratio: float
1507 :type final_relative_width: float
1508 :type final_trial_duration: float
1509 :type initial_trial_duration: float
1510 :type number_of_intermediate_phases: int
1511 :type timeout: float
1513 :type resetter: Optional[Callable[[], None]]
1514 :type traffic_directions: int
1515 :type transaction_duration: float
1516 :type transaction_scale: int
1517 :type transaction_type: str
1518 :type use_latency: bool
1519 :type ramp_up_rate: float
1520 :type ramp_up_duration: float
1521 :type state_timeout: float
1522 :type expansion_coefficient: float
1523 :returns: Structure containing narrowed down NDR and PDR intervals
1524 and their measurements.
1525 :rtype: List[Receiverateinterval]
1526 :raises RuntimeError: If total duration is larger than timeout.
1528 # we need instance of TrafficGenerator instantiated by Robot Framework
1529 # to be able to use trex_stl-*()
1530 tg_instance = BuiltIn().get_library_instance(
1531 u"resources.libraries.python.TrafficGenerator"
1533 # Overrides for fixed transaction amount.
1534 # TODO: Move to robot code? We have two call sites, so this saves space,
1535 # even though this is surprising for log readers.
1536 if transaction_scale:
1537 initial_trial_duration = 1.0
1538 final_trial_duration = 1.0
1539 number_of_intermediate_phases = 0
1540 timeout += transaction_scale * 3e-4
1541 tg_instance.set_rate_provider_defaults(
1542 frame_size=frame_size,
1543 traffic_profile=traffic_profile,
1544 sleep_till_duration=False,
1547 traffic_directions=traffic_directions,
1548 transaction_duration=transaction_duration,
1549 transaction_scale=transaction_scale,
1550 transaction_type=transaction_type,
1551 use_latency=use_latency,
1552 ramp_up_rate=ramp_up_rate,
1553 ramp_up_duration=ramp_up_duration,
1554 state_timeout=state_timeout,
1556 algorithm = MultipleLossRatioSearch(
1557 measurer=tg_instance,
1558 final_trial_duration=final_trial_duration,
1559 final_relative_width=final_relative_width,
1560 number_of_intermediate_phases=number_of_intermediate_phases,
1561 initial_trial_duration=initial_trial_duration,
1564 expansion_coefficient=expansion_coefficient,
1566 if packet_loss_ratio:
1567 packet_loss_ratios = [0.0, packet_loss_ratio]
1569 # Happens in reconf tests.
1570 packet_loss_ratios = [packet_loss_ratio]
1571 results = algorithm.narrow_down_intervals(
1572 min_rate=minimum_transmit_rate,
1573 max_rate=maximum_transmit_rate,
1574 packet_loss_ratios=packet_loss_ratios,
1579 def perform_soak_search(
1582 minimum_transmit_rate,
1583 maximum_transmit_rate,
1590 trace_enabled=False,
1591 traffic_directions=2,
1592 transaction_duration=0.0,
1593 transaction_scale=0,
1594 transaction_type=u"packet",
1597 ramp_up_duration=None,
1598 state_timeout=300.0,
1600 """Setup initialized TG, perform soak search, return avg and stdev.
1602 :param frame_size: Frame size identifier or value [B].
1603 :param traffic_profile: Module name as a traffic profile identifier.
1604 See GPL/traffic_profiles/trex for implemented modules.
1605 :param minimum_transmit_rate: Minimal load in transactions per second.
1606 :param maximum_transmit_rate: Maximal load in transactions per second.
1607 :param plr_target: Ratio of packets lost to achieve [1].
1608 :param tdpt: Trial duration per trial.
1609 The algorithm linearly increases trial duration with trial number,
1610 this is the increment between succesive trials, in seconds.
1611 :param initial_count: Offset to apply before the first trial.
1612 For example initial_count=50 makes first trial to be 51*tdpt long.
1613 This is needed because initial "search" phase of integrator
1614 takes significant time even without any trial results.
1615 :param timeout: The search will stop after this overall time [s].
1616 :param ppta: Packets per transaction, aggregated over directions.
1617 Needed for udp_pps which does not have a good transaction counter,
1618 so we need to compute expected number of packets.
1620 :param resetter: Callable to reset DUT state for repeated trials.
1621 :param trace_enabled: True if trace enabled else False.
1622 This is very verbose tracing on numeric computations,
1623 do not use in production.
1625 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1627 :param transaction_duration: Total expected time to close transaction.
1628 :param transaction_scale: Number of transactions to perform.
1629 0 (default) means unlimited.
1630 :param transaction_type: An identifier specifying which counters
1631 and formulas to use when computing attempted and failed
1632 transactions. Default: "packet".
1633 :param use_latency: Whether to measure latency during the trial.
1635 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1636 :param ramp_up_duration: Duration of ramp-up trials [s].
1637 :param state_timeout: Time of life of DUT state [s].
1638 :type frame_size: str or int
1639 :type traffic_profile: str
1640 :type minimum_transmit_rate: float
1641 :type maximum_transmit_rate: float
1642 :type plr_target: float
1643 :type initial_count: int
1644 :type timeout: float
1646 :type resetter: Optional[Callable[[], None]]
1647 :type trace_enabled: bool
1648 :type traffic_directions: int
1649 :type transaction_duration: float
1650 :type transaction_scale: int
1651 :type transaction_type: str
1652 :type use_latency: bool
1653 :type ramp_up_rate: float
1654 :type ramp_up_duration: float
1655 :type state_timeout: float
1656 :returns: Average and stdev of estimated aggregate rate giving PLR.
1657 :rtype: 2-tuple of float
1659 tg_instance = BuiltIn().get_library_instance(
1660 u"resources.libraries.python.TrafficGenerator"
1662 # Overrides for fixed transaction amount.
1663 # TODO: Move to robot code? We have a single call site
1664 # but MLRsearch has two and we want the two to be used similarly.
1665 if transaction_scale:
1666 # TODO: What is a good value for max scale?
1667 # TODO: Scale the timeout with transaction scale.
1669 tg_instance.set_rate_provider_defaults(
1670 frame_size=frame_size,
1671 traffic_profile=traffic_profile,
1672 negative_loss=False,
1673 sleep_till_duration=True,
1676 traffic_directions=traffic_directions,
1677 transaction_duration=transaction_duration,
1678 transaction_scale=transaction_scale,
1679 transaction_type=transaction_type,
1680 use_latency=use_latency,
1681 ramp_up_rate=ramp_up_rate,
1682 ramp_up_duration=ramp_up_duration,
1683 state_timeout=state_timeout,
1685 algorithm = PLRsearch(
1686 measurer=tg_instance,
1687 trial_duration_per_trial=tdpt,
1688 packet_loss_ratio_target=plr_target,
1689 trial_number_offset=initial_count,
1691 trace_enabled=trace_enabled,
1693 result = algorithm.search(
1694 min_rate=minimum_transmit_rate,
1695 max_rate=maximum_transmit_rate,