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."""
19 from robot.api import logger
20 from robot.libraries.BuiltIn import BuiltIn
22 from .Constants import Constants
23 from .CpuUtils import CpuUtils
24 from .DropRateSearch import DropRateSearch
25 from .MLRsearch.AbstractMeasurer import AbstractMeasurer
26 from .MLRsearch.MultipleLossRatioSearch import MultipleLossRatioSearch
27 from .MLRsearch.ReceiveRateMeasurement import ReceiveRateMeasurement
28 from .PLRsearch.PLRsearch import PLRsearch
29 from .OptionString import OptionString
30 from .ssh import exec_cmd_no_error, exec_cmd
31 from .topology import NodeType
32 from .topology import NodeSubTypeTG
33 from .topology import Topology
35 __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"]
38 def check_subtype(node):
39 """Return supported subtype of given node, or raise an exception.
41 Currently only one subtype is supported,
42 but we want our code to be ready for other ones.
44 :param node: Topology node to check. Can be None.
45 :type node: dict or NoneType
46 :returns: Subtype detected.
48 :raises RuntimeError: If node is not supported, message explains how.
50 if node.get(u"type") is None:
51 msg = u"Node type is not defined"
52 elif node[u"type"] != NodeType.TG:
53 msg = f"Node type is {node[u'type']!r}, not a TG"
54 elif node.get(u"subtype") is None:
55 msg = u"TG subtype is not defined"
56 elif node[u"subtype"] != NodeSubTypeTG.TREX:
57 msg = f"TG subtype {node[u'subtype']!r} is not supported"
59 return NodeSubTypeTG.TREX
60 raise RuntimeError(msg)
63 class TGDropRateSearchImpl(DropRateSearch):
64 """Drop Rate Search implementation."""
67 # super(TGDropRateSearchImpl, self).__init__()
70 self, rate, frame_size, loss_acceptance, loss_acceptance_type,
72 """Runs the traffic and evaluate the measured results.
74 :param rate: Offered traffic load.
75 :param frame_size: Size of frame.
76 :param loss_acceptance: Permitted drop ratio or frames count.
77 :param loss_acceptance_type: Type of permitted loss.
78 :param traffic_profile: Module name as a traffic profile identifier.
79 See GPL/traffic_profiles/trex for implemented modules.
82 :type loss_acceptance: float
83 :type loss_acceptance_type: LossAcceptanceType
84 :type traffic_profile: str
85 :returns: Drop threshold exceeded? (True/False)
87 :raises NotImplementedError: If TG is not supported.
88 :raises RuntimeError: If TG is not specified.
90 # we need instance of TrafficGenerator instantiated by Robot Framework
91 # to be able to use trex_stl-*()
92 tg_instance = BuiltIn().get_library_instance(
93 u"resources.libraries.python.TrafficGenerator"
95 subtype = check_subtype(tg_instance.node)
96 if subtype == NodeSubTypeTG.TREX:
97 unit_rate = str(rate) + self.get_rate_type_str()
98 tg_instance.trex_stl_start_remote_exec(
99 self.get_duration(), unit_rate, frame_size, traffic_profile
101 loss = tg_instance.get_loss()
102 sent = tg_instance.get_sent()
103 if self.loss_acceptance_type_is_percentage():
104 loss = (float(loss) / float(sent)) * 100
106 f"comparing: {loss} < {loss_acceptance} {loss_acceptance_type}"
108 return float(loss) <= float(loss_acceptance)
111 def get_latency(self):
112 """Returns min/avg/max latency.
114 :returns: Latency stats.
117 tg_instance = BuiltIn().get_library_instance(
118 u"resources.libraries.python.TrafficGenerator"
120 return tg_instance.get_latency_int()
124 """Defines mode of T-Rex traffic generator."""
125 # Advanced stateful mode
131 # TODO: Pylint says too-many-instance-attributes.
132 class TrafficGenerator(AbstractMeasurer):
133 """Traffic Generator."""
135 # TODO: Remove "trex" from lines which could work with other TGs.
137 # Use one instance of TrafficGenerator for all tests in test suite
138 ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
141 # TODO: Separate into few dataclasses/dicts.
142 # Pylint dislikes large unstructured state, and it is right.
145 # TG interface order mapping
146 self._ifaces_reordered = False
147 # Result holding fields, to be removed.
152 self._received = None
153 self._approximated_rate = None
154 self._approximated_duration = None
156 # Measurement input fields, needed for async stop result.
157 self._start_time = None
158 self._stop_time = None
160 self._target_duration = None
161 self._duration = None
162 # Other input parameters, not knowable from measure() signature.
163 self.frame_size = None
164 self.traffic_profile = None
165 self.traffic_directions = None
166 self.negative_loss = None
167 self.use_latency = None
170 self.transaction_scale = None
171 self.transaction_duration = None
172 self.sleep_till_duration = None
173 self.transaction_type = None
174 self.duration_limit = None
175 self.ramp_up_start = None
176 self.ramp_up_stop = None
177 self.ramp_up_rate = None
178 self.ramp_up_duration = None
179 self.state_timeout = None
180 # Transient data needed for async measurements.
181 self._xstats = (None, None)
182 # TODO: Rename "xstats" to something opaque, so T-Rex is not privileged?
188 :returns: Traffic generator node.
194 """Return number of lost packets.
196 :returns: Number of lost packets.
202 """Return number of sent packets.
204 :returns: Number of sent packets.
209 def get_received(self):
210 """Return number of received packets.
212 :returns: Number of received packets.
215 return self._received
217 def get_latency_int(self):
218 """Return rounded min/avg/max latency.
220 :returns: Latency stats.
225 def get_approximated_rate(self):
226 """Return approximated rate computed as ratio of transmitted packets
227 over duration of trial.
229 :returns: Approximated rate.
232 return self._approximated_rate
234 def get_l7_data(self):
237 :returns: Number of received packets.
242 def check_mode(self, expected_mode):
245 :param expected_mode: Expected traffic generator mode.
246 :type expected_mode: object
247 :raises RuntimeError: In case of unexpected TG mode.
249 if self._mode == expected_mode:
252 f"{self._node[u'subtype']} not running in {expected_mode} mode!"
255 # TODO: pylint says disable=too-many-locals.
256 def initialize_traffic_generator(
257 self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
258 tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
259 tg_if2_dst_mac=None):
260 """TG initialization.
262 TODO: Document why do we need (and how do we use) _ifaces_reordered.
264 :param tg_node: Traffic generator node.
265 :param tg_if1: TG - name of first interface.
266 :param tg_if2: TG - name of second interface.
267 :param tg_if1_adj_node: TG if1 adjecent node.
268 :param tg_if1_adj_if: TG if1 adjecent interface.
269 :param tg_if2_adj_node: TG if2 adjecent node.
270 :param tg_if2_adj_if: TG if2 adjecent interface.
271 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
272 :param tg_if1_dst_mac: Interface 1 destination MAC address.
273 :param tg_if2_dst_mac: Interface 2 destination MAC address.
277 :type tg_if1_adj_node: dict
278 :type tg_if1_adj_if: str
279 :type tg_if2_adj_node: dict
280 :type tg_if2_adj_if: str
282 :type tg_if1_dst_mac: str
283 :type tg_if2_dst_mac: str
285 :raises RuntimeError: In case of issue during initialization.
287 subtype = check_subtype(tg_node)
288 if subtype == NodeSubTypeTG.TREX:
290 self._mode = TrexMode.ASTF if osi_layer == u"L7" else TrexMode.STL
293 if1[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if1)
294 if2[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if2)
295 if1[u"addr"] = Topology().get_interface_mac(self._node, tg_if1)
296 if2[u"addr"] = Topology().get_interface_mac(self._node, tg_if2)
298 if osi_layer == u"L2":
299 if1[u"adj_addr"] = if2[u"addr"]
300 if2[u"adj_addr"] = if1[u"addr"]
301 elif osi_layer in (u"L3", u"L7"):
302 if1[u"adj_addr"] = Topology().get_interface_mac(
303 tg_if1_adj_node, tg_if1_adj_if
305 if2[u"adj_addr"] = Topology().get_interface_mac(
306 tg_if2_adj_node, tg_if2_adj_if
309 raise ValueError(u"Unknown OSI layer!")
311 # in case of switched environment we can override MAC addresses
312 if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
313 if1[u"adj_addr"] = tg_if1_dst_mac
314 if2[u"adj_addr"] = tg_if2_dst_mac
316 if min(if1[u"pci"], if2[u"pci"]) != if1[u"pci"]:
318 self._ifaces_reordered = True
320 master_thread_id, latency_thread_id, socket, threads = \
321 CpuUtils.get_affinity_trex(
322 self._node, tg_if1, tg_if2,
323 tg_dtc=Constants.TREX_CORE_COUNT)
325 if osi_layer in (u"L2", u"L3", u"L7"):
328 f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
330 f" c: {len(threads)}\n"
331 f" limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
332 f" interfaces: [\"{if1[u'pci']}\",\"{if2[u'pci']}\"]\n"
334 f" - dest_mac: \'{if1[u'adj_addr']}\'\n"
335 f" src_mac: \'{if1[u'addr']}\'\n"
336 f" - dest_mac: \'{if2[u'adj_addr']}\'\n"
337 f" src_mac: \'{if2[u'addr']}\'\n"
339 f" master_thread_id: {master_thread_id}\n"
340 f" latency_thread_id: {latency_thread_id}\n"
342 f" - socket: {socket}\n"
343 f" threads: {threads}\n"
345 sudo=True, message=u"T-Rex config generation!"
348 if Constants.TREX_RX_DESCRIPTORS_COUNT != 0:
351 f"sh -c 'cat << EOF >> /etc/trex_cfg.yaml\n"
352 f" rx_desc: {Constants.TREX_RX_DESCRIPTORS_COUNT}\n"
354 sudo=True, message=u"T-Rex rx_desc modification!"
357 if Constants.TREX_TX_DESCRIPTORS_COUNT != 0:
360 f"sh -c 'cat << EOF >> /etc/trex_cfg.yaml\n"
361 f" tx_desc: {Constants.TREX_TX_DESCRIPTORS_COUNT}\n"
363 sudo=True, message=u"T-Rex tx_desc modification!"
366 raise ValueError(u"Unknown OSI layer!")
368 TrafficGenerator.startup_trex(
369 self._node, osi_layer, subtype=subtype
373 def startup_trex(tg_node, osi_layer, subtype=None):
374 """Startup sequence for the TRex traffic generator.
376 :param tg_node: Traffic generator node.
377 :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type.
378 :param subtype: Traffic generator sub-type.
381 :type subtype: NodeSubTypeTG
382 :raises RuntimeError: If T-Rex startup failed.
383 :raises ValueError: If OSI layer is not supported.
386 subtype = check_subtype(tg_node)
387 if subtype == NodeSubTypeTG.TREX:
388 for _ in range(0, 3):
389 # Kill TRex only if it is already running.
390 cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\""
392 tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
397 for port in tg_node[u"interfaces"].values():
398 if u'Mellanox' not in port.get(u'model'):
399 ports += f" {port.get(u'pci_address')}"
401 cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \
402 f"./dpdk_nic_bind.py -u {ports} || true\""
404 tg_node, cmd, sudo=True,
405 message=u"Unbind PCI ports from driver failed!"
409 cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
410 trex_cmd = OptionString([u"nohup", u"./t-rex-64"])
412 trex_cmd.add(u"--prefix $(hostname)")
413 trex_cmd.add(u"--hdrh")
414 trex_cmd.add(u"--no-scapy-server")
415 trex_cmd.add_if(u"--astf", osi_layer == u"L7")
416 # OptionString does not create double space if extra is empty.
417 trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}")
418 inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &"
419 cmd = f"sh -c \"{inner_command}\" > /dev/null"
421 exec_cmd_no_error(tg_node, cmd, sudo=True)
423 cmd = u"sh -c \"cat /tmp/trex.log\""
425 tg_node, cmd, sudo=True,
426 message=u"Get TRex logs failed!"
428 raise RuntimeError(u"Start TRex failed!")
430 # Test T-Rex API responsiveness.
431 cmd = f"python3 {Constants.REMOTE_FW_DIR}/GPL/tools/trex/"
432 if osi_layer in (u"L2", u"L3"):
433 cmd += u"trex_stl_assert.py"
434 elif osi_layer == u"L7":
435 cmd += u"trex_astf_assert.py"
437 raise ValueError(u"Unknown OSI layer!")
440 tg_node, cmd, sudo=True,
441 message=u"T-Rex API is not responding!", retries=20
446 # After max retries TRex is still not responding to API critical
448 exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True)
449 raise RuntimeError(u"Start T-Rex failed after multiple retries!")
452 def is_trex_running(node):
453 """Check if T-Rex is running using pidof.
455 :param node: Traffic generator node.
457 :returns: True if T-Rex is running otherwise False.
460 ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True)
461 return bool(int(ret) == 0)
464 def teardown_traffic_generator(node):
467 :param node: Traffic generator node.
470 :raises RuntimeError: If node type is not a TG,
471 or if T-Rex teardown fails.
473 subtype = check_subtype(node)
474 if subtype == NodeSubTypeTG.TREX:
478 u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"",
480 message=u"T-Rex kill failed!"
483 def trex_astf_stop_remote_exec(self, node):
484 """Execute T-Rex ASTF script on remote node over ssh to stop running
487 Internal state is updated with measurement results.
489 :param node: T-Rex generator node.
491 :raises RuntimeError: If stop traffic script fails.
493 command_line = OptionString().add(u"python3")
494 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
495 command_line.add(f"'{dirname}/trex_astf_stop.py'")
496 command_line.change_prefix(u"--")
497 for index, value in enumerate(self._xstats):
498 if value is not None:
499 value = value.replace(u"'", u"\"")
500 command_line.add_equals(f"xstat{index}", f"'{value}'")
501 stdout, _ = exec_cmd_no_error(
503 message=u"T-Rex ASTF runtime error!"
505 self._parse_traffic_results(stdout)
507 def trex_stl_stop_remote_exec(self, node):
508 """Execute T-Rex STL script on remote node over ssh to stop running
511 Internal state is updated with measurement results.
513 :param node: T-Rex generator node.
515 :raises RuntimeError: If stop traffic script fails.
517 command_line = OptionString().add(u"python3")
518 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
519 command_line.add(f"'{dirname}/trex_stl_stop.py'")
520 command_line.change_prefix(u"--")
521 for index, value in enumerate(self._xstats):
522 if value is not None:
523 value = value.replace(u"'", u"\"")
524 command_line.add_equals(f"xstat{index}", f"'{value}'")
525 stdout, _ = exec_cmd_no_error(
527 message=u"T-Rex STL runtime error!"
529 self._parse_traffic_results(stdout)
531 def stop_traffic_on_tg(self):
532 """Stop all traffic on TG.
534 :returns: Structure containing the result of the measurement.
535 :rtype: ReceiveRateMeasurement
536 :raises ValueError: If TG traffic profile is not supported.
538 subtype = check_subtype(self._node)
539 if subtype != NodeSubTypeTG.TREX:
540 raise ValueError(f"Unsupported TG subtype: {subtype!r}")
541 if u"trex-astf" in self.traffic_profile:
542 self.trex_astf_stop_remote_exec(self._node)
543 elif u"trex-stl" in self.traffic_profile:
544 self.trex_stl_stop_remote_exec(self._node)
546 raise ValueError(u"Unsupported T-Rex traffic profile!")
547 self._stop_time = time.monotonic()
549 return self._get_measurement_result()
551 def _compute_duration(self, duration, multiplier):
552 """Compute duration for profile driver.
554 The final result is influenced by transaction scale and duration limit.
555 It is assumed a higher level function has already set those to self.
556 The duration argument is the target value from search point of view,
557 before the overrides are applied here.
559 Minus one (signalling async traffic start) is kept.
561 Completeness flag is also included. Duration limited or async trials
562 are not considered complete for ramp-up purposes.
564 :param duration: Time expressed in seconds for how long to send traffic.
565 :param multiplier: Traffic rate in transactions per second.
566 :type duration: float
567 :type multiplier: float
568 :returns: New duration and whether it was a complete ramp-up candidate.
573 return duration, False
574 computed_duration = duration
575 if self.transaction_scale:
576 computed_duration = self.transaction_scale / multiplier
577 # Log the computed duration,
578 # so we can compare with what telemetry suggests
579 # the real duration was.
580 logger.debug(f"Expected duration {computed_duration}")
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)
655 command_line.add_with_value(
656 u"delay", Constants.PERF_TRIAL_ASTF_DELAY
659 self._start_time = time.monotonic()
660 self._rate = multiplier
661 stdout, _ = exec_cmd_no_error(
662 self._node, command_line, timeout=computed_duration + 10.0,
663 message=u"T-Rex ASTF runtime error!"
668 self._target_duration = None
669 self._duration = None
670 self._received = None
674 xstats = [None, None]
675 self._l7_data = dict()
676 self._l7_data[u"client"] = dict()
677 self._l7_data[u"client"][u"active_flows"] = None
678 self._l7_data[u"client"][u"established_flows"] = None
679 self._l7_data[u"client"][u"traffic_duration"] = None
680 self._l7_data[u"server"] = dict()
681 self._l7_data[u"server"][u"active_flows"] = None
682 self._l7_data[u"server"][u"established_flows"] = None
683 self._l7_data[u"server"][u"traffic_duration"] = None
684 if u"udp" in self.traffic_profile:
685 self._l7_data[u"client"][u"udp"] = dict()
686 self._l7_data[u"client"][u"udp"][u"connects"] = None
687 self._l7_data[u"client"][u"udp"][u"closed_flows"] = None
688 self._l7_data[u"client"][u"udp"][u"err_cwf"] = None
689 self._l7_data[u"server"][u"udp"] = dict()
690 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = None
691 self._l7_data[u"server"][u"udp"][u"closed_flows"] = None
692 elif u"tcp" in self.traffic_profile:
693 self._l7_data[u"client"][u"tcp"] = dict()
694 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = None
695 self._l7_data[u"client"][u"tcp"][u"connects"] = None
696 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = None
697 self._l7_data[u"client"][u"tcp"][u"connattempt"] = None
698 self._l7_data[u"server"][u"tcp"] = dict()
699 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = None
700 self._l7_data[u"server"][u"tcp"][u"connects"] = None
701 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = None
703 logger.warn(u"Unsupported T-Rex ASTF traffic profile!")
705 for line in stdout.splitlines():
706 if f"Xstats snapshot {index}: " in line:
707 xstats[index] = line[19:]
711 self._xstats = tuple(xstats)
713 self._target_duration = duration
714 self._duration = computed_duration
715 self._parse_traffic_results(stdout)
717 def trex_stl_start_remote_exec(self, duration, rate, async_call=False):
718 """Execute T-Rex STL script on remote node over ssh to start running
721 In sync mode, measurement results are stored internally.
722 In async mode, initial data including xstats are stored internally.
724 Mode-unaware code (e.g. in search algorithms) works with transactions.
725 To keep the logic simple, multiplier is set to that value.
726 As bidirectional traffic profiles send packets in both directions,
727 they are treated as transactions with two packets (one per direction).
729 :param duration: Time expressed in seconds for how long to send traffic.
730 :param rate: Traffic rate in transactions per second.
731 :param async_call: If enabled then don't wait for all incoming traffic.
732 :type duration: float
734 :type async_call: bool
735 :raises RuntimeError: In case of T-Rex driver issue.
737 self.check_mode(TrexMode.STL)
738 p_0, p_1 = (1, 0) if self._ifaces_reordered else (0, 1)
739 if not isinstance(duration, (float, int)):
740 duration = float(duration)
742 # TODO: Refactor the code so duration is computed only once,
743 # and both the initial and the computed durations are logged.
744 duration, _ = self._compute_duration(duration=duration, multiplier=rate)
746 command_line = OptionString().add(u"python3")
747 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex"
748 command_line.add(f"'{dirname}/trex_stl_profile.py'")
749 command_line.change_prefix(u"--")
750 dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex"
751 command_line.add_with_value(
752 u"profile", f"'{dirname}/{self.traffic_profile}.py'"
754 command_line.add_with_value(u"duration", f"{duration!r}")
755 command_line.add_with_value(u"frame_size", self.frame_size)
756 command_line.add_with_value(u"rate", f"{rate!r}")
757 command_line.add_with_value(u"port_0", p_0)
758 command_line.add_with_value(u"port_1", p_1)
759 command_line.add_with_value(
760 u"traffic_directions", self.traffic_directions
762 command_line.add_if(u"async_start", async_call)
763 command_line.add_if(u"latency", self.use_latency)
764 command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
765 command_line.add_with_value(u"delay", Constants.PERF_TRIAL_STL_DELAY)
767 # TODO: This is ugly. Handle parsing better.
768 self._start_time = time.monotonic()
769 self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
770 stdout, _ = exec_cmd_no_error(
771 self._node, command_line, timeout=int(duration) + 60,
772 message=u"T-Rex STL runtime error"
777 self._target_duration = None
778 self._duration = None
779 self._received = None
784 xstats = [None, None]
786 for line in stdout.splitlines():
787 if f"Xstats snapshot {index}: " in line:
788 xstats[index] = line[19:]
792 self._xstats = tuple(xstats)
794 self._target_duration = duration
795 self._duration = duration
796 self._parse_traffic_results(stdout)
798 def send_traffic_on_tg(
806 traffic_directions=2,
807 transaction_duration=0.0,
809 transaction_type=u"packet",
813 ramp_up_duration=None,
817 """Send traffic from all configured interfaces on TG.
819 In async mode, xstats is stored internally,
820 to enable getting correct result when stopping the traffic.
821 In both modes, stdout is returned,
822 but _parse_traffic_results only works in sync output.
824 Note that traffic generator uses DPDK driver which might
825 reorder port numbers based on wiring and PCI numbering.
826 This method handles that, so argument values are invariant,
827 but you can see swapped valued in debug logs.
829 When transaction_scale is specified, the duration value is ignored
830 and the needed time is computed. For cases where this results in
831 to too long measurement (e.g. teardown trial with small rate),
832 duration_limit is applied (of non-zero), so the trial is stopped sooner.
834 Bidirectional STL profiles are treated as transactions with two packets.
836 The return value is None for async.
838 :param duration: Duration of test traffic generation in seconds.
839 :param rate: Traffic rate in transactions per second.
840 :param frame_size: Frame size (L2) in Bytes.
841 :param traffic_profile: Module name as a traffic profile identifier.
842 See GPL/traffic_profiles/trex for implemented modules.
843 :param async_call: Async mode.
844 :param ppta: Packets per transaction, aggregated over directions.
845 Needed for udp_pps which does not have a good transaction counter,
846 so we need to compute expected number of packets.
848 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
850 :param transaction_duration: Total expected time to close transaction.
851 :param transaction_scale: Number of transactions to perform.
852 0 (default) means unlimited.
853 :param transaction_type: An identifier specifying which counters
854 and formulas to use when computing attempted and failed
855 transactions. Default: "packet".
856 :param duration_limit: Zero or maximum limit for computed (or given)
858 :param use_latency: Whether to measure latency during the trial.
860 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
861 :param ramp_up_duration: Duration of ramp-up trials [s].
862 :param state_timeout: Time of life of DUT state [s].
863 :param ramp_up_only: If true, do not perform main trial measurement.
864 :type duration: float
866 :type frame_size: str
867 :type traffic_profile: str
868 :type async_call: bool
870 :type traffic_directions: int
871 :type transaction_duration: float
872 :type transaction_scale: int
873 :type transaction_type: str
874 :type duration_limit: float
875 :type use_latency: bool
876 :type ramp_up_rate: float
877 :type ramp_up_duration: float
878 :type state_timeout: float
879 :type ramp_up_only: bool
880 :returns: TG results.
881 :rtype: ReceiveRateMeasurement or None
882 :raises ValueError: If TG traffic profile is not supported.
884 self.set_rate_provider_defaults(
885 frame_size=frame_size,
886 traffic_profile=traffic_profile,
888 traffic_directions=traffic_directions,
889 transaction_duration=transaction_duration,
890 transaction_scale=transaction_scale,
891 transaction_type=transaction_type,
892 duration_limit=duration_limit,
893 use_latency=use_latency,
894 ramp_up_rate=ramp_up_rate,
895 ramp_up_duration=ramp_up_duration,
896 state_timeout=state_timeout,
898 return self._send_traffic_on_tg_with_ramp_up(
901 async_call=async_call,
902 ramp_up_only=ramp_up_only,
905 def _send_traffic_on_tg_internal(
906 self, duration, rate, async_call=False):
907 """Send traffic from all configured interfaces on TG.
909 This is an internal function, it assumes set_rate_provider_defaults
910 has been called to remember most values.
911 The reason why need to remember various values is that
912 the traffic can be asynchronous, and parsing needs those values.
913 The reason why this is is a separate function from the one
914 which calls set_rate_provider_defaults is that some search algorithms
915 need to specify their own values, and we do not want the measure call
916 to overwrite them with defaults.
918 This function is used both for automated ramp-up trials
919 and for explicitly called trials.
921 :param duration: Duration of test traffic generation in seconds.
922 :param rate: Traffic rate in transactions per second.
923 :param async_call: Async mode.
924 :type duration: float
926 :type async_call: bool
927 :returns: TG results.
928 :rtype: ReceiveRateMeasurement or None
929 :raises ValueError: If TG traffic profile is not supported.
931 subtype = check_subtype(self._node)
932 if subtype == NodeSubTypeTG.TREX:
933 if u"trex-astf" in self.traffic_profile:
934 self.trex_astf_start_remote_exec(
935 duration, float(rate), async_call
937 elif u"trex-stl" in self.traffic_profile:
938 unit_rate_str = str(rate) + u"pps"
939 # TODO: Suport transaction_scale et al?
940 self.trex_stl_start_remote_exec(
941 duration, unit_rate_str, async_call
944 raise ValueError(u"Unsupported T-Rex traffic profile!")
946 return None if async_call else self._get_measurement_result()
948 def _send_traffic_on_tg_with_ramp_up(
949 self, duration, rate, async_call=False, ramp_up_only=False):
950 """Send traffic from all interfaces on TG, maybe after ramp-up.
952 This is an internal function, it assumes set_rate_provider_defaults
953 has been called to remember most values.
954 The reason why need to remember various values is that
955 the traffic can be asynchronous, and parsing needs those values.
956 The reason why this is a separate function from the one
957 which calls set_rate_provider_defaults is that some search algorithms
958 need to specify their own values, and we do not want the measure call
959 to overwrite them with defaults.
961 If ramp-up tracking is detected, a computation is performed,
962 and if state timeout is near, trial at ramp-up rate and duration
963 is inserted before the main trial measurement.
965 The ramp_up_only parameter forces a ramp-up without immediate
966 trial measurement, which is useful in case self remembers
967 a previous ramp-up trial that belongs to a different test (phase).
969 Return None if trial is async or ramp-up only.
971 :param duration: Duration of test traffic generation in seconds.
972 :param rate: Traffic rate in transactions per second.
973 :param async_call: Async mode.
974 :param ramp_up_only: If true, do not perform main trial measurement.
975 :type duration: float
977 :type async_call: bool
978 :type ramp_up_only: bool
979 :returns: TG results.
980 :rtype: ReceiveRateMeasurement or None
981 :raises ValueError: If TG traffic profile is not supported.
984 if self.ramp_up_rate:
985 # Figure out whether we need to insert a ramp-up trial.
986 # TODO: Give up on async_call=True?
987 if ramp_up_only or self.ramp_up_start is None:
988 # We never ramped up yet (at least not in this test case).
989 ramp_up_needed = True
991 # We ramped up before, but maybe it was too long ago.
992 # Adding a constant overhead to be safe.
993 time_now = time.monotonic() + 1.0
994 computed_duration, complete = self._compute_duration(
998 # There are two conditions for inserting ramp-up.
999 # If early sessions are expiring already,
1000 # or if late sessions are to expire before measurement is over.
1001 ramp_up_start_delay = time_now - self.ramp_up_start
1002 ramp_up_stop_delay = time_now - self.ramp_up_stop
1003 ramp_up_stop_delay += computed_duration
1004 bigger_delay = max(ramp_up_start_delay, ramp_up_stop_delay)
1005 # Final boolean decision.
1006 ramp_up_needed = (bigger_delay >= self.state_timeout)
1009 u"State may time out during next real trial, "
1010 u"inserting a ramp-up trial."
1012 self.ramp_up_start = time.monotonic()
1013 self._send_traffic_on_tg_internal(
1014 duration=self.ramp_up_duration,
1015 rate=self.ramp_up_rate,
1016 async_call=async_call,
1018 self.ramp_up_stop = time.monotonic()
1019 logger.debug(u"Ramp-up done.")
1022 u"State will probably not time out during next real trial, "
1023 u"no ramp-up trial needed just yet."
1027 trial_start = time.monotonic()
1028 result = self._send_traffic_on_tg_internal(
1031 async_call=async_call,
1033 trial_end = time.monotonic()
1034 if self.ramp_up_rate:
1035 # Optimization: No loss acts as a good ramp-up, if it was complete.
1036 if complete and result is not None and result.loss_count == 0:
1037 logger.debug(u"Good trial acts as a ramp-up")
1038 self.ramp_up_start = trial_start
1039 self.ramp_up_stop = trial_end
1041 logger.debug(u"Loss or incomplete, does not act as a ramp-up.")
1044 def no_traffic_loss_occurred(self):
1045 """Fail if loss occurred in traffic run.
1048 :raises Exception: If loss occured.
1050 if self._loss is None:
1051 raise RuntimeError(u"The traffic generation has not been issued")
1052 if self._loss != u"0":
1053 raise RuntimeError(f"Traffic loss occurred: {self._loss}")
1055 def fail_if_no_traffic_forwarded(self):
1056 """Fail if no traffic forwarded.
1058 TODO: Check number of passed transactions instead.
1061 :raises Exception: If no traffic forwarded.
1063 if self._received is None:
1064 raise RuntimeError(u"The traffic generation has not been issued")
1065 if self._received == 0:
1066 raise RuntimeError(u"No traffic forwarded")
1068 def partial_traffic_loss_accepted(
1069 self, loss_acceptance, loss_acceptance_type):
1070 """Fail if loss is higher then accepted in traffic run.
1072 :param loss_acceptance: Permitted drop ratio or frames count.
1073 :param loss_acceptance_type: Type of permitted loss.
1074 :type loss_acceptance: float
1075 :type loss_acceptance_type: LossAcceptanceType
1077 :raises Exception: If loss is above acceptance criteria.
1079 if self._loss is None:
1080 raise Exception(u"The traffic generation has not been issued")
1082 if loss_acceptance_type == u"percentage":
1083 loss = (float(self._loss) / float(self._sent)) * 100
1084 elif loss_acceptance_type == u"frames":
1085 loss = float(self._loss)
1087 raise Exception(u"Loss acceptance type not supported")
1089 if loss > float(loss_acceptance):
1091 f"Traffic loss {loss} above loss acceptance: {loss_acceptance}"
1094 def _parse_traffic_results(self, stdout):
1095 """Parse stdout of scripts into fields of self.
1097 Block of code to reuse, by sync start, or stop after async.
1099 :param stdout: Text containing the standard output.
1102 subtype = check_subtype(self._node)
1103 if subtype == NodeSubTypeTG.TREX:
1104 # Last line from console output
1105 line = stdout.splitlines()[-1]
1106 results = line.split(u";")
1107 if results[-1] in (u" ", u""):
1109 self._result = dict()
1110 for result in results:
1111 key, value = result.split(u"=", maxsplit=1)
1112 self._result[key.strip()] = value
1113 logger.info(f"TrafficGen results:\n{self._result}")
1114 self._received = int(self._result.get(u"total_received"), 0)
1115 self._sent = int(self._result.get(u"total_sent", 0))
1116 self._loss = int(self._result.get(u"frame_loss", 0))
1117 self._approximated_duration = \
1118 self._result.get(u"approximated_duration", 0.0)
1119 if u"manual" not in str(self._approximated_duration):
1120 self._approximated_duration = float(self._approximated_duration)
1121 self._latency = list()
1122 self._latency.append(self._result.get(u"latency_stream_0(usec)"))
1123 self._latency.append(self._result.get(u"latency_stream_1(usec)"))
1124 if self._mode == TrexMode.ASTF:
1125 self._l7_data = dict()
1126 self._l7_data[u"client"] = dict()
1127 self._l7_data[u"client"][u"sent"] = \
1128 int(self._result.get(u"client_sent", 0))
1129 self._l7_data[u"client"][u"received"] = \
1130 int(self._result.get(u"client_received", 0))
1131 self._l7_data[u"client"][u"active_flows"] = \
1132 int(self._result.get(u"client_active_flows", 0))
1133 self._l7_data[u"client"][u"established_flows"] = \
1134 int(self._result.get(u"client_established_flows", 0))
1135 self._l7_data[u"client"][u"traffic_duration"] = \
1136 float(self._result.get(u"client_traffic_duration", 0.0))
1137 self._l7_data[u"client"][u"err_rx_throttled"] = \
1138 int(self._result.get(u"client_err_rx_throttled", 0))
1139 self._l7_data[u"client"][u"err_c_nf_throttled"] = \
1140 int(self._result.get(u"client_err_nf_throttled", 0))
1141 self._l7_data[u"client"][u"err_flow_overflow"] = \
1142 int(self._result.get(u"client_err_flow_overflow", 0))
1143 self._l7_data[u"server"] = dict()
1144 self._l7_data[u"server"][u"active_flows"] = \
1145 int(self._result.get(u"server_active_flows", 0))
1146 self._l7_data[u"server"][u"established_flows"] = \
1147 int(self._result.get(u"server_established_flows", 0))
1148 self._l7_data[u"server"][u"traffic_duration"] = \
1149 float(self._result.get(u"server_traffic_duration", 0.0))
1150 self._l7_data[u"server"][u"err_rx_throttled"] = \
1151 int(self._result.get(u"client_err_rx_throttled", 0))
1152 if u"udp" in self.traffic_profile:
1153 self._l7_data[u"client"][u"udp"] = dict()
1154 self._l7_data[u"client"][u"udp"][u"connects"] = \
1155 int(self._result.get(u"client_udp_connects", 0))
1156 self._l7_data[u"client"][u"udp"][u"closed_flows"] = \
1157 int(self._result.get(u"client_udp_closed", 0))
1158 self._l7_data[u"client"][u"udp"][u"tx_bytes"] = \
1159 int(self._result.get(u"client_udp_tx_bytes", 0))
1160 self._l7_data[u"client"][u"udp"][u"rx_bytes"] = \
1161 int(self._result.get(u"client_udp_rx_bytes", 0))
1162 self._l7_data[u"client"][u"udp"][u"tx_packets"] = \
1163 int(self._result.get(u"client_udp_tx_packets", 0))
1164 self._l7_data[u"client"][u"udp"][u"rx_packets"] = \
1165 int(self._result.get(u"client_udp_rx_packets", 0))
1166 self._l7_data[u"client"][u"udp"][u"keep_drops"] = \
1167 int(self._result.get(u"client_udp_keep_drops", 0))
1168 self._l7_data[u"client"][u"udp"][u"err_cwf"] = \
1169 int(self._result.get(u"client_err_cwf", 0))
1170 self._l7_data[u"server"][u"udp"] = dict()
1171 self._l7_data[u"server"][u"udp"][u"accepted_flows"] = \
1172 int(self._result.get(u"server_udp_accepts", 0))
1173 self._l7_data[u"server"][u"udp"][u"closed_flows"] = \
1174 int(self._result.get(u"server_udp_closed", 0))
1175 self._l7_data[u"server"][u"udp"][u"tx_bytes"] = \
1176 int(self._result.get(u"server_udp_tx_bytes", 0))
1177 self._l7_data[u"server"][u"udp"][u"rx_bytes"] = \
1178 int(self._result.get(u"server_udp_rx_bytes", 0))
1179 self._l7_data[u"server"][u"udp"][u"tx_packets"] = \
1180 int(self._result.get(u"server_udp_tx_packets", 0))
1181 self._l7_data[u"server"][u"udp"][u"rx_packets"] = \
1182 int(self._result.get(u"server_udp_rx_packets", 0))
1183 elif u"tcp" in self.traffic_profile:
1184 self._l7_data[u"client"][u"tcp"] = dict()
1185 self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = \
1186 int(self._result.get(u"client_tcp_connect_inits", 0))
1187 self._l7_data[u"client"][u"tcp"][u"connects"] = \
1188 int(self._result.get(u"client_tcp_connects", 0))
1189 self._l7_data[u"client"][u"tcp"][u"closed_flows"] = \
1190 int(self._result.get(u"client_tcp_closed", 0))
1191 self._l7_data[u"client"][u"tcp"][u"connattempt"] = \
1192 int(self._result.get(u"client_tcp_connattempt", 0))
1193 self._l7_data[u"client"][u"tcp"][u"tx_bytes"] = \
1194 int(self._result.get(u"client_tcp_tx_bytes", 0))
1195 self._l7_data[u"client"][u"tcp"][u"rx_bytes"] = \
1196 int(self._result.get(u"client_tcp_rx_bytes", 0))
1197 self._l7_data[u"server"][u"tcp"] = dict()
1198 self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = \
1199 int(self._result.get(u"server_tcp_accepts", 0))
1200 self._l7_data[u"server"][u"tcp"][u"connects"] = \
1201 int(self._result.get(u"server_tcp_connects", 0))
1202 self._l7_data[u"server"][u"tcp"][u"closed_flows"] = \
1203 int(self._result.get(u"server_tcp_closed", 0))
1204 self._l7_data[u"server"][u"tcp"][u"tx_bytes"] = \
1205 int(self._result.get(u"server_tcp_tx_bytes", 0))
1206 self._l7_data[u"server"][u"tcp"][u"rx_bytes"] = \
1207 int(self._result.get(u"server_tcp_rx_bytes", 0))
1209 def _get_measurement_result(self):
1210 """Return the result of last measurement as ReceiveRateMeasurement.
1212 Separate function, as measurements can end either by time
1213 or by explicit call, this is the common block at the end.
1215 The target_tr field of ReceiveRateMeasurement is in
1216 transactions per second. Transmit count and loss count units
1217 depend on the transaction type. Usually they are in transactions
1218 per second, or aggregate packets per second.
1220 TODO: Fail on running or already reported measurement.
1222 :returns: Structure containing the result of the measurement.
1223 :rtype: ReceiveRateMeasurement
1226 # Client duration seems to include a setup period
1227 # where TRex does not send any packets yet.
1228 # Server duration does not include it.
1229 server_data = self._l7_data[u"server"]
1230 approximated_duration = float(server_data[u"traffic_duration"])
1231 except (KeyError, AttributeError, ValueError, TypeError):
1232 approximated_duration = None
1234 if not approximated_duration:
1235 approximated_duration = float(self._approximated_duration)
1236 except ValueError: # "manual"
1237 approximated_duration = None
1238 if not approximated_duration:
1239 if self._duration and self._duration > 0:
1240 # Known recomputed or target duration.
1241 approximated_duration = self._duration
1243 # It was an explicit stop.
1244 if not self._stop_time:
1245 raise RuntimeError(u"Unable to determine duration.")
1246 approximated_duration = self._stop_time - self._start_time
1247 target_duration = self._target_duration
1248 if not target_duration:
1249 target_duration = approximated_duration
1250 transmit_rate = self._rate
1252 if self.transaction_type == u"packet":
1253 partial_attempt_count = self._sent
1254 packet_rate = transmit_rate * self.ppta
1255 # We have a float. TRex way of rounding it is not obvious.
1256 # The biggest source of mismatch is Inter Stream Gap.
1257 # So the code tolerates 10 usec of missing packets.
1258 expected_attempt_count = (target_duration - 1e-5) * packet_rate
1259 expected_attempt_count = math.ceil(expected_attempt_count)
1260 # TRex can send more.
1261 expected_attempt_count = max(expected_attempt_count, self._sent)
1262 unsent = expected_attempt_count - self._sent
1263 pass_count = self._received
1264 fail_count = expected_attempt_count - pass_count
1265 elif self.transaction_type == u"udp_cps":
1266 if not self.transaction_scale:
1267 raise RuntimeError(u"Add support for no-limit udp_cps.")
1268 partial_attempt_count = self._l7_data[u"client"][u"sent"]
1269 # We do not care whether TG is slow, it should have attempted all.
1270 expected_attempt_count = self.transaction_scale
1271 unsent = expected_attempt_count - partial_attempt_count
1272 pass_count = self._l7_data[u"client"][u"received"]
1273 fail_count = expected_attempt_count - pass_count
1274 elif self.transaction_type == u"tcp_cps":
1275 if not self.transaction_scale:
1276 raise RuntimeError(u"Add support for no-limit tcp_cps.")
1277 ctca = self._l7_data[u"client"][u"tcp"][u"connattempt"]
1278 partial_attempt_count = ctca
1279 # We do not care whether TG is slow, it should have attempted all.
1280 expected_attempt_count = self.transaction_scale
1281 unsent = expected_attempt_count - partial_attempt_count
1282 # From TCP point of view, server/connects counts full connections,
1283 # but we are testing NAT session so client/connects counts that
1284 # (half connections from TCP point of view).
1285 pass_count = self._l7_data[u"client"][u"tcp"][u"connects"]
1286 fail_count = expected_attempt_count - pass_count
1287 elif self.transaction_type == u"udp_pps":
1288 if not self.transaction_scale:
1289 raise RuntimeError(u"Add support for no-limit udp_pps.")
1290 partial_attempt_count = self._sent
1291 expected_attempt_count = self.transaction_scale * self.ppta
1292 unsent = expected_attempt_count - self._sent
1293 fail_count = self._loss + unsent
1294 elif self.transaction_type == u"tcp_pps":
1295 if not self.transaction_scale:
1296 raise RuntimeError(u"Add support for no-limit tcp_pps.")
1297 partial_attempt_count = self._sent
1298 expected_attempt_count = self.transaction_scale * self.ppta
1299 # One loss-like scenario happens when TRex receives all packets
1300 # on L2 level, but is not fast enough to process them all
1301 # at L7 level, which leads to retransmissions.
1302 # Those manifest as opackets larger than expected.
1303 # A simple workaround is to add absolute difference.
1304 # Probability of retransmissions exactly cancelling
1305 # packets unsent due to duration stretching is quite low.
1306 unsent = abs(expected_attempt_count - self._sent)
1307 fail_count = self._loss + unsent
1309 raise RuntimeError(f"Unknown parsing {self.transaction_type!r}")
1310 if unsent and isinstance(self._approximated_duration, float):
1311 # Do not report unsent for "manual".
1312 logger.debug(f"Unsent packets/transactions: {unsent}")
1313 if fail_count < 0 and not self.negative_loss:
1315 measurement = ReceiveRateMeasurement(
1316 duration=target_duration,
1317 target_tr=transmit_rate,
1318 transmit_count=expected_attempt_count,
1319 loss_count=fail_count,
1320 approximated_duration=approximated_duration,
1321 partial_transmit_count=partial_attempt_count,
1323 measurement.latency = self.get_latency_int()
1326 def measure(self, duration, transmit_rate):
1327 """Run trial measurement, parse and return results.
1329 The input rate is for transactions. Stateles bidirectional traffic
1330 is understood as sequence of (asynchronous) transactions,
1333 The result units depend on test type, generally
1334 the count either transactions or packets (aggregated over directions).
1336 Optionally, this method sleeps if measurement finished before
1337 the time specified as duration.
1339 :param duration: Trial duration [s].
1340 :param transmit_rate: Target rate in transactions per second.
1341 :type duration: float
1342 :type transmit_rate: float
1343 :returns: Structure containing the result of the measurement.
1344 :rtype: ReceiveRateMeasurement
1345 :raises RuntimeError: If TG is not set or if node is not TG
1346 or if subtype is not specified.
1347 :raises NotImplementedError: If TG is not supported.
1349 duration = float(duration)
1350 time_start = time.monotonic()
1351 time_stop = time_start + duration
1354 result = self._send_traffic_on_tg_with_ramp_up(
1359 logger.debug(f"trial measurement result: {result!r}")
1360 # In PLRsearch, computation needs the specified time to complete.
1361 if self.sleep_till_duration:
1362 sleeptime = time_stop - time.monotonic()
1364 # TODO: Sometimes we have time to do additional trials here,
1365 # adapt PLRsearch to accept all the results.
1366 time.sleep(sleeptime)
1369 def set_rate_provider_defaults(
1375 traffic_directions=2,
1376 transaction_duration=0.0,
1377 transaction_scale=0,
1378 transaction_type=u"packet",
1381 sleep_till_duration=False,
1384 ramp_up_duration=None,
1385 state_timeout=300.0,
1387 """Store values accessed by measure().
1389 :param frame_size: Frame size identifier or value [B].
1390 :param traffic_profile: Module name as a traffic profile identifier.
1391 See GPL/traffic_profiles/trex for implemented modules.
1392 :param ppta: Packets per transaction, aggregated over directions.
1393 Needed for udp_pps which does not have a good transaction counter,
1394 so we need to compute expected number of packets.
1396 :param resetter: Callable to reset DUT state for repeated trials.
1397 :param traffic_directions: Traffic from packet counting point of view
1398 is bi- (2) or uni- (1) directional.
1400 :param transaction_duration: Total expected time to close transaction.
1401 :param transaction_scale: Number of transactions to perform.
1402 0 (default) means unlimited.
1403 :param transaction_type: An identifier specifying which counters
1404 and formulas to use when computing attempted and failed
1405 transactions. Default: "packet".
1406 TODO: Does this also specify parsing for the measured duration?
1407 :param duration_limit: Zero or maximum limit for computed (or given)
1409 :param negative_loss: If false, negative loss is reported as zero loss.
1410 :param sleep_till_duration: If true and measurement returned faster,
1411 sleep until it matches duration. Needed for PLRsearch.
1412 :param use_latency: Whether to measure latency during the trial.
1414 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1415 :param ramp_up_duration: Duration of ramp-up trials [s].
1416 :param state_timeout: Time of life of DUT state [s].
1417 :type frame_size: str or int
1418 :type traffic_profile: str
1420 :type resetter: Optional[Callable[[], None]]
1421 :type traffic_directions: int
1422 :type transaction_duration: float
1423 :type transaction_scale: int
1424 :type transaction_type: str
1425 :type duration_limit: float
1426 :type negative_loss: bool
1427 :type sleep_till_duration: bool
1428 :type use_latency: bool
1429 :type ramp_up_rate: float
1430 :type ramp_up_duration: float
1431 :type state_timeout: float
1433 self.frame_size = frame_size
1434 self.traffic_profile = str(traffic_profile)
1435 self.resetter = resetter
1437 self.traffic_directions = int(traffic_directions)
1438 self.transaction_duration = float(transaction_duration)
1439 self.transaction_scale = int(transaction_scale)
1440 self.transaction_type = str(transaction_type)
1441 self.duration_limit = float(duration_limit)
1442 self.negative_loss = bool(negative_loss)
1443 self.sleep_till_duration = bool(sleep_till_duration)
1444 self.use_latency = bool(use_latency)
1445 self.ramp_up_rate = float(ramp_up_rate)
1446 self.ramp_up_duration = float(ramp_up_duration)
1447 self.state_timeout = float(state_timeout)
1450 class OptimizedSearch:
1451 """Class to be imported as Robot Library, containing search keywords.
1453 Aside of setting up measurer and forwarding arguments,
1454 the main business is to translate min/max rate from unidir to aggregate.
1458 def perform_optimized_ndrpdr_search(
1461 minimum_transmit_rate,
1462 maximum_transmit_rate,
1463 packet_loss_ratio=0.005,
1464 final_relative_width=0.005,
1465 final_trial_duration=30.0,
1466 initial_trial_duration=1.0,
1467 number_of_intermediate_phases=2,
1471 traffic_directions=2,
1472 transaction_duration=0.0,
1473 transaction_scale=0,
1474 transaction_type=u"packet",
1477 ramp_up_duration=None,
1478 state_timeout=300.0,
1479 expansion_coefficient=4.0,
1481 """Setup initialized TG, perform optimized search, return intervals.
1483 If transaction_scale is nonzero, all init and non-init trial durations
1484 are set to 1.0 (as they do not affect the real trial duration)
1485 and zero intermediate phases are used.
1486 This way no re-measurement happens.
1487 Warmup has to be handled via resetter or ramp-up mechanisms.
1489 :param frame_size: Frame size identifier or value [B].
1490 :param traffic_profile: Module name as a traffic profile identifier.
1491 See GPL/traffic_profiles/trex for implemented modules.
1492 :param minimum_transmit_rate: Minimal load in transactions per second.
1493 :param maximum_transmit_rate: Maximal load in transactions per second.
1494 :param packet_loss_ratio: Ratio of packets lost, for PDR [1].
1495 :param final_relative_width: Final lower bound transmit rate
1496 cannot be more distant that this multiple of upper bound [1].
1497 :param final_trial_duration: Trial duration for the final phase [s].
1498 :param initial_trial_duration: Trial duration for the initial phase
1499 and also for the first intermediate phase [s].
1500 :param number_of_intermediate_phases: Number of intermediate phases
1501 to perform before the final phase [1].
1502 :param timeout: The search will fail itself when not finished
1503 before this overall time [s].
1504 :param ppta: Packets per transaction, aggregated over directions.
1505 Needed for udp_pps which does not have a good transaction counter,
1506 so we need to compute expected number of packets.
1508 :param resetter: Callable to reset DUT state for repeated trials.
1509 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1511 :param transaction_duration: Total expected time to close transaction.
1512 :param transaction_scale: Number of transactions to perform.
1513 0 (default) means unlimited.
1514 :param transaction_type: An identifier specifying which counters
1515 and formulas to use when computing attempted and failed
1516 transactions. Default: "packet".
1517 :param use_latency: Whether to measure latency during the trial.
1519 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1520 :param ramp_up_duration: Duration of ramp-up trials [s].
1521 :param state_timeout: Time of life of DUT state [s].
1522 :param expansion_coefficient: In external search multiply width by this.
1523 :type frame_size: str or int
1524 :type traffic_profile: str
1525 :type minimum_transmit_rate: float
1526 :type maximum_transmit_rate: float
1527 :type packet_loss_ratio: float
1528 :type final_relative_width: float
1529 :type final_trial_duration: float
1530 :type initial_trial_duration: float
1531 :type number_of_intermediate_phases: int
1532 :type timeout: float
1534 :type resetter: Optional[Callable[[], None]]
1535 :type traffic_directions: int
1536 :type transaction_duration: float
1537 :type transaction_scale: int
1538 :type transaction_type: str
1539 :type use_latency: bool
1540 :type ramp_up_rate: float
1541 :type ramp_up_duration: float
1542 :type state_timeout: float
1543 :type expansion_coefficient: float
1544 :returns: Structure containing narrowed down NDR and PDR intervals
1545 and their measurements.
1546 :rtype: List[Receiverateinterval]
1547 :raises RuntimeError: If total duration is larger than timeout.
1549 # we need instance of TrafficGenerator instantiated by Robot Framework
1550 # to be able to use trex_stl-*()
1551 tg_instance = BuiltIn().get_library_instance(
1552 u"resources.libraries.python.TrafficGenerator"
1554 # Overrides for fixed transaction amount.
1555 # TODO: Move to robot code? We have two call sites, so this saves space,
1556 # even though this is surprising for log readers.
1557 if transaction_scale:
1558 initial_trial_duration = 1.0
1559 final_trial_duration = 1.0
1560 number_of_intermediate_phases = 0
1561 timeout += transaction_scale * 3e-4
1562 tg_instance.set_rate_provider_defaults(
1563 frame_size=frame_size,
1564 traffic_profile=traffic_profile,
1565 sleep_till_duration=False,
1568 traffic_directions=traffic_directions,
1569 transaction_duration=transaction_duration,
1570 transaction_scale=transaction_scale,
1571 transaction_type=transaction_type,
1572 use_latency=use_latency,
1573 ramp_up_rate=ramp_up_rate,
1574 ramp_up_duration=ramp_up_duration,
1575 state_timeout=state_timeout,
1577 algorithm = MultipleLossRatioSearch(
1578 measurer=tg_instance,
1579 final_trial_duration=final_trial_duration,
1580 final_relative_width=final_relative_width,
1581 number_of_intermediate_phases=number_of_intermediate_phases,
1582 initial_trial_duration=initial_trial_duration,
1585 expansion_coefficient=expansion_coefficient,
1587 if packet_loss_ratio:
1588 packet_loss_ratios = [0.0, packet_loss_ratio]
1590 # Happens in reconf tests.
1591 packet_loss_ratios = [packet_loss_ratio]
1592 results = algorithm.narrow_down_intervals(
1593 min_rate=minimum_transmit_rate,
1594 max_rate=maximum_transmit_rate,
1595 packet_loss_ratios=packet_loss_ratios,
1600 def perform_soak_search(
1603 minimum_transmit_rate,
1604 maximum_transmit_rate,
1611 trace_enabled=False,
1612 traffic_directions=2,
1613 transaction_duration=0.0,
1614 transaction_scale=0,
1615 transaction_type=u"packet",
1618 ramp_up_duration=None,
1619 state_timeout=300.0,
1621 """Setup initialized TG, perform soak search, return avg and stdev.
1623 :param frame_size: Frame size identifier or value [B].
1624 :param traffic_profile: Module name as a traffic profile identifier.
1625 See GPL/traffic_profiles/trex for implemented modules.
1626 :param minimum_transmit_rate: Minimal load in transactions per second.
1627 :param maximum_transmit_rate: Maximal load in transactions per second.
1628 :param plr_target: Ratio of packets lost to achieve [1].
1629 :param tdpt: Trial duration per trial.
1630 The algorithm linearly increases trial duration with trial number,
1631 this is the increment between succesive trials, in seconds.
1632 :param initial_count: Offset to apply before the first trial.
1633 For example initial_count=50 makes first trial to be 51*tdpt long.
1634 This is needed because initial "search" phase of integrator
1635 takes significant time even without any trial results.
1636 :param timeout: The search will stop after this overall time [s].
1637 :param ppta: Packets per transaction, aggregated over directions.
1638 Needed for udp_pps which does not have a good transaction counter,
1639 so we need to compute expected number of packets.
1641 :param resetter: Callable to reset DUT state for repeated trials.
1642 :param trace_enabled: True if trace enabled else False.
1643 This is very verbose tracing on numeric computations,
1644 do not use in production.
1646 :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
1648 :param transaction_duration: Total expected time to close transaction.
1649 :param transaction_scale: Number of transactions to perform.
1650 0 (default) means unlimited.
1651 :param transaction_type: An identifier specifying which counters
1652 and formulas to use when computing attempted and failed
1653 transactions. Default: "packet".
1654 :param use_latency: Whether to measure latency during the trial.
1656 :param ramp_up_rate: Rate to use in ramp-up trials [pps].
1657 :param ramp_up_duration: Duration of ramp-up trials [s].
1658 :param state_timeout: Time of life of DUT state [s].
1659 :type frame_size: str or int
1660 :type traffic_profile: str
1661 :type minimum_transmit_rate: float
1662 :type maximum_transmit_rate: float
1663 :type plr_target: float
1664 :type initial_count: int
1665 :type timeout: float
1667 :type resetter: Optional[Callable[[], None]]
1668 :type trace_enabled: bool
1669 :type traffic_directions: int
1670 :type transaction_duration: float
1671 :type transaction_scale: int
1672 :type transaction_type: str
1673 :type use_latency: bool
1674 :type ramp_up_rate: float
1675 :type ramp_up_duration: float
1676 :type state_timeout: float
1677 :returns: Average and stdev of estimated aggregate rate giving PLR.
1678 :rtype: 2-tuple of float
1680 tg_instance = BuiltIn().get_library_instance(
1681 u"resources.libraries.python.TrafficGenerator"
1683 # Overrides for fixed transaction amount.
1684 # TODO: Move to robot code? We have a single call site
1685 # but MLRsearch has two and we want the two to be used similarly.
1686 if transaction_scale:
1687 # TODO: What is a good value for max scale?
1688 # TODO: Scale the timeout with transaction scale.
1690 tg_instance.set_rate_provider_defaults(
1691 frame_size=frame_size,
1692 traffic_profile=traffic_profile,
1693 negative_loss=False,
1694 sleep_till_duration=True,
1697 traffic_directions=traffic_directions,
1698 transaction_duration=transaction_duration,
1699 transaction_scale=transaction_scale,
1700 transaction_type=transaction_type,
1701 use_latency=use_latency,
1702 ramp_up_rate=ramp_up_rate,
1703 ramp_up_duration=ramp_up_duration,
1704 state_timeout=state_timeout,
1706 algorithm = PLRsearch(
1707 measurer=tg_instance,
1708 trial_duration_per_trial=tdpt,
1709 packet_loss_ratio_target=plr_target,
1710 trial_number_offset=initial_count,
1712 trace_enabled=trace_enabled,
1714 result = algorithm.search(
1715 min_rate=minimum_transmit_rate,
1716 max_rate=maximum_transmit_rate,