X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=nfvbench%2Ftraffic_client.py;h=47af2651ab419f9a6b84d7a9a2bb7100d4ec8333;hb=8ecfd4c886507fe602398a8623e6044d40ea8090;hp=e2ae977fcce70775749d6d9df9a874d3d8a57d73;hpb=64579b717d47ab7f654c574794831be984bf32e1;p=nfvbench.git diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py index e2ae977..47af265 100755 --- a/nfvbench/traffic_client.py +++ b/nfvbench/traffic_client.py @@ -13,10 +13,10 @@ # under the License. """Interface to the traffic generator clients including NDR/PDR binary search.""" -from math import gcd import socket import struct import time +import sys from attrdict import AttrDict import bitmath @@ -37,7 +37,7 @@ from .packet_stats import PacketPathStats from .stats_collector import IntervalCollector from .stats_collector import IterationCollector from .traffic_gen import traffic_utils as utils -from .utils import cast_integer +from .utils import cast_integer, find_max_size, find_tuples_equal_to_lcm_value, get_divisors, lcm class TrafficClientException(Exception): """Generic traffic client exception.""" @@ -59,8 +59,11 @@ class TrafficRunner(object): return None LOG.info('Running traffic generator') self.client.gen.clear_stats() - # Debug use only : new '--service-mode' option available for the NFVBench command line. - # A read-only mode TRex console would be able to capture the generated traffic. + # Debug use only: the service_mode flag may have been set in + # the configuration, in order to enable the 'service' mode + # in the trex generator, before starting the traffic (run). + # From this point, a T-rex console (launched in readonly mode) would + # then be able to capture the transmitted and/or received traffic. self.client.gen.set_service_mode(enabled=self.service_mode) LOG.info('Service mode is %sabled', 'en' if self.service_mode else 'dis') self.client.gen.start_traffic() @@ -116,6 +119,8 @@ class IpBlock(object): def __init__(self, base_ip, step_ip, count_ip): """Create an IP block.""" self.base_ip_int = Device.ip_to_int(base_ip) + if step_ip == 'random': + step_ip = '0.0.0.1' self.step = Device.ip_to_int(step_ip) self.max_available = count_ip self.next_free = 0 @@ -126,25 +131,23 @@ class IpBlock(object): raise IndexError('Index out of bounds: %d (max=%d)' % (index, self.max_available)) return Device.int_to_ip(self.base_ip_int + index * self.step) - def reserve_ip_range(self, count, force_ip_reservation=False): + def get_ip_from_chain_first_ip(self, first_ip, index=0): + """Return the IP address at given index starting from chain first ip.""" + if index < 0 or index >= self.max_available: + raise IndexError('Index out of bounds: %d (max=%d)' % (index, self.max_available)) + return Device.int_to_ip(first_ip + index * self.step) + + def reserve_ip_range(self, count): """Reserve a range of count consecutive IP addresses spaced by step. - force_ip_reservation parameter allows to continue the calculation of IPs when - the 2 sides (ports) have different size and the flow is greater than - the size as well. """ - if self.next_free + count > self.max_available and force_ip_reservation is False: + if self.next_free + count > self.max_available: raise IndexError('No more IP addresses next free=%d max_available=%d requested=%d' % (self.next_free, self.max_available, count)) - if self.next_free + count > self.max_available and force_ip_reservation is True: - first_ip = self.get_ip(self.next_free) - last_ip = self.get_ip(self.next_free + self.max_available - 1) - self.next_free += self.max_available - else: - first_ip = self.get_ip(self.next_free) - last_ip = self.get_ip(self.next_free + count - 1) - self.next_free += count + first_ip = self.get_ip(self.next_free) + last_ip = self.get_ip(self.next_free + count - 1) + self.next_free += count return (first_ip, last_ip) def reset_reservation(self): @@ -154,14 +157,24 @@ class IpBlock(object): class UdpPorts(object): - def __init__(self, src_min, src_max, dst_min, dst_max, step): + def __init__(self, src_min, src_max, dst_min, dst_max, udp_src_size, udp_dst_size, step): - self.src_min = src_min - self.src_max = src_max - self.dst_min = dst_min - self.dst_max = dst_max + self.src_min = int(src_min) + self.src_max = int(src_max) + self.dst_min = int(dst_min) + self.dst_max = int(dst_max) + self.udp_src_size = udp_src_size + self.udp_dst_size = udp_dst_size self.step = step + def get_src_max(self, index=0): + """Return the UDP src port at given index.""" + return int(self.src_min) + index * int(self.step) + + def get_dst_max(self, index=0): + """Return the UDP dst port at given index.""" + return int(self.dst_min) + index * int(self.step) + class Device(object): """Represent a port device and all information associated to it. @@ -202,17 +215,17 @@ class Device(object): self.ip_src_static = generator_config.ip_src_static self.ip_addrs_step = generator_config.ip_addrs_step if self.ip_addrs_step == 'random': - # Set step to 1 to calculate the IP range size (see check_ip_size below) + # Set step to 1 to calculate the IP range size (see check_range_size below) step = '0.0.0.1' else: step = self.ip_addrs_step - self.ip_size = self.check_ipsize(IPNetwork(self.ip_addrs).size, Device.ip_to_int(step)) + self.ip_size = self.check_range_size(IPNetwork(self.ip_addrs).size, Device.ip_to_int(step)) self.ip = str(IPNetwork(self.ip_addrs).network) ip_addrs_left = generator_config.ip_addrs[0] ip_addrs_right = generator_config.ip_addrs[1] self.ip_addrs_size = { - 'left': self.check_ipsize(IPNetwork(ip_addrs_left).size, Device.ip_to_int(step)), - 'right': self.check_ipsize(IPNetwork(ip_addrs_right).size, Device.ip_to_int(step))} + 'left': self.check_range_size(IPNetwork(ip_addrs_left).size, Device.ip_to_int(step)), + 'right': self.check_range_size(IPNetwork(ip_addrs_right).size, Device.ip_to_int(step))} udp_src_port = generator_config.gen_config.udp_src_port if udp_src_port is None: udp_src_port = 53 @@ -221,33 +234,28 @@ class Device(object): udp_dst_port = 53 src_max, src_min = self.define_udp_range(udp_src_port, 'udp_src_port') dst_max, dst_min = self.define_udp_range(udp_dst_port, 'udp_dst_port') - udp_src_range = int(src_max) - int(src_min) + 1 - udp_dst_range = int(dst_max) - int(dst_min) + 1 - lcm_port = self.lcm(udp_src_range, udp_dst_range) + if generator_config.gen_config.udp_port_step == 'random': + # Set step to 1 to calculate the UDP range size + udp_step = 1 + else: + udp_step = int(generator_config.gen_config.udp_port_step) + udp_src_size = self.check_range_size(int(src_max) - int(src_min) + 1, udp_step) + udp_dst_size = self.check_range_size(int(dst_max) - int(dst_min) + 1, udp_step) + lcm_port = lcm(udp_src_size, udp_dst_size) if self.ip_src_static is True: - lcm_ip = self.lcm(1, min(self.ip_addrs_size['left'], self.ip_addrs_size['right'])) + lcm_ip = lcm(1, min(self.ip_addrs_size['left'], self.ip_addrs_size['right'])) else: - lcm_ip = self.lcm(self.ip_addrs_size['left'], self.ip_addrs_size['right']) - flow_max = self.lcm(lcm_port, lcm_ip) + lcm_ip = lcm(self.ip_addrs_size['left'], self.ip_addrs_size['right']) + flow_max = lcm(lcm_port, lcm_ip) if self.flow_count > flow_max: raise TrafficClientException('Trying to set unachievable traffic (%d > %d)' % (self.flow_count, flow_max)) - # manage udp range regarding flow count value - # UDP dst range is greater than FC => range will be limited to min + FC - if self.flow_count <= udp_dst_range: - dst_max = int(dst_min) + self.flow_count - 1 - # UDP src range is greater than FC => range will be limited to min + FC - if self.flow_count <= udp_src_range: - src_max = int(src_min) + self.flow_count - 1 - # Define IP block limit regarding flow count - if self.flow_count <= self.ip_size: - self.ip_block = IpBlock(self.ip, step, self.flow_count) - else: - self.ip_block = IpBlock(self.ip, step, self.ip_size) - - self.udp_ports = UdpPorts(src_min, src_max, dst_min, dst_max, + self.udp_ports = UdpPorts(src_min, src_max, dst_min, dst_max, udp_src_size, udp_dst_size, generator_config.gen_config.udp_port_step) + + self.ip_block = IpBlock(self.ip, step, self.ip_size) + self.gw_ip_block = IpBlock(generator_config.gateway_ips[port], generator_config.gateway_ip_addrs_step, self.chain_count) @@ -256,6 +264,120 @@ class Device(object): generator_config.tg_gateway_ip_addrs_step, self.chain_count) + def limit_ip_udp_ranges(self, peer_ip_size, cur_chain_flow_count): + # init to min value in case of no matching values found with lcm calculation + new_src_ip_size = 1 + new_peer_ip_size = 1 + new_src_udp_size = 1 + new_dst_udp_size = 1 + + if self.ip_src_static is True: + src_ip_size = 1 + else: + src_ip_size = self.ip_size + ip_src_divisors = list(get_divisors(src_ip_size)) + ip_dst_divisors = list(get_divisors(peer_ip_size)) + udp_src_divisors = list(get_divisors(self.udp_ports.udp_src_size)) + udp_dst_divisors = list(get_divisors(self.udp_ports.udp_dst_size)) + fc = int(cur_chain_flow_count) + tuples_ip = list(find_tuples_equal_to_lcm_value(ip_src_divisors, ip_dst_divisors, fc)) + tuples_udp = list(find_tuples_equal_to_lcm_value(udp_src_divisors, udp_dst_divisors, fc)) + + if tuples_ip: + new_src_ip_size = tuples_ip[-1][0] + new_peer_ip_size = tuples_ip[-1][1] + + if tuples_udp: + new_src_udp_size = tuples_udp[-1][0] + new_dst_udp_size = tuples_udp[-1][1] + + tuples_src = [] + tuples_dst = [] + if not tuples_ip and not tuples_udp: + # in case of not divisors in common matching LCM value (i.e. requested flow count) + # try to find an accurate UDP range to fit requested flow count + udp_src_int = range(self.udp_ports.src_min, self.udp_ports.src_max) + udp_dst_int = range(self.udp_ports.dst_min, self.udp_ports.dst_max) + tuples_src = list(find_tuples_equal_to_lcm_value(ip_src_divisors, udp_src_int, fc)) + tuples_dst = list(find_tuples_equal_to_lcm_value(ip_dst_divisors, udp_dst_int, fc)) + + if not tuples_src and not tuples_dst: + # iterate IP and UDP ranges to find a tuple that match flow count values + src_ip_range = range(1,src_ip_size) + dst_ip_range = range(1, peer_ip_size) + tuples_src = list(find_tuples_equal_to_lcm_value(src_ip_range, udp_src_int, fc)) + tuples_dst = list(find_tuples_equal_to_lcm_value(dst_ip_range, udp_dst_int, fc)) + + if tuples_src or tuples_dst: + if tuples_src: + new_src_ip_size = tuples_src[-1][0] + new_src_udp_size = tuples_src[-1][1] + if tuples_dst: + new_peer_ip_size = tuples_dst[-1][0] + new_dst_udp_size = tuples_dst[-1][1] + else: + if not tuples_ip: + if src_ip_size != 1: + if src_ip_size > fc: + new_src_ip_size = fc + else: + new_src_ip_size = find_max_size(src_ip_size, tuples_udp, fc) + if peer_ip_size != 1: + if peer_ip_size > fc: + new_peer_ip_size = fc + else: + new_peer_ip_size = find_max_size(peer_ip_size, tuples_udp, fc) + + if not tuples_udp: + if self.udp_ports.udp_src_size != 1: + if self.udp_ports.udp_src_size > fc: + new_src_udp_size = fc + else: + new_src_udp_size = find_max_size(self.udp_ports.udp_src_size, + tuples_ip, fc) + if self.udp_ports.udp_dst_size != 1: + if self.udp_ports.udp_dst_size > fc: + new_dst_udp_size = fc + else: + new_dst_udp_size = find_max_size(self.udp_ports.udp_dst_size, + tuples_ip, fc) + max_possible_flows = lcm(lcm(new_src_ip_size, new_peer_ip_size), + lcm(new_src_udp_size, new_dst_udp_size)) + + LOG.debug("IP dst size: %d", new_peer_ip_size) + LOG.debug("LCM IP: %d", lcm(new_src_ip_size, new_peer_ip_size)) + LOG.debug("LCM UDP: %d", lcm(new_src_udp_size, new_dst_udp_size)) + LOG.debug("Global LCM: %d", max_possible_flows) + LOG.debug("IP src size: %d, IP dst size: %d, UDP src size: %d, UDP dst size: %d", + new_src_ip_size, new_peer_ip_size, self.udp_ports.udp_src_size, + self.udp_ports.udp_dst_size) + if not max_possible_flows == cur_chain_flow_count: + if (self.ip_addrs_step != '0.0.0.1' or self.udp_ports.step != '1') and not ( + self.ip_addrs_step == 'random' and self.udp_ports.step == 'random'): + LOG.warning("Current values of ip_addrs_step and/or udp_port_step properties " + "do not allow to control an accurate flow count. " + "Values will be overridden as follows:") + if self.ip_addrs_step != '0.0.0.1': + LOG.info("ip_addrs_step='0.0.0.1' (previous value: ip_addrs_step='%s')", + self.ip_addrs_step) + self.ip_addrs_step = '0.0.0.1' + + if self.udp_ports.step != '1': + LOG.info("udp_port_step='1' (previous value: udp_port_step='%s')", + self.udp_ports.step) + self.udp_ports.step = '1' + # override config for not logging random step warning message in trex_gen.py + self.generator_config.gen_config.udp_port_step = self.udp_ports.step + else: + LOG.error("Current values of ip_addrs_step and udp_port_step properties " + "do not allow to control an accurate flow count.") + else: + src_ip_size = new_src_ip_size + peer_ip_size = new_peer_ip_size + self.udp_ports.udp_src_size = new_src_udp_size + self.udp_ports.udp_dst_size = new_dst_udp_size + return src_ip_size, peer_ip_size + @staticmethod def define_udp_range(udp_port, property_name): if isinstance(udp_port, int): @@ -269,23 +391,15 @@ class Device(object): % property_name) return max, min - @staticmethod - def lcm(a, b): - """Calculate the maximum possible value for both IP and ports, - eventually for maximum possible flux.""" - if a != 0 and b != 0: - lcm_value = a * b // gcd(a, b) - return lcm_value - raise TypeError(" IP size or port range can't be zero !") @staticmethod - def check_ipsize(ip_size, step): - """Check and set the available IPs, considering the step.""" + def check_range_size(range_size, step): + """Check and set the available IPs or UDP ports, considering the step.""" try: - if ip_size % step == 0: - value = int(ip_size / step) + if range_size % step == 0: + value = range_size // step else: - value = int((ip_size / step)) + 1 + value = range_size // step + 1 return value except ZeroDivisionError: raise ZeroDivisionError("step can't be zero !") from ZeroDivisionError @@ -389,27 +503,42 @@ class Device(object): # example 11 flows and 3 chains => 3, 4, 4 flows_per_chain = int((self.flow_count + self.chain_count - 1) / self.chain_count) cur_chain_flow_count = int(self.flow_count - flows_per_chain * (self.chain_count - 1)) - force_ip_reservation = False - # use case example of this parameter: - # - static IP addresses (source & destination), netmask = /30 - # - 4 varying UDP source ports | 1 UDP destination port - # - Flow count = 8 - # --> parameter 'reserve_ip_range' should have flag 'force_ip_reservation' - # in order to assign the maximum available IP on each iteration - if self.ip_size < cur_chain_flow_count \ - or self.ip_addrs_size['left'] != self.ip_addrs_size['right']: - force_ip_reservation = True peer = self.get_peer_device() self.ip_block.reset_reservation() peer.ip_block.reset_reservation() dest_macs = self.get_dest_macs() + # limit ranges of UDP ports and IP to avoid overflow of the number of flows + peer_size = peer.ip_size // self.chain_count + for chain_idx in range(self.chain_count): - src_ip_first, src_ip_last = self.ip_block.reserve_ip_range\ - (cur_chain_flow_count, force_ip_reservation) - dst_ip_first, dst_ip_last = peer.ip_block.reserve_ip_range\ - (cur_chain_flow_count, force_ip_reservation) + src_ip_size, peer_ip_size = self.limit_ip_udp_ranges(peer_size, cur_chain_flow_count) + + src_ip_first, src_ip_last = self.ip_block.reserve_ip_range \ + (src_ip_size) + dst_ip_first, dst_ip_last = peer.ip_block.reserve_ip_range \ + (peer_ip_size) + + if self.ip_addrs_step != 'random': + src_ip_last = self.ip_block.get_ip_from_chain_first_ip( + Device.ip_to_int(src_ip_first), src_ip_size - 1) + dst_ip_last = peer.ip_block.get_ip_from_chain_first_ip( + Device.ip_to_int(dst_ip_first), peer_ip_size - 1) + if self.udp_ports.step != 'random': + self.udp_ports.src_max = self.udp_ports.get_src_max(self.udp_ports.udp_src_size - 1) + self.udp_ports.dst_max = self.udp_ports.get_dst_max(self.udp_ports.udp_dst_size - 1) + if self.ip_src_static: + src_ip_last = src_ip_first + + LOG.info("Port %d, chain %d: IP src range [%s,%s]", self.port, chain_idx, + src_ip_first, src_ip_last) + LOG.info("Port %d, chain %d: IP dst range [%s,%s]", self.port, chain_idx, + dst_ip_first, dst_ip_last) + LOG.info("Port %d, chain %d: UDP src range [%s,%s]", self.port, chain_idx, + self.udp_ports.src_min, self.udp_ports.src_max) + LOG.info("Port %d, chain %d: UDP dst range [%s,%s]", self.port, chain_idx, + self.udp_ports.dst_min, self.udp_ports.dst_max) configs.append({ 'count': cur_chain_flow_count, @@ -417,18 +546,18 @@ class Device(object): 'mac_dst': dest_macs[chain_idx], 'ip_src_addr': src_ip_first, 'ip_src_addr_max': src_ip_last, - 'ip_src_count': cur_chain_flow_count, + 'ip_src_count': src_ip_size, 'ip_dst_addr': dst_ip_first, 'ip_dst_addr_max': dst_ip_last, - 'ip_dst_count': cur_chain_flow_count, + 'ip_dst_count': peer_ip_size, 'ip_addrs_step': self.ip_addrs_step, 'ip_src_static': self.ip_src_static, 'udp_src_port': self.udp_ports.src_min, 'udp_src_port_max': self.udp_ports.src_max, - 'udp_src_count': int(self.udp_ports.src_max) - int(self.udp_ports.src_min) + 1, + 'udp_src_count': self.udp_ports.udp_src_size, 'udp_dst_port': self.udp_ports.dst_min, 'udp_dst_port_max': self.udp_ports.dst_max, - 'udp_dst_count': int(self.udp_ports.dst_max) - int(self.udp_ports.dst_min) + 1, + 'udp_dst_count': self.udp_ports.udp_dst_size, 'udp_port_step': self.udp_ports.step, 'mac_discovery_gw': self.get_gw_ip(chain_idx), 'ip_src_tg_gw': self.tg_gw_ip_block.get_ip(chain_idx), @@ -486,15 +615,24 @@ class GeneratorConfig(object): self.cores = config.cores else: self.cores = gen_config.get('cores', 1) + # let's report the value actually used in the end + config.cores_used = self.cores self.mbuf_factor = config.mbuf_factor self.mbuf_64 = config.mbuf_64 self.hdrh = not config.disable_hdrh - if gen_config.intf_speed: - # interface speed is overriden from config - self.intf_speed = bitmath.parse_string(gen_config.intf_speed.replace('ps', '')).bits + if config.intf_speed: + # interface speed is overriden from the command line + self.intf_speed = config.intf_speed + elif gen_config.intf_speed: + # interface speed is overriden from the generator config + self.intf_speed = gen_config.intf_speed else: + self.intf_speed = "auto" + if self.intf_speed in ("auto", "0"): # interface speed is discovered/provided by the traffic generator self.intf_speed = 0 + else: + self.intf_speed = bitmath.parse_string(self.intf_speed.replace('ps', '')).bits self.name = gen_config.name self.zmq_pub_port = gen_config.get('zmq_pub_port', 4500) self.zmq_rpc_port = gen_config.get('zmq_rpc_port', 4501) @@ -713,13 +851,17 @@ class TrafficClient(object): # interface speed is overriden from config if self.intf_speed != tg_if_speed: # Warn the user if the speed in the config is different - LOG.warning('Interface speed provided is different from actual speed (%d Gbps)', - intf_speeds[0]) + LOG.warning( + 'Interface speed provided (%g Gbps) is different from actual speed (%d Gbps)', + self.intf_speed / 1000000000.0, intf_speeds[0]) else: # interface speed not provisioned by config self.intf_speed = tg_if_speed # also update the speed in the tg config self.generator_config.intf_speed = tg_if_speed + # let's report detected and actually used interface speed + self.config.intf_speed_detected = tg_if_speed + self.config.intf_speed_used = self.intf_speed # Save the traffic generator local MAC for mac, device in zip(self.gen.get_macs(), self.generator_config.devices): @@ -923,7 +1065,12 @@ class TrafficClient(object): """Collect final stats for previous run.""" stats = self.gen.get_stats(self.ifstats) retDict = {'total_tx_rate': stats['total_tx_rate'], - 'offered_tx_rate_bps': stats['offered_tx_rate_bps']} + 'offered_tx_rate_bps': stats['offered_tx_rate_bps'], + 'theoretical_tx_rate_bps': stats['theoretical_tx_rate_bps'], + 'theoretical_tx_rate_pps': stats['theoretical_tx_rate_pps']} + + if self.config.periodic_gratuitous_arp: + retDict['garp_total_tx_rate'] = stats['garp_total_tx_rate'] tx_keys = ['total_pkts', 'total_pkt_bytes', 'pkt_rate', 'pkt_bit_rate'] rx_keys = tx_keys + ['dropped_pkts'] @@ -969,21 +1116,25 @@ class TrafficClient(object): for key in ['pkt_bit_rate', 'pkt_rate']: for dirc in ['tx', 'rx']: retDict['overall'][dirc][key] /= 2.0 - retDict['overall']['hdrh'] = stats.get('hdrh', None) - if retDict['overall']['hdrh']: - decoded_histogram = HdrHistogram.decode(retDict['overall']['hdrh']) - # override min max and avg from hdrh - retDict['overall']['rx']['min_delay_usec'] = decoded_histogram.get_min_value() - retDict['overall']['rx']['max_delay_usec'] = decoded_histogram.get_max_value() - retDict['overall']['rx']['avg_delay_usec'] = decoded_histogram.get_mean_value() - retDict['overall']['rx']['lat_percentile'] = {} - for percentile in self.config.lat_percentiles: - retDict['overall']['rx']['lat_percentile'][percentile] = \ - decoded_histogram.get_value_at_percentile(percentile) - else: retDict['overall'] = retDict[ports[0]] retDict['overall']['drop_rate_percent'] = self.__get_dropped_rate(retDict['overall']) + + if 'overall_hdrh' in stats: + retDict['overall']['hdrh'] = stats.get('overall_hdrh', None) + decoded_histogram = HdrHistogram.decode(retDict['overall']['hdrh']) + retDict['overall']['rx']['lat_percentile'] = {} + # override min max and avg from hdrh (only if histogram is valid) + if decoded_histogram.get_total_count() != 0: + retDict['overall']['rx']['min_delay_usec'] = decoded_histogram.get_min_value() + retDict['overall']['rx']['max_delay_usec'] = decoded_histogram.get_max_value() + retDict['overall']['rx']['avg_delay_usec'] = decoded_histogram.get_mean_value() + for percentile in self.config.lat_percentiles: + retDict['overall']['rx']['lat_percentile'][percentile] = \ + decoded_histogram.get_value_at_percentile(percentile) + else: + for percentile in self.config.lat_percentiles: + retDict['overall']['rx']['lat_percentile'][percentile] = 'n/a' return retDict def __convert_rates(self, rate): @@ -1012,19 +1163,21 @@ class TrafficClient(object): } if key == 'overall': - stats[key]['hdrh'] = interface.get('hdrh', None) - if stats[key]['hdrh']: + if 'hdrh' in interface: + stats[key]['hdrh'] = interface.get('hdrh', None) decoded_histogram = HdrHistogram.decode(stats[key]['hdrh']) - # override min max and avg from hdrh - stats[key]['min_delay_usec'] = decoded_histogram.get_min_value() - stats[key]['max_delay_usec'] = decoded_histogram.get_max_value() - stats[key]['avg_delay_usec'] = decoded_histogram.get_mean_value() stats[key]['lat_percentile'] = {} - for percentile in self.config.lat_percentiles: - stats[key]['lat_percentile'][percentile] = decoded_histogram.\ - get_value_at_percentile(percentile) - - + # override min max and avg from hdrh (only if histogram is valid) + if decoded_histogram.get_total_count() != 0: + stats[key]['min_delay_usec'] = decoded_histogram.get_min_value() + stats[key]['max_delay_usec'] = decoded_histogram.get_max_value() + stats[key]['avg_delay_usec'] = decoded_histogram.get_mean_value() + for percentile in self.config.lat_percentiles: + stats[key]['lat_percentile'][percentile] = decoded_histogram.\ + get_value_at_percentile(percentile) + else: + for percentile in self.config.lat_percentiles: + stats[key]['lat_percentile'][percentile] = 'n/a' return stats def __targets_found(self, rate, targets, results): @@ -1110,6 +1263,17 @@ class TrafficClient(object): """ self._modify_load(rate) + # There used to be a inconsistency in case of interface speed override. + # The emulated 'intf_speed' value is unknown to the T-Rex generator which + # refers to the detected line rate for converting relative traffic loads. + # Therefore, we need to convert actual rates here, in terms of packets/s. + + for idx, str_rate in enumerate(self.gen.rates): + if str_rate.endswith('%'): + float_rate = float(str_rate.replace('%', '').strip()) + pps_rate = self.__convert_rates({'rate_percent': float_rate})['rate_pps'] + self.gen.rates[idx] = str(pps_rate) + 'pps' + # poll interval stats and collect them for stats in self.run_traffic(): self.interval_collector.add(stats) @@ -1142,6 +1306,9 @@ class TrafficClient(object): delta_tx = cur_tx - self.prev_tx delta_rx = cur_rx - self.prev_rx drops = delta_tx - delta_rx + if delta_tx == 0: + LOG.info("\x1b[1mConfiguration issue!\x1b[0m (no transmission)") + sys.exit(0) drop_rate_pct = 100 * (delta_tx - delta_rx)/delta_tx self.prev_tx = cur_tx self.prev_rx = cur_rx @@ -1203,12 +1370,25 @@ class TrafficClient(object): for idx, key in enumerate(["direction-forward", "direction-reverse"]): tx_rate = results["stats"][str(idx)]["tx"]["total_pkts"] / self.config.duration_sec rx_rate = results["stats"][str(1 - idx)]["rx"]["total_pkts"] / self.config.duration_sec + + orig_rate = self.run_config['rates'][idx] + if self.config.periodic_gratuitous_arp: + orig_rate['rate_pps'] = float( + orig_rate['rate_pps']) - self.config.gratuitous_arp_pps + r[key] = { - "orig": self.__convert_rates(self.run_config['rates'][idx]), + "orig": self.__convert_rates(orig_rate), "tx": self.__convert_rates({'rate_pps': tx_rate}), "rx": self.__convert_rates({'rate_pps': rx_rate}) } + if self.config.periodic_gratuitous_arp: + r['garp-direction-total'] = { + "orig": self.__convert_rates({'rate_pps': self.config.gratuitous_arp_pps * 2}), + "tx": self.__convert_rates({'rate_pps': results["stats"]["garp_total_tx_rate"]}), + "rx": self.__convert_rates({'rate_pps': 0}) + } + total = {} for direction in ['orig', 'tx', 'rx']: total[direction] = {} @@ -1216,6 +1396,7 @@ class TrafficClient(object): total[direction][unit] = sum([float(x[direction][unit]) for x in list(r.values())]) r['direction-total'] = total + return r def insert_interface_stats(self, pps_list):