diff options
author | Sridhar Rao <sridhar.rao@spirent.com> | 2020-11-25 06:41:05 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@opnfv.org> | 2020-11-25 06:41:05 +0000 |
commit | d3e04a4d7ce6cdbbc6375ee37264d1528a6ae622 (patch) | |
tree | 8f3a914b278bc25d543e075d4832c33d4643f0d2 /tools/pkt_gen/trex/trex_client.py | |
parent | 656acff48dfba2f2945a3baf8d696007b8229200 (diff) | |
parent | 33918409da9a5d4b60d57cd05f3277c6ff1918cd (diff) |
Merge "Tools: User Latest Version of T-Rex."
Diffstat (limited to 'tools/pkt_gen/trex/trex_client.py')
-rw-r--r-- | tools/pkt_gen/trex/trex_client.py | 778 |
1 files changed, 778 insertions, 0 deletions
diff --git a/tools/pkt_gen/trex/trex_client.py b/tools/pkt_gen/trex/trex_client.py new file mode 100644 index 00000000..3d6836d8 --- /dev/null +++ b/tools/pkt_gen/trex/trex_client.py @@ -0,0 +1,778 @@ +# Copyright 2017 Martin Goldammer, OPNFV, Red Hat Inc. +# +# 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +Trex Traffic Generator Model +""" + +# pylint: disable=undefined-variable +import logging +import subprocess +import sys +import time +import os +import re +from collections import OrderedDict +# pylint: disable=unused-import +import netaddr +#import zmq +from conf import settings +from conf import merge_spec +from core.results.results_constants import ResultsConstants +from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator +try: + # pylint: disable=wrong-import-position, import-error + sys.path.append(settings.getValue('PATHS')['trafficgen']['Trex']['src']['path']) + from trex_stl_lib.api import * + # from trex_stl_lib import trex_stl_exceptions +except ImportError: + # VSPERF performs detection of T-Rex api during testcase initialization. So if + # T-Rex is requsted and API is not available it will fail before this code + # is reached. + # This code can be reached in case that --list-trafficgens is called, but T-Rex + # api is not installed. In this case we can ignore an exception, becuase T-Rex + # import won't be used. + pass + +_EMPTY_STATS = { + 'global': {'bw_per_core': 0.0, + 'cpu_util': 0.0, + 'queue_full': 0.0, + 'rx_bps': 0.0, + 'rx_cpu_util': 0.0, + 'rx_drop_bps': 0.0, + 'rx_pps': 0.0, + 'tx_bps': 0.0, + 'tx_pps': 0.0,}, + 'latency': {}, + 'total': {'ibytes': 0.0, + 'ierrors': 0.0, + 'ipackets': 0.0, + 'obytes': 0.0, + 'oerrors': 0.0, + 'opackets': 0.0, + 'rx_bps': 0.0, + 'rx_bps_L1': 0.0, + 'rx_pps': 0.0, + 'rx_util': 0.0, + 'tx_bps': 0.0, + 'tx_bps_L1': 0.0, + 'tx_pps': 0.0, + 'tx_util': 0.0,}} + +# Default frame definition, which can be overridden by TRAFFIC['scapy']. +# The content of the frame and its network layers are driven by TRAFFIC +# dictionary, i.e. 'l2', 'l3, 'l4' and 'vlan' parts. +_SCAPY_FRAME = { + '0' : 'Ether(src={Ether_src}, dst={Ether_dst})/' + 'Dot1Q(prio={Dot1Q_prio}, id={Dot1Q_id}, vlan={Dot1Q_vlan})/' + 'IP(proto={IP_proto}, src={IP_src}, dst={IP_dst})/' + '{IP_PROTO}(sport={IP_PROTO_sport}, dport={IP_PROTO_dport})', + '1' : 'Ether(src={Ether_dst}, dst={Ether_src})/' + 'Dot1Q(prio={Dot1Q_prio}, id={Dot1Q_id}, vlan={Dot1Q_vlan})/' + 'IP(proto={IP_proto}, src={IP_dst}, dst={IP_src})/' + '{IP_PROTO}(sport={IP_PROTO_dport}, dport={IP_PROTO_sport})', +} + + +class Trex(ITrafficGenerator): + """Trex Traffic generator wrapper.""" + _logger = logging.getLogger(__name__) + + def __init__(self): + """Trex class constructor.""" + super().__init__() + self._logger.info("In trex __init__ method") + self._params = {} + self._trex_host_ip_addr = ( + settings.getValue('TRAFFICGEN_TREX_HOST_IP_ADDR')) + self._trex_base_dir = ( + settings.getValue('TRAFFICGEN_TREX_BASE_DIR')) + self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER') + self._stlclient = None + self._verification_params = None + self._show_packet_data = False + + def show_packet_info(self, packet_a, packet_b): + """ + Log packet layers to screen + :param packet_a: Scapy.layers packet + :param packet_b: Scapy.layers packet + :return: None + """ + # we only want to show packet data once per test + if self._show_packet_data: + self._show_packet_data = False + self._logger.info(packet_a.show()) + self._logger.info(packet_b.show()) + + def connect(self): + '''Connect to Trex traffic generator + + Verify that Trex is on the system indicated by + the configuration file + ''' + self._stlclient = STLClient() + self._logger.info("T-Rex: In Trex connect method...") + if self._trex_host_ip_addr: + cmd_ping = "ping -c1 " + self._trex_host_ip_addr + else: + raise RuntimeError('T-Rex: Trex host not defined') + + ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE) + output, error = ping.communicate() + + if ping.returncode: + self._logger.error(error) + self._logger.error(output) + raise RuntimeError('T-Rex: Cannot ping Trex host at ' + \ + self._trex_host_ip_addr) + + connect_trex = "ssh " + self._trex_user + \ + "@" + self._trex_host_ip_addr + + cmd_find_trex = connect_trex + " ls " + \ + self._trex_base_dir + "t-rex-64" + + + find_trex = subprocess.Popen(cmd_find_trex, + shell=True, + stderr=subprocess.PIPE) + output, error = find_trex.communicate() + + if find_trex.returncode: + self._logger.error(error) + self._logger.error(output) + raise RuntimeError( + 'T-Rex: Cannot locate Trex program at %s within %s' \ + % (self._trex_host_ip_addr, self._trex_base_dir)) + + try: + self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr, + verbose_level='info') + self._stlclient.connect() + except STLError: + raise RuntimeError('T-Rex: Cannot connect to T-Rex server. Please check if it is ' + 'running and that firewall allows connection to TCP port 4501.') + + self._logger.info("T-Rex: Trex host successfully found...") + + def disconnect(self): + """Disconnect from the traffic generator. + + As with :func:`connect`, this function is optional. + + Where implemented, this function should raise an exception on + failure. + + :returns: None + """ + self._logger.info("T-Rex: In trex disconnect method") + self._stlclient.disconnect(stop_traffic=True, release_ports=True) + + def create_packets(self, traffic, ports_info): + """Create base packet according to traffic specification. + If traffic haven't specified srcmac and dstmac fields + packet will be created with mac address of trex server. + """ + if not traffic or traffic['l2']['framesize'] <= 0: + return (None, None) + + if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \ + traffic['l2']['srcmac'] == '00:00:00:00:00:00': + + mac_add = [li['hw_mac'] for li in ports_info] + src_mac = mac_add[0] + dst_mac = mac_add[1] + else: + src_mac = traffic['l2']['srcmac'] + dst_mac = traffic['l2']['dstmac'] + + if traffic['scapy']['enabled']: + base_pkt_a = traffic['scapy']['0'] + base_pkt_b = traffic['scapy']['1'] + else: + base_pkt_a = _SCAPY_FRAME['0'] + base_pkt_b = _SCAPY_FRAME['1'] + + # check and remove network layers disabled by TRAFFIC dictionary + # Note: In general, it is possible to remove layers from scapy object by + # e.g. del base_pkt_a['IP']. However it doesn't work for all layers + # (e.g. Dot1Q). Thus it is safer to modify string with scapy frame definition + # directly, before it is converted to the real scapy object. + if not traffic['vlan']['enabled']: + self._logger.info('VLAN headers are disabled by TRAFFIC') + base_pkt_a = re.sub(r'(^|\/)Dot1Q?\([^\)]*\)', '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)Dot1Q?\([^\)]*\)', '', base_pkt_b) + if not traffic['l3']['enabled']: + self._logger.info('IP headers are disabled by TRAFFIC') + base_pkt_a = re.sub(r'(^|\/)IP(v6)?\([^\)]*\)', '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)IP(v6)?\([^\)]*\)', '', base_pkt_b) + if not traffic['l4']['enabled']: + self._logger.info('%s headers are disabled by TRAFFIC', + traffic['l3']['proto'].upper()) + base_pkt_a = re.sub(r'(^|\/)(UDP|TCP|SCTP|{{IP_PROTO}}|{})\([^\)]*\)'.format( + traffic['l3']['proto'].upper()), '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)(UDP|TCP|SCTP|{{IP_PROTO}}|{})\([^\)]*\)'.format( + traffic['l3']['proto'].upper()), '', base_pkt_b) + + # pylint: disable=eval-used + base_pkt_a = eval(base_pkt_a.format( + Ether_src=repr(src_mac), + Ether_dst=repr(dst_mac), + Dot1Q_prio=traffic['vlan']['priority'], + Dot1Q_id=traffic['vlan']['cfi'], + Dot1Q_vlan=traffic['vlan']['id'], + IP_proto=repr(traffic['l3']['proto']), + IP_PROTO=traffic['l3']['proto'].upper(), + IP_src=repr(traffic['l3']['srcip']), + IP_dst=repr(traffic['l3']['dstip']), + IP_PROTO_sport=traffic['l4']['srcport'], + IP_PROTO_dport=traffic['l4']['dstport'])) + base_pkt_b = eval(base_pkt_b.format( + Ether_src=repr(src_mac), + Ether_dst=repr(dst_mac), + Dot1Q_prio=traffic['vlan']['priority'], + Dot1Q_id=traffic['vlan']['cfi'], + Dot1Q_vlan=traffic['vlan']['id'], + IP_proto=repr(traffic['l3']['proto']), + IP_PROTO=traffic['l3']['proto'].upper(), + IP_src=repr(traffic['l3']['srcip']), + IP_dst=repr(traffic['l3']['dstip']), + IP_PROTO_sport=traffic['l4']['srcport'], + IP_PROTO_dport=traffic['l4']['dstport'])) + + return (base_pkt_a, base_pkt_b) + + @staticmethod + def create_streams(base_pkt_a, base_pkt_b, traffic): + """Add the base packet to the streams. Erase FCS and add payload + according to traffic specification + """ + stream_1_lat = None + stream_2_lat = None + frame_size = int(traffic['l2']['framesize']) + fsize_no_fcs = frame_size - 4 + payload_a = max(0, fsize_no_fcs - len(base_pkt_a)) * 'x' + payload_b = max(0, fsize_no_fcs - len(base_pkt_b)) * 'x' + + # Multistream configuration, increments source values only + ms_mod = list() # mod list for incrementing values to be populated based on layer + if traffic['multistream'] > 1: + if traffic['stream_type'].upper() == 'L2': + for _ in [base_pkt_a, base_pkt_b]: + ms_mod += [STLVmFlowVar(name="mac_start", min_value=0, + max_value=traffic['multistream'] - 1, size=4, op="inc"), + STLVmWrFlowVar(fv_name="mac_start", pkt_offset=7)] + elif traffic['stream_type'].upper() == 'L3': + ip_src = {"start": int(netaddr.IPAddress(traffic['l3']['srcip'])), + "end": int(netaddr.IPAddress(traffic['l3']['srcip'])) + traffic['multistream'] - 1} + ip_dst = {"start": int(netaddr.IPAddress(traffic['l3']['dstip'])), + "end": int(netaddr.IPAddress(traffic['l3']['dstip'])) + traffic['multistream'] - 1} + for ip_address in [ip_src, ip_dst]: + ms_mod += [STLVmFlowVar(name="ip_src", min_value=ip_address['start'], + max_value=ip_address['end'], size=4, op="inc"), + STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP.src")] + elif traffic['stream_type'].upper() == 'L4': + for udpport in [traffic['l4']['srcport'], traffic['l4']['dstport']]: + if udpport + (traffic['multistream'] - 1) > 65535: + start_port = udpport + # find the max/min port number based on the loop around of 65535 to 0 if needed + minimum_value = 65535 - (traffic['multistream'] -1) + maximum_value = 65535 + else: + start_port, minimum_value = udpport, udpport + maximum_value = start_port + (traffic['multistream'] - 1) + ms_mod += [STLVmFlowVar(name="port_src", init_value=start_port, + min_value=minimum_value, max_value=maximum_value, + size=2, op="inc"), + STLVmWrFlowVar(fv_name="port_src", pkt_offset="UDP.sport"),] + + if ms_mod: # multistream detected + pkt_a = STLPktBuilder(pkt=base_pkt_a/payload_a, vm=[ms_mod[0], ms_mod[1]]) + pkt_b = STLPktBuilder(pkt=base_pkt_b/payload_b, vm=[ms_mod[2], ms_mod[3]]) + else: + pkt_a = STLPktBuilder(pkt=base_pkt_a / payload_a) + pkt_b = STLPktBuilder(pkt=base_pkt_b / payload_b) + + lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') + if traffic['traffic_type'] == 'burst': + if lat_pps > 0: + # latency statistics are requested; in case of frame burst we can enable + # statistics for all frames + stream_1 = STLStream(packet=pkt_a, + flow_stats=STLFlowLatencyStats(pg_id=0), + name='stream_1', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + stream_2 = STLStream(packet=pkt_b, + flow_stats=STLFlowLatencyStats(pg_id=1), + name='stream_2', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + else: + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + else: + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXCont(percentage=traffic['frame_rate'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXCont(percentage=traffic['frame_rate'])) + # workaround for latency statistics, which can't be enabled for streams + # with high framerate due to the huge performance impact + if lat_pps > 0: + stream_1_lat = STLStream(packet=pkt_a, + flow_stats=STLFlowLatencyStats(pg_id=0), + name='stream_1_lat', + mode=STLTXCont(pps=lat_pps)) + stream_2_lat = STLStream(packet=pkt_b, + flow_stats=STLFlowLatencyStats(pg_id=1), + name='stream_2_lat', + mode=STLTXCont(pps=lat_pps)) + + return (stream_1, stream_2, stream_1_lat, stream_2_lat) + + + # pylint: disable=too-many-locals, too-many-statements + def generate_traffic(self, traffic, duration, disable_capture=False): + """The method that generate a stream + """ + my_ports = [0, 1] + + # initialize ports + self._stlclient.reset(my_ports) + self._stlclient.remove_all_captures() + self._stlclient.set_service_mode(ports=my_ports, enabled=False) + + ports_info = self._stlclient.get_port_info(my_ports) + + # get max support speed + max_speed = 0 + if settings.getValue('TRAFFICGEN_TREX_FORCE_PORT_SPEED'): + max_speed = settings.getValue('TRAFFICGEN_TREX_PORT_SPEED') + elif ports_info[0]['supp_speeds']: + max_speed_1 = max(ports_info[0]['supp_speeds']) + max_speed_2 = max(ports_info[1]['supp_speeds']) + else: + # if max supported speed not in port info or set manually, just assume 10G + max_speed = 10000 + if not max_speed: + # since we can only control both ports at once take the lower of the two + max_speed = min(max_speed_1, max_speed_2) + gbps_speed = (max_speed / 1000) * (float(traffic['frame_rate']) / 100.0) + self._logger.debug('Starting traffic at %s Gbps speed', gbps_speed) + + # for SR-IOV + if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'): + self._stlclient.set_port_attr(my_ports, promiscuous=True) + + packet_1, packet_2 = self.create_packets(traffic, ports_info) + self.show_packet_info(packet_1, packet_2) + stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic) + self._stlclient.add_streams(stream_1, ports=[0]) + self._stlclient.add_streams(stream_2, ports=[1]) + + if stream_1_lat is not None: + self._stlclient.add_streams(stream_1_lat, ports=[0]) + self._stlclient.add_streams(stream_2_lat, ports=[1]) + + # enable traffic capture if requested + pcap_id = {} + if traffic['capture']['enabled'] and not disable_capture: + for ports in ['tx_ports', 'rx_ports']: + if traffic['capture'][ports]: + pcap_dir = ports[:2] + self._logger.info("T-Rex starting %s traffic capture", pcap_dir.upper()) + capture = {ports : traffic['capture'][ports], + 'limit' : traffic['capture']['count'], + 'bpf_filter' : traffic['capture']['filter']} + self._stlclient.set_service_mode(ports=traffic['capture'][ports], enabled=True) + pcap_id[pcap_dir] = self._stlclient.start_capture(**capture) + + self._stlclient.clear_stats() + # if the user did not start up T-Rex server with more than default cores, use default mask. + # Otherwise use mask to take advantage of multiple cores. + try: + self._stlclient.start(ports=my_ports, force=True, duration=duration, mult="{}gbps".format(gbps_speed), + core_mask=self._stlclient.CORE_MASK_PIN) + except STLError: + self._stlclient.start(ports=my_ports, force=True, duration=duration, mult="{}gbps".format(gbps_speed)) + + if settings.getValue('TRAFFICGEN_TREX_LIVE_RESULTS'): + filec = os.path.join(settings.getValue('RESULTS_PATH'), + settings.getValue('TRAFFICGEN_TREX_LC_FILE')) + filee = os.path.join(settings.getValue('RESULTS_PATH'), + settings.getValue('TRAFFICGEN_TREX_LE_FILE')) + pgids = self._stlclient.get_active_pgids() + rx_port_0 = 1 + tx_port_0 = 0 + rx_port_1 = 0 + tx_port_1 = 1 + with open(filec, 'a') as fcp, open(filee, 'a') as fep: + fcp.write("ts,rx_port,tx_port,rx_pkts,tx_pkts,rx_pps,tx_pps,"+ + "rx_bps_num,rx_bps_den,tx_bps_num,tx_bps_den\n") + fep.write('ts,dropped,ooo,dup,seq_too_high,seq_too_low\n') + while True: + tr_status = self._stlclient.is_traffic_active(ports=my_ports) + if not tr_status: + break + time.sleep(1) + stats = self._stlclient.get_pgid_stats(pgids['flow_stats']) + lat_stats = stats['latency'].get(0) + flow_stats_0 = stats['flow_stats'].get(0) + flow_stats_1 = stats['flow_stats'].get(1) + if flow_stats_0: + rx_pkts = flow_stats_0['rx_pkts'][rx_port_0] + tx_pkts = flow_stats_0['tx_pkts'][tx_port_0] + rx_pps = flow_stats_0['rx_pps'][rx_port_0] + tx_pps = flow_stats_0['tx_pps'][tx_port_0] + rx_bps = flow_stats_0['rx_bps'][rx_port_0] + tx_bps = flow_stats_0['tx_bps'][tx_port_0] + rx_bps_l1 = flow_stats_0['rx_bps_l1'][rx_port_0] + tx_bps_l1 = flow_stats_0['tx_bps_l1'][tx_port_0] + # https://github.com/cisco-system-traffic-generator/\ + # trex-core/blob/master/scripts/automation/\ + # trex_control_plane/interactive/trex/examples/\ + # stl/stl_flow_latency_stats.py + fcp.write("{10},{8},{9},{0},{1},{2},{3},{4},{5},{6},{7}\n" + .format(rx_pkts, tx_pkts, rx_pps, tx_pps, + rx_bps, rx_bps_l1, tx_bps, tx_bps_l1, + rx_port_0, tx_port_0, time.time())) + if flow_stats_1: + rx_pkts = flow_stats_1['rx_pkts'][rx_port_1] + tx_pkts = flow_stats_1['tx_pkts'][tx_port_1] + rx_pps = flow_stats_1['rx_pps'][rx_port_1] + tx_pps = flow_stats_1['tx_pps'][tx_port_1] + rx_bps = flow_stats_1['rx_bps'][rx_port_1] + tx_bps = flow_stats_1['tx_bps'][tx_port_1] + rx_bps_l1 = flow_stats_1['rx_bps_l1'][rx_port_1] + tx_bps_l1 = flow_stats_1['tx_bps_l1'][tx_port_1] + fcp.write("{10},{8},{9},{0},{1},{2},{3},{4},{5},{6},{7}\n" + .format(rx_pkts, tx_pkts, rx_pps, tx_pps, + rx_bps, rx_bps_l1, tx_bps, tx_bps_l1, + rx_port_1, tx_port_1, time.time())) + if lat_stats: + drops = lat_stats['err_cntrs']['dropped'] + ooo = lat_stats['err_cntrs']['out_of_order'] + dup = lat_stats['err_cntrs']['dup'] + sth = lat_stats['err_cntrs']['seq_too_high'] + stl = lat_stats['err_cntrs']['seq_too_low'] + fep.write('{5},{0},{1},{2},{3},{4}\n' + .format(drops, ooo, dup, sth, stl, time.time())) + else: + self._stlclient.wait_on_traffic(ports=my_ports) + stats = self._stlclient.get_stats(sync_now=True) + + # export captured data into pcap file if possible + if pcap_id: + for pcap_dir in pcap_id: + pcap_file = 'capture_{}.pcap'.format(pcap_dir) + self._stlclient.stop_capture(pcap_id[pcap_dir]['id'], + os.path.join(settings.getValue('RESULTS_PATH'), pcap_file)) + stats['capture_{}'.format(pcap_dir)] = pcap_file + self._logger.info("T-Rex writing %s traffic capture into %s", pcap_dir.upper(), pcap_file) + # disable service mode for all ports used by Trex + self._stlclient.set_service_mode(ports=my_ports, enabled=False) + + return stats + + @staticmethod + def calculate_results(stats): + """Calculate results from Trex statistic + """ + result = OrderedDict() + result[ResultsConstants.TX_FRAMES] = ( + stats["total"]["opackets"]) + result[ResultsConstants.RX_FRAMES] = ( + stats["total"]["ipackets"]) + result[ResultsConstants.TX_RATE_FPS] = ( + '{:.3f}'.format( + float(stats["total"]["tx_pps"]))) + + result[ResultsConstants.THROUGHPUT_RX_FPS] = ( + '{:.3f}'.format( + float(stats["total"]["rx_pps"]))) + + result[ResultsConstants.TX_RATE_MBPS] = ( + '{:.3f}'.format( + float(stats["total"]["tx_bps"] / 1000000))) + result[ResultsConstants.THROUGHPUT_RX_MBPS] = ( + '{:.3f}'.format( + float(stats["total"]["rx_bps"] / 1000000))) + + result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown' + + result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown' + if stats["total"]["opackets"]: + result[ResultsConstants.FRAME_LOSS_PERCENT] = ( + '{:.3f}'.format( + float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 / + stats["total"]["opackets"]))) + else: + result[ResultsConstants.FRAME_LOSS_PERCENT] = 100 + + if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']: + try: + result[ResultsConstants.MIN_LATENCY_NS] = ( + '{:.3f}'.format( + (float(min(stats["latency"][0]["latency"]["total_min"], + stats["latency"][1]["latency"]["total_min"]))))) + except TypeError: + result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' + + try: + result[ResultsConstants.MAX_LATENCY_NS] = ( + '{:.3f}'.format( + (float(max(stats["latency"][0]["latency"]["total_max"], + stats["latency"][1]["latency"]["total_max"]))))) + except TypeError: + result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown' + + try: + result[ResultsConstants.AVG_LATENCY_NS] = ( + '{:.3f}'.format( + float((stats["latency"][0]["latency"]["average"]+ + stats["latency"][1]["latency"]["average"])/2))) + except TypeError: + result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown' + + else: + result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' + result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown' + result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown' + + if 'capture_tx' in stats: + result[ResultsConstants.CAPTURE_TX] = stats['capture_tx'] + if 'capture_rx' in stats: + result[ResultsConstants.CAPTURE_RX] = stats['capture_rx'] + return result + + def learning_packets(self, traffic): + """ + Send learning packets before testing + :param traffic: traffic structure as per send_cont_traffic guidelines + :return: None + """ + self._logger.info("T-Rex sending learning packets") + learning_thresh_traffic = copy.deepcopy(traffic) + learning_thresh_traffic["frame_rate"] = 1 + self.generate_traffic(learning_thresh_traffic, + settings.getValue("TRAFFICGEN_TREX_LEARNING_DURATION"), + disable_capture=True) + self._logger.info("T-Rex finished learning packets") + time.sleep(3) # allow packets to complete before starting test traffic + + def run_trials(self, traffic, boundaries, duration, lossrate): + """ + Run rfc2544 trial loop + :param traffic: traffic profile dictionary + :param boundaries: A dictionary of three keys left, right, center to dictate + the highest, lowest, and starting point of the binary search. + Values are percentages of line rates for each key. + :param duration: length in seconds for trials + :param lossrate: loweset loss rate percentage calculated from + comparision between received and sent packets + :return: passing stats as dictionary + """ + threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD') + max_repeat = settings.getValue('TRAFFICGEN_TREX_RFC2544_MAX_REPEAT') + loss_verification = settings.getValue('TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION') + if loss_verification: + self._logger.info("Running Binary Search with Loss Verification") + stats_ok = _EMPTY_STATS + new_params = copy.deepcopy(traffic) + iteration = 1 + repeat = 0 + left = boundaries['left'] + right = boundaries['right'] + center = boundaries['center'] + self._logger.info('Starting RFC2544 trials') + while (right - left) > threshold: + stats = self.generate_traffic(new_params, duration) + test_lossrate = ((stats["total"]["opackets"] - stats[ + "total"]["ipackets"]) * 100) / stats["total"]["opackets"] + if stats["total"]["ipackets"] == 0: + self._logger.error('No packets recieved. Test failed') + return _EMPTY_STATS + if settings.getValue('TRAFFICGEN_TREX_VERIFICATION_MODE'): + if test_lossrate <= lossrate: + # save the last passing trial for verification + self._verification_params = copy.deepcopy(new_params) + packets_lost = stats['total']['opackets'] - stats['total']['ipackets'] + self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s," + + " frames lost %s, frame_loss_percent: %s", iteration, + "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'], + packets_lost, "{:.3f}".format(test_lossrate)) + if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']: + return copy.deepcopy(stats) + elif test_lossrate > lossrate: + if loss_verification: + if repeat < max_repeat: + repeat += 1 + iteration += 1 + continue + else: + repeat = 0 + right = center + center = (left + right) / 2 + new_params = copy.deepcopy(traffic) + new_params['frame_rate'] = center + else: + if loss_verification: + repeat = 0 + stats_ok = copy.deepcopy(stats) + left = center + center = (left + right) / 2 + new_params = copy.deepcopy(traffic) + new_params['frame_rate'] = center + iteration += 1 + return stats_ok + + def send_cont_traffic(self, traffic=None, duration=30): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex send_cont_traffic method") + self._params.clear() + + self._show_packet_data = True + + self._params['traffic'] = self.traffic_defaults.copy() + if traffic: + self._params['traffic'] = merge_spec( + self._params['traffic'], traffic) + + if settings.getValue('TRAFFICGEN_TREX_LEARNING_MODE'): + self.learning_packets(traffic) + self._logger.info("T-Rex sending traffic") + stats = self.generate_traffic(traffic, duration) + + return self.calculate_results(stats) + + def start_cont_traffic(self, traffic=None, duration=30): + raise NotImplementedError( + 'Trex start cont traffic not implemented') + + def stop_cont_traffic(self): + """See ITrafficGenerator for description + """ + raise NotImplementedError( + 'Trex stop_cont_traffic method not implemented') + + def send_rfc2544_throughput(self, traffic=None, tests=1, duration=60, + lossrate=0.0): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex send_rfc2544_throughput method") + self._params.clear() + self._show_packet_data = True + self._params['traffic'] = self.traffic_defaults.copy() + if traffic: + self._params['traffic'] = merge_spec( + self._params['traffic'], traffic) + if settings.getValue('TRAFFICGEN_TREX_LEARNING_MODE'): + self.learning_packets(traffic) + self._verification_params = copy.deepcopy(traffic) + + binary_bounds = {'right' : traffic['frame_rate'], + 'left' : 0, + 'center': traffic['frame_rate'],} + + # Loops until the preconfigured differencde between frame rate + # of successful and unsuccessful iterations is reached + stats_ok = self.run_trials(boundaries=binary_bounds, duration=duration, + lossrate=lossrate, traffic=traffic) + if settings.getValue('TRAFFICGEN_TREX_VERIFICATION_MODE'): + verification_iterations = 1 + while verification_iterations <= settings.getValue('TRAFFICGEN_TREX_MAXIMUM_VERIFICATION_TRIALS'): + self._logger.info('Starting Trex Verification trial for %s seconds at frame rate %s', + settings.getValue('TRAFFICGEN_TREX_VERIFICATION_DURATION'), + self._verification_params['frame_rate']) + stats = self.generate_traffic(self._verification_params, + settings.getValue('TRAFFICGEN_TREX_VERIFICATION_DURATION')) + verification_lossrate = ((stats["total"]["opackets"] - stats[ + "total"]["ipackets"]) * 100) / stats["total"]["opackets"] + if verification_lossrate <= lossrate: + self._logger.info('Trex Verification passed, %s packets were lost', + stats["total"]["opackets"] - stats["total"]["ipackets"]) + stats_ok = copy.deepcopy(stats) + break + else: + self._logger.info('Trex Verification failed, %s packets were lost', + stats["total"]["opackets"] - stats["total"]["ipackets"]) + new_right = self._verification_params['frame_rate'] - settings.getValue( + 'TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD') + self._verification_params['frame_rate'] = new_right + binary_bounds = {'right': new_right, + 'left': 0, + 'center': new_right,} + stats_ok = self.run_trials(boundaries=binary_bounds, duration=duration, + lossrate=lossrate, traffic=self._verification_params) + verification_iterations += 1 + else: + self._logger.error('Could not pass Trex Verification. Test failed') + return self.calculate_results(stats_ok) + + def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60, + lossrate=0.0): + raise NotImplementedError( + 'Trex start rfc2544 throughput not implemented') + + def wait_rfc2544_throughput(self): + raise NotImplementedError( + 'Trex wait rfc2544 throughput not implemented') + + def send_burst_traffic(self, traffic=None, duration=20): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex send_burst_traffic method") + self._params.clear() + + self._params['traffic'] = self.traffic_defaults.copy() + if traffic: + self._params['traffic'] = merge_spec( + self._params['traffic'], traffic) + + if settings.getValue('TRAFFICGEN_TREX_LEARNING_MODE'): + self.learning_packets(traffic) + self._logger.info("T-Rex sending traffic") + stats = self.generate_traffic(traffic, duration) + + time.sleep(3) # allow packets to complete before reading stats + + return self.calculate_results(stats) + + def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30, + lossrate=0.0): + raise NotImplementedError( + 'Trex send rfc2544 back2back not implemented') + + def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30, + lossrate=0.0): + raise NotImplementedError( + 'Trex start rfc2544 back2back not implemented') + + def wait_rfc2544_back2back(self): + raise NotImplementedError( + 'Trex wait rfc2544 back2back not implemented') + +if __name__ == "__main__": + pass |