X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FTrafficGenerator.py;h=c63dc2d50468d50d9d804f6b0324a019de316549;hp=a73e471f7717d48ce10c4de67a035c5a9ee611ec;hb=79f5ba9bf7656972dd988508eff9465562dde42c;hpb=efcdd3eadfadb2814a9e3b67cce84b5dc89c7108 diff --git a/resources/libraries/python/TrafficGenerator.py b/resources/libraries/python/TrafficGenerator.py index a73e471f77..c63dc2d504 100644 --- a/resources/libraries/python/TrafficGenerator.py +++ b/resources/libraries/python/TrafficGenerator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Cisco and/or its affiliates. +# Copyright (c) 2020 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: @@ -18,16 +18,18 @@ import time from robot.api import logger from robot.libraries.BuiltIn import BuiltIn -from .DropRateSearch import DropRateSearch from .Constants import Constants -from .ssh import exec_cmd_no_error, exec_cmd -from .topology import NodeType -from .topology import NodeSubTypeTG -from .topology import Topology +from .CpuUtils import CpuUtils +from .DropRateSearch import DropRateSearch from .MLRsearch.AbstractMeasurer import AbstractMeasurer from .MLRsearch.MultipleLossRatioSearch import MultipleLossRatioSearch from .MLRsearch.ReceiveRateMeasurement import ReceiveRateMeasurement from .PLRsearch.PLRsearch import PLRsearch +from .OptionString import OptionString +from .ssh import exec_cmd_no_error, exec_cmd +from .topology import NodeType +from .topology import NodeSubTypeTG +from .topology import Topology __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"] @@ -73,7 +75,7 @@ class TGDropRateSearchImpl(DropRateSearch): :param loss_acceptance: Permitted drop ratio or frames count. :param loss_acceptance_type: Type of permitted loss. :param traffic_profile: Module name as a traffic profile identifier. - See resources/traffic_profiles/trex for implemented modules. + See GPL/traffic_profiles/trex for implemented modules. :param skip_warmup: Start TRex without warmup traffic if true. :type rate: float :type frame_size: str @@ -149,6 +151,8 @@ class TrafficGenerator(AbstractMeasurer): self._sent = None self._latency = None self._received = None + self._approximated_rate = None + self._approximated_duration = None # Measurement input fields, needed for async stop result. self._start_time = None self._rate = None @@ -202,6 +206,15 @@ class TrafficGenerator(AbstractMeasurer): """ return self._latency + def get_approximated_rate(self): + """Return approximated rate computed as ratio of transmited packets over + duration of trial. + + :returns: Approximated rate. + :rtype: str + """ + return self._approximated_rate + # TODO: pylint says disable=too-many-locals. # A fix is developed in https://gerrit.fd.io/r/c/csit/+/22221 def initialize_traffic_generator( @@ -277,6 +290,11 @@ class TrafficGenerator(AbstractMeasurer): if1_adj_addr, if2_adj_addr = if2_adj_addr, if1_adj_addr self._ifaces_reordered = True + master_thread_id, latency_thread_id, socket, threads = \ + CpuUtils.get_affinity_trex( + self._node, tg_if1, tg_if2, + tg_dtc=Constants.TREX_CORE_COUNT) + if osi_layer in (u"L2", u"L3"): dst_mac0 = f"0x{if1_adj_addr.replace(u':', u',0x')}" src_mac0 = f"0x{if1_addr.replace(u':', u',0x')}" @@ -286,6 +304,7 @@ class TrafficGenerator(AbstractMeasurer): self._node, f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n" f"- version: 2\n" + f" c: {len(threads)}\n" f" limit_memory: {Constants.TREX_LIMIT_MEMORY}\n" f" interfaces: [\"{if1_pci}\",\"{if2_pci}\"]\n" f" port_info:\n" @@ -293,14 +312,21 @@ class TrafficGenerator(AbstractMeasurer): f" src_mac: [{src_mac0}]\n" f" - dest_mac: [{dst_mac1}]\n" f" src_mac: [{src_mac1}]\n" + f" platform :\n" + f" master_thread_id: {master_thread_id}\n" + f" latency_thread_id: {latency_thread_id}\n" + f" dual_if:\n" + f" - socket: {socket}\n" + f" threads: {threads}\n" f"EOF'", - sudo=True, message=u"TRex config generation error" + sudo=True, message=u"TRex config generation!" ) elif osi_layer == u"L7": exec_cmd_no_error( self._node, f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n" f"- version: 2\n" + f" c: {len(threads)}\n" f" limit_memory: {Constants.TREX_LIMIT_MEMORY}\n" f" interfaces: [\"{if1_pci}\",\"{if2_pci}\"]\n" f" port_info:\n" @@ -308,70 +334,94 @@ class TrafficGenerator(AbstractMeasurer): f" default_gw: [{if1_adj_addr}]\n" f" - ip: [{if2_addr}]\n" f" default_gw: [{if2_adj_addr}]\n" + f" platform :\n" + f" master_thread_id: {master_thread_id}\n" + f" latency_thread_id: {latency_thread_id}\n" + f" dual_if:\n" + f" - socket: {socket}\n" + f" threads: {threads}\n" f"EOF'", - sudo=True, message=u"TRex config generation error" + sudo=True, message=u"TRex config generation!" ) else: - raise ValueError(u"Unknown Test Type") + raise ValueError(u"Unknown Test Type!") - self._startup_trex(osi_layer) + TrafficGenerator.startup_trex( + self._node, osi_layer, subtype=subtype + ) - def _startup_trex(self, osi_layer): + @staticmethod + def startup_trex(tg_node, osi_layer, subtype=None): """Startup sequence for the TRex traffic generator. + :param tg_node: Traffic generator node. :param osi_layer: 'L2', 'L3' or 'L7' - OSI Layer testing type. + :param subtype: Traffic generator sub-type. + :type tg_node: dict :type osi_layer: str + :type subtype: NodeSubTypeTG :raises RuntimeError: If node subtype is not a TREX or startup failed. """ - # No need to check subtype, we know it is TREX. - for _ in range(0, 3): - # Kill TRex only if it is already running. - cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\"" - exec_cmd_no_error( - self._node, cmd, sudo=True, message=u"Kill TRex failed!" - ) - - # Configure TRex. - ports = '' - for port in self._node[u"interfaces"].values(): - 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( - self._node, cmd, sudo=True, - message=u"Unbind PCI ports from driver failed!" - ) - - # Start TRex. - cmd = f"sh -c \"cd {Constants.TREX_INSTALL_DIR}/scripts/ && " \ - f"nohup ./t-rex-64 -i -c {Constants.TREX_CORE_COUNT} --hdrh " \ - f"{u' --astf' if osi_layer == u'L7' else u''} " \ - f"--prefix $(hostname) {Constants.TREX_EXTRA_CMDLINE} " \ - f"> /tmp/trex.log 2>&1 &\" > /dev/null" - try: - exec_cmd_no_error(self._node, cmd, sudo=True) - except RuntimeError: - cmd = u"sh -c \"cat /tmp/trex.log\"" + if not subtype: + subtype = check_subtype(tg_node) + if subtype == NodeSubTypeTG.TREX: + for _ in range(0, 3): + # Kill TRex only if it is already running. + cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\"" exec_cmd_no_error( - self._node, cmd, sudo=True, message=u"Get TRex logs failed!" + tg_node, cmd, sudo=True, message=u"Kill TRex failed!" ) - raise RuntimeError(u"Start TRex failed!") - # Test if TRex starts successfuly. - cmd = f"sh -c \"{Constants.REMOTE_FW_DIR}/resources/tools/trex/" \ - f"trex_server_info.py\"" - try: + # Configure TRex. + ports = '' + for port in tg_node[u"interfaces"].values(): + 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( - self._node, cmd, sudo=True, message=u"Test TRex failed!", - retries=20 + tg_node, cmd, sudo=True, + message=u"Unbind PCI ports from driver failed!" ) - except RuntimeError: - continue - return - # After max retries TRex is still not responding to API critical error - # occurred. - raise RuntimeError(u"Start TRex failed after multiple retries!") + + # Start TRex. + cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'" + trex_cmd = OptionString([u"nohup", u"./t-rex-64"]) + trex_cmd.add(u"-i") + trex_cmd.add(u"--prefix $(hostname)") + trex_cmd.add(u"--hdrh") + trex_cmd.add(u"--no-scapy-server") + trex_cmd.add_if(u"--astf", osi_layer == u"L7") + # OptionString does not create double space if extra is empty. + trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}") + inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &" + cmd = f"sh -c \"{inner_command}\" > /dev/null" + try: + exec_cmd_no_error(tg_node, cmd, sudo=True) + except RuntimeError: + cmd = u"sh -c \"cat /tmp/trex.log\"" + exec_cmd_no_error( + tg_node, cmd, sudo=True, + message=u"Get TRex logs failed!" + ) + raise RuntimeError(u"Start TRex failed!") + + # Test if TRex starts successfuly. + command_line = OptionString().add(u"python3") + dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex" + command_line.add(f"'{dirname}/trex_server_info.py'") + try: + exec_cmd_no_error( + tg_node, command_line, sudo=True, + message=u"Test TRex failed!", retries=20 + ) + except RuntimeError: + continue + return + # After max retries TRex is still not responding to API critical + # error occurred. + exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True) + raise RuntimeError(u"Start TRex failed after multiple retries!") @staticmethod def is_trex_running(node): @@ -383,9 +433,7 @@ class TrafficGenerator(AbstractMeasurer): :rtype: bool :raises RuntimeError: If node type is not a TG. """ - # No need to check subtype, we know it is TREX. - - ret, _, _ = exec_cmd(node, u"pidof t-rex", sudo=True) + ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True) return bool(int(ret) == 0) @staticmethod @@ -401,8 +449,11 @@ class TrafficGenerator(AbstractMeasurer): subtype = check_subtype(node) if subtype == NodeSubTypeTG.TREX: exec_cmd_no_error( - node, u"sh -c \"sudo pkill t-rex && sleep 3\"", - sudo=False, message=u"pkill t-rex failed" + node, + u"sh -c " + u"\"if pgrep t-rex; then sudo pkill t-rex && sleep 3; fi\"", + sudo=False, + message=u"pkill t-rex failed" ) def _parse_traffic_results(self, stdout): @@ -421,9 +472,12 @@ class TrafficGenerator(AbstractMeasurer): self._received = self._result.split(u", ")[1].split(u"=", 1)[1] self._sent = self._result.split(u", ")[2].split(u"=", 1)[1] self._loss = self._result.split(u", ")[3].split(u"=", 1)[1] + self._approximated_duration = \ + self._result.split(u", ")[5].split(u"=", 1)[1] + self._approximated_rate = self._result.split(u", ")[6].split(u"=", 1)[1] self._latency = list() - self._latency.append(self._result.split(u", ")[4].split(u"=", 1)[1]) - self._latency.append(self._result.split(u", ")[5].split(u"=", 1)[1]) + self._latency.append(self._result.split(u", ")[7].split(u"=", 1)[1]) + self._latency.append(self._result.split(u", ")[8].split(u"=", 1)[1]) def trex_stl_stop_remote_exec(self, node): """Execute script on remote node over ssh to stop running traffic. @@ -435,16 +489,16 @@ class TrafficGenerator(AbstractMeasurer): :raises RuntimeError: If stop traffic script fails. """ # No need to check subtype, we know it is TREX. - x_args = u"" + command_line = OptionString().add(u"python3") + dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex" + command_line.add(f"'{dirname}/trex_stateless_stop.py'") + command_line.change_prefix(u"--") for index, value in enumerate(self._xstats): if value is not None: - # Nested quoting is fun. value = value.replace(u"'", u"\"") - x_args += f" --xstat{index}='\"'\"'{value}'\"'\"'" + command_line.add_equals(f"xstat{index}", f"'{value}'") stdout, _ = exec_cmd_no_error( - node, f"sh -c '{Constants.REMOTE_FW_DIR}/resources/tools/trex/" - f"trex_stateless_stop.py{x_args}'", - message=u"TRex stateless runtime error" + node, command_line, message=u"TRex stateless runtime error" ) self._parse_traffic_results(stdout) @@ -461,7 +515,7 @@ class TrafficGenerator(AbstractMeasurer): :param rate: Traffic rate expressed with units (pps, %) :param frame_size: L2 frame size to send (without padding and IPG). :param traffic_profile: Module name as a traffic profile identifier. - See resources/traffic_profiles/trex for implemented modules. + See GPL/traffic_profiles/trex for implemented modules. :param async_call: If enabled then don't wait for all incomming trafic. :param latency: With latency measurement. :param warmup_time: Warmup time period. @@ -486,28 +540,31 @@ class TrafficGenerator(AbstractMeasurer): # No need to check subtype, we know it is TREX. reorder = self._ifaces_reordered # Just to make the next line fit. p_0, p_1 = (rx_port, tx_port) if reorder else (tx_port, rx_port) - if not isinstance(duration, (float, int)): duration = float(duration) if not isinstance(warmup_time, (float, int)): warmup_time = float(warmup_time) - command = f"sh -c \"" \ - f"{Constants.REMOTE_FW_DIR}/resources/tools/trex/" \ - f"trex_stateless_profile.py" \ - f" --profile {Constants.REMOTE_FW_DIR}/resources/" \ - f"traffic_profiles/trex/{traffic_profile}.py" \ - f" --duration {duration!r} --frame_size {frame_size} " \ - f"--rate {rate!r} --warmup_time {warmup_time!r} " \ - f"--port_0 {p_0} --port_1 {p_1}" \ - f" --traffic_directions {traffic_directions}" - if async_call: - command += u" --async_start" - if latency: - command += u" --latency" - command += u"\"" + + command_line = OptionString().add(u"python3") + dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex" + command_line.add(f"'{dirname}/trex_stateless_profile.py'") + command_line.change_prefix(u"--") + dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex" + quoted_path = f"'{dirname}/{traffic_profile}.py'" + command_line.add_with_value(u"profile", quoted_path) + command_line.add_with_value(u"duration", f"{duration!r}") + command_line.add_with_value(u"frame_size", frame_size) + command_line.add_with_value(u"rate", f"{rate!r}") + command_line.add_with_value(u"warmup_time", f"{warmup_time!r}") + command_line.add_with_value(u"port_0", p_0) + command_line.add_with_value(u"port_1", p_1) + command_line.add_with_value(u"traffic_directions", traffic_directions) + command_line.add_if(u"async_start", async_call) + command_line.add_if(u"latency", latency) + command_line.add_if(u"force", Constants.TREX_SEND_FORCE) stdout, _ = exec_cmd_no_error( - self._node, command, timeout=float(duration) + 60, + self._node, command_line, timeout=float(duration) + 60, message=u"TRex stateless runtime error" ) @@ -575,7 +632,7 @@ class TrafficGenerator(AbstractMeasurer): :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...). :param frame_size: Frame size (L2) in Bytes. :param traffic_profile: Module name as a traffic profile identifier. - See resources/traffic_profiles/trex for implemented modules. + See GPL/traffic_profiles/trex for implemented modules. :param warmup_time: Warmup phase in seconds. :param async_call: Async mode. :param latency: With latency measurement. @@ -665,7 +722,7 @@ class TrafficGenerator(AbstractMeasurer): :param frame_size: Frame size identifier or value [B]. :param traffic_profile: Module name as a traffic profile identifier. - See resources/traffic_profiles/trex for implemented modules. + See GPL/traffic_profiles/trex for implemented modules. :param warmup_time: Traffic duration before measurement starts [s]. :param traffic_directions: Traffic is bi- (2) or uni- (1) directional. Default: 2 @@ -707,6 +764,7 @@ class TrafficGenerator(AbstractMeasurer): duration, transmit_rate, transmit_count, loss_count ) measurement.latency = self.get_latency_int() + measurement.approximated_rate = self.get_approximated_rate() return measurement def measure(self, duration, transmit_rate): @@ -755,7 +813,7 @@ class OptimizedSearch: :param frame_size: Frame size identifier or value [B]. :param traffic_profile: Module name as a traffic profile identifier. - See resources/traffic_profiles/trex for implemented modules. + See GPL/traffic_profiles/trex for implemented modules. :param minimum_transmit_rate: Minimal uni-directional target transmit rate [pps]. :param maximum_transmit_rate: Maximal uni-directional @@ -823,7 +881,7 @@ class OptimizedSearch: :param frame_size: Frame size identifier or value [B]. :param traffic_profile: Module name as a traffic profile identifier. - See resources/traffic_profiles/trex for implemented modules. + See GPL/traffic_profiles/trex for implemented modules. :param minimum_transmit_rate: Minimal uni-directional target transmit rate [pps]. :param maximum_transmit_rate: Maximal uni-directional