-# Copyright (c) 2021 Cisco and/or its affiliates.
+# Copyright (c) 2022 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
"""Performance testing traffic generator library."""
+import math
import time
from robot.api import logger
f"{self._node[u'subtype']} not running in {expected_mode} mode!"
)
- # TODO: pylint says disable=too-many-locals.
+ @staticmethod
+ def get_tg_type(tg_node):
+ """Log and return the installed traffic generator type.
+
+ :param tg_node: Node from topology file.
+ :type tg_node: dict
+ :returns: Traffic generator type string.
+ :rtype: str
+ :raises RuntimeError: If command returns nonzero return code.
+ """
+ return str(check_subtype(tg_node))
+
+ @staticmethod
+ def get_tg_version(tg_node):
+ """Log and return the installed traffic generator version.
+
+ :param tg_node: Node from topology file.
+ :type tg_node: dict
+ :returns: Traffic generator version string.
+ :rtype: str
+ :raises RuntimeError: If command returns nonzero return code.
+ """
+ subtype = check_subtype(tg_node)
+ if subtype == NodeSubTypeTG.TREX:
+ command = f"cat {Constants.TREX_INSTALL_DIR}/VERSION"
+ message = u"Get T-Rex version failed!"
+ stdout, _ = exec_cmd_no_error(tg_node, command, message=message)
+ return stdout.strip()
+ else:
+ return "none"
+
+ # TODO: pylint disable=too-many-locals.
def initialize_traffic_generator(
self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
tg_node, cmd, sudo=True, message=u"Kill TRex failed!"
)
- # Configure TRex.
- ports = ''
+ # Prepare interfaces for TRex.
+ mlx_ports = u""
+ mlx_driver = u""
+ itl_ports = u""
for port in tg_node[u"interfaces"].values():
- if u'Mellanox' not in port.get(u'model'):
- ports += f" {port.get(u'pci_address')}"
-
- cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \
- f"./dpdk_nic_bind.py -u {ports} || true\""
- exec_cmd_no_error(
- tg_node, cmd, sudo=True,
- message=u"Unbind PCI ports from driver failed!"
- )
+ if u"Mellanox" in port.get(u"model"):
+ mlx_ports += f" {port.get(u'pci_address')}"
+ mlx_driver = port.get(u"driver")
+ if u"Intel" in port.get(u"model"):
+ itl_ports += f" {port.get(u'pci_address')}"
+
+ if itl_ports:
+ cmd = (
+ f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && ",
+ f"./dpdk_nic_bind.py -u {itl_ports} || ",
+ f"true\""
+ )
+ exec_cmd_no_error(
+ tg_node, cmd, sudo=True,
+ message=u"Unbind PCI ports from driver failed!"
+ )
+ if mlx_ports:
+ cmd = (
+ f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && ",
+ f"./dpdk_nic_bind.py -b {mlx_driver} {mlx_ports} || ",
+ f"true\""
+ )
+ exec_cmd_no_error(
+ tg_node, cmd, sudo=True,
+ message=u"Bind PCI ports from driver failed!"
+ )
# Start TRex.
cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'"
# so we can compare with what telemetry suggests
# the real duration was.
logger.debug(f"Expected duration {computed_duration}")
- computed_duration += 0.1115
if not self.duration_limit:
return computed_duration, True
limited_duration = min(computed_duration, self.duration_limit)
)
command_line.add_with_value(u"duration", f"{computed_duration!r}")
command_line.add_with_value(u"frame_size", self.frame_size)
+ command_line.add_with_value(
+ u"n_data_frames", Constants.ASTF_N_DATA_FRAMES
+ )
command_line.add_with_value(u"multiplier", multiplier)
command_line.add_with_value(u"port_0", p_0)
command_line.add_with_value(u"port_1", p_1)
command_line.add_if(u"async_start", async_call)
command_line.add_if(u"latency", self.use_latency)
command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
+ command_line.add_with_value(
+ u"delay", Constants.PERF_TRIAL_ASTF_DELAY
+ )
self._start_time = time.monotonic()
self._rate = multiplier
command_line.add_if(u"async_start", async_call)
command_line.add_if(u"latency", self.use_latency)
command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
+ command_line.add_with_value(u"delay", Constants.PERF_TRIAL_STL_DELAY)
# TODO: This is ugly. Handle parsing better.
self._start_time = time.monotonic()
use_latency=False,
ramp_up_rate=None,
ramp_up_duration=None,
- state_timeout=300.0,
+ state_timeout=240.0,
ramp_up_only=False,
):
"""Send traffic from all configured interfaces on TG.
The target_tr field of ReceiveRateMeasurement is in
transactions per second. Transmit count and loss count units
depend on the transaction type. Usually they are in transactions
- per second, or aggregate packets per second.
+ per second, or aggregated packets per second.
TODO: Fail on running or already reported measurement.
if not target_duration:
target_duration = approximated_duration
transmit_rate = self._rate
+ unsent = 0
if self.transaction_type == u"packet":
partial_attempt_count = self._sent
- expected_attempt_count = self._sent
- fail_count = self._loss
+ packet_rate = transmit_rate * self.ppta
+ # We have a float. TRex way of rounding it is not obvious.
+ # The biggest source of mismatch is Inter Stream Gap.
+ # So the code tolerates 10 usec of missing packets.
+ expected_attempt_count = (target_duration - 1e-5) * packet_rate
+ expected_attempt_count = math.ceil(expected_attempt_count)
+ # TRex can send more.
+ expected_attempt_count = max(expected_attempt_count, self._sent)
+ unsent = expected_attempt_count - self._sent
+ pass_count = self._received
+ fail_count = expected_attempt_count - pass_count
elif self.transaction_type == u"udp_cps":
if not self.transaction_scale:
raise RuntimeError(u"Add support for no-limit udp_cps.")
partial_attempt_count = self._l7_data[u"client"][u"sent"]
# We do not care whether TG is slow, it should have attempted all.
expected_attempt_count = self.transaction_scale
+ unsent = expected_attempt_count - partial_attempt_count
pass_count = self._l7_data[u"client"][u"received"]
fail_count = expected_attempt_count - pass_count
elif self.transaction_type == u"tcp_cps":
partial_attempt_count = ctca
# We do not care whether TG is slow, it should have attempted all.
expected_attempt_count = self.transaction_scale
+ unsent = expected_attempt_count - partial_attempt_count
# From TCP point of view, server/connects counts full connections,
# but we are testing NAT session so client/connects counts that
# (half connections from TCP point of view).
raise RuntimeError(u"Add support for no-limit udp_pps.")
partial_attempt_count = self._sent
expected_attempt_count = self.transaction_scale * self.ppta
- fail_count = self._loss + (expected_attempt_count - self._sent)
+ unsent = expected_attempt_count - self._sent
+ fail_count = self._loss + unsent
elif self.transaction_type == u"tcp_pps":
if not self.transaction_scale:
raise RuntimeError(u"Add support for no-limit tcp_pps.")
# A simple workaround is to add absolute difference.
# Probability of retransmissions exactly cancelling
# packets unsent due to duration stretching is quite low.
- fail_count = self._loss + abs(expected_attempt_count - self._sent)
+ unsent = abs(expected_attempt_count - self._sent)
+ fail_count = self._loss + unsent
else:
raise RuntimeError(f"Unknown parsing {self.transaction_type!r}")
+ if unsent and isinstance(self._approximated_duration, float):
+ # Do not report unsent for "manual".
+ logger.debug(f"Unsent packets/transactions: {unsent}")
if fail_count < 0 and not self.negative_loss:
fail_count = 0
measurement = ReceiveRateMeasurement(
use_latency=False,
ramp_up_rate=None,
ramp_up_duration=None,
- state_timeout=300.0,
+ state_timeout=240.0,
):
"""Store values accessed by measure().
"""Class to be imported as Robot Library, containing search keywords.
Aside of setting up measurer and forwarding arguments,
- the main business is to translate min/max rate from unidir to aggregate.
+ the main business is to translate min/max rate from unidir to aggregated.
"""
@staticmethod
final_trial_duration=30.0,
initial_trial_duration=1.0,
number_of_intermediate_phases=2,
- timeout=720.0,
+ timeout=1200.0,
ppta=1,
resetter=None,
traffic_directions=2,
use_latency=False,
ramp_up_rate=None,
ramp_up_duration=None,
- state_timeout=300.0,
+ state_timeout=240.0,
expansion_coefficient=4.0,
):
"""Setup initialized TG, perform optimized search, return intervals.
use_latency=False,
ramp_up_rate=None,
ramp_up_duration=None,
- state_timeout=300.0,
+ state_timeout=240.0,
):
"""Setup initialized TG, perform soak search, return avg and stdev.
:type ramp_up_rate: float
:type ramp_up_duration: float
:type state_timeout: float
- :returns: Average and stdev of estimated aggregate rate giving PLR.
+ :returns: Average and stdev of estimated aggregated rate giving PLR.
:rtype: 2-tuple of float
"""
tg_instance = BuiltIn().get_library_instance(