diff options
Diffstat (limited to 'yardstick/network_services/traffic_profile')
7 files changed, 253 insertions, 139 deletions
diff --git a/yardstick/network_services/traffic_profile/__init__.py b/yardstick/network_services/traffic_profile/__init__.py index a1b26a24d..91d8a665f 100644 --- a/yardstick/network_services/traffic_profile/__init__.py +++ b/yardstick/network_services/traffic_profile/__init__.py @@ -28,6 +28,7 @@ def register_modules(): 'yardstick.network_services.traffic_profile.prox_ramp', 'yardstick.network_services.traffic_profile.rfc2544', 'yardstick.network_services.traffic_profile.pktgen', + 'yardstick.network_services.traffic_profile.landslide_profile', ] for module in modules: diff --git a/yardstick/network_services/traffic_profile/base.py b/yardstick/network_services/traffic_profile/base.py index a8f950b7b..ea3f17874 100644 --- a/yardstick/network_services/traffic_profile/base.py +++ b/yardstick/network_services/traffic_profile/base.py @@ -44,6 +44,7 @@ class TrafficProfileConfig(object): self.lower_bound = tprofile.get('lower_bound') self.upper_bound = tprofile.get('upper_bound') self.step_interval = tprofile.get('step_interval') + self.enable_latency = tprofile.get('enable_latency', False) def _parse_rate(self, rate): """Parse traffic profile rate @@ -96,6 +97,9 @@ class TrafficProfile(object): self.params = tp_config self.config = TrafficProfileConfig(tp_config) + def is_ended(self): + return False + def execute_traffic(self, traffic_generator, **kawrgs): """ This methods defines the behavior of the traffic generator. It will be called in a loop until the traffic generator exits. diff --git a/yardstick/network_services/traffic_profile/ixia_rfc2544.py b/yardstick/network_services/traffic_profile/ixia_rfc2544.py index 26dc1fe04..b8aa78d80 100644 --- a/yardstick/network_services/traffic_profile/ixia_rfc2544.py +++ b/yardstick/network_services/traffic_profile/ixia_rfc2544.py @@ -56,67 +56,83 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): if not traffickey.startswith((self.UPLINK, self.DOWNLINK)): continue + # values should be single-item dict, so just grab the first item try: - # values should be single-item dict, so just grab the first item - try: - key, value = next(iter(values.items())) - except StopIteration: - result[traffickey] = {} - continue - - port_id = value.get('id', 1) - port_index = port_id - 1 - - if value.get('outer_l3v4'): - ip = value['outer_l3v4'] - src_key, dst_key = 'srcip4', 'dstip4' - else: - ip = value['outer_l3v6'] - src_key, dst_key = 'srcip6', 'dstip6' - - srcip, srcmask = self._get_ip_and_mask(ip[src_key]) - dstip, dstmask = self._get_ip_and_mask(ip[dst_key]) - - outer_l4 = value.get('outer_l4') - src_port, src_port_mask = self._get_fixed_and_mask(outer_l4['srcport']) - dst_port, dst_port_mask = self._get_fixed_and_mask(outer_l4['dstport']) - result[traffickey] = { - 'bidir': False, - 'id': port_id, - 'rate': self.rate, - 'rate_unit': self.rate_unit, - 'outer_l2': { - 'framesize': value['outer_l2']['framesize'], - 'framesPerSecond': True, - 'QinQ': value['outer_l2'].get('QinQ'), - 'srcmac': mac['src_mac_{}'.format(port_index)], - 'dstmac': mac['dst_mac_{}'.format(port_index)], - }, - 'outer_l3': { - 'count': ip['count'], - 'dscp': ip['dscp'], - 'ttl': ip['ttl'], - 'seed': ip['seed'], - 'srcip': srcip, - 'dstip': dstip, - 'srcmask': srcmask, - 'dstmask': dstmask, - 'type': key, - 'proto': ip['proto'], - }, - 'outer_l4': { - 'srcport': src_port, - 'dstport': dst_port, - 'srcportmask': src_port_mask, - 'dstportmask': dst_port_mask, - 'count': outer_l4['count'], - 'seed': outer_l4['seed'], - } - - } - except KeyError: + key, value = next(iter(values.items())) + except StopIteration: + result[traffickey] = {} continue + port_id = value.get('id', 1) + port_index = port_id - 1 + + result[traffickey] = { + 'bidir': False, + 'id': port_id, + 'rate': self.rate, + 'rate_unit': self.rate_unit, + 'outer_l2': {}, + 'outer_l3': {}, + 'outer_l4': {}, + } + + outer_l2 = value.get('outer_l2') + if outer_l2: + result[traffickey]['outer_l2'].update({ + 'framesize': outer_l2.get('framesize'), + 'framesPerSecond': True, + 'QinQ': outer_l2.get('QinQ'), + 'srcmac': mac.get('src_mac_{}'.format(port_index)), + 'dstmac': mac.get('dst_mac_{}'.format(port_index)), + }) + + if value.get('outer_l3v4'): + outer_l3 = value['outer_l3v4'] + src_key, dst_key = 'srcip4', 'dstip4' + else: + outer_l3 = value.get('outer_l3v6') + src_key, dst_key = 'srcip6', 'dstip6' + if outer_l3: + srcip = srcmask = dstip = dstmask = None + if outer_l3.get(src_key): + srcip, srcmask = self._get_ip_and_mask(outer_l3[src_key]) + if outer_l3.get(dst_key): + dstip, dstmask = self._get_ip_and_mask(outer_l3[dst_key]) + + result[traffickey]['outer_l3'].update({ + 'count': outer_l3.get('count', 1), + 'dscp': outer_l3.get('dscp'), + 'ttl': outer_l3.get('ttl'), + 'srcseed': outer_l3.get('srcseed', 1), + 'dstseed': outer_l3.get('dstseed', 1), + 'srcip': srcip, + 'dstip': dstip, + 'srcmask': srcmask, + 'dstmask': dstmask, + 'type': key, + 'proto': outer_l3.get('proto'), + }) + + outer_l4 = value.get('outer_l4') + if outer_l4: + src_port = src_port_mask = dst_port = dst_port_mask = None + if outer_l4.get('srcport'): + src_port, src_port_mask = ( + self._get_fixed_and_mask(outer_l4['srcport'])) + + if outer_l4.get('dstport'): + dst_port, dst_port_mask = ( + self._get_fixed_and_mask(outer_l4['dstport'])) + + result[traffickey]['outer_l4'].update({ + 'srcport': src_port, + 'dstport': dst_port, + 'srcportmask': src_port_mask, + 'dstportmask': dst_port_mask, + 'count': outer_l4.get('count', 1), + 'seed': outer_l4.get('seed', 1), + }) + return result def _ixia_traffic_generate(self, traffic, ixia_obj): @@ -168,12 +184,8 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): [samples[iface]['in_packets'] for iface in samples]) out_packets_sum = sum( [samples[iface]['out_packets'] for iface in samples]) - rx_throughput = sum( - [samples[iface]['RxThroughput'] for iface in samples]) - rx_throughput = round(float(rx_throughput), 2) - tx_throughput = sum( - [samples[iface]['TxThroughput'] for iface in samples]) - tx_throughput = round(float(tx_throughput), 2) + rx_throughput = round(float(in_packets_sum) / duration, 3) + tx_throughput = round(float(out_packets_sum) / duration, 3) packet_drop = abs(out_packets_sum - in_packets_sum) try: @@ -183,10 +195,6 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): except ZeroDivisionError: LOG.info('No traffic is flowing') - samples['TxThroughput'] = tx_throughput - samples['RxThroughput'] = rx_throughput - samples['DropPercentage'] = drop_percent - if first_run: completed = True if drop_percent <= tolerance else False if (first_run and @@ -200,4 +208,21 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): else: completed = True + latency_ns_avg = float( + sum([samples[iface]['Store-Forward_Avg_latency_ns'] + for iface in samples])) / num_ifaces + latency_ns_min = float( + sum([samples[iface]['Store-Forward_Min_latency_ns'] + for iface in samples])) / num_ifaces + latency_ns_max = float( + sum([samples[iface]['Store-Forward_Max_latency_ns'] + for iface in samples])) / num_ifaces + + samples['TxThroughput'] = tx_throughput + samples['RxThroughput'] = rx_throughput + samples['DropPercentage'] = drop_percent + samples['latency_ns_avg'] = latency_ns_avg + samples['latency_ns_min'] = latency_ns_min + samples['latency_ns_max'] = latency_ns_max + return completed, samples diff --git a/yardstick/network_services/traffic_profile/landslide_profile.py b/yardstick/network_services/traffic_profile/landslide_profile.py new file mode 100644 index 000000000..f79226fb4 --- /dev/null +++ b/yardstick/network_services/traffic_profile/landslide_profile.py @@ -0,0 +1,47 @@ +# Copyright (c) 2018 Intel Corporation +# +# 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. +""" Spirent Landslide traffic profile definitions """ + +from yardstick.network_services.traffic_profile import base + + +class LandslideProfile(base.TrafficProfile): + """ + This traffic profile handles attributes of Landslide data stream + """ + + def __init__(self, tp_config): + super(LandslideProfile, self).__init__(tp_config) + + # for backward compatibility support dict and list of dicts + if isinstance(tp_config["dmf_config"], dict): + self.dmf_config = [tp_config["dmf_config"]] + else: + self.dmf_config = tp_config["dmf_config"] + + def execute(self, traffic_generator): + pass + + def update_dmf(self, options): + if 'dmf' in options: + if isinstance(options['dmf'], dict): + _dmfs = [options['dmf']] + else: + _dmfs = options['dmf'] + + for index, _dmf in enumerate(_dmfs): + try: + self.dmf_config[index].update(_dmf) + except IndexError: + pass diff --git a/yardstick/network_services/traffic_profile/prox_binsearch.py b/yardstick/network_services/traffic_profile/prox_binsearch.py index 506a880e0..f924cf419 100644 --- a/yardstick/network_services/traffic_profile/prox_binsearch.py +++ b/yardstick/network_services/traffic_profile/prox_binsearch.py @@ -25,6 +25,14 @@ from yardstick.common import constants as overall_constants LOG = logging.getLogger(__name__) +STATUS_SUCCESS = "Success" +STATUS_FAIL = "Failure" +STATUS_RESULT = "Result" +STEP_CONFIRM = "Confirm retry" +STEP_INCREASE_LOWER = "Increase lower" +STEP_DECREASE_LOWER = "Decrease lower" +STEP_DECREASE_UPPER = "Decrease upper" + class ProxBinSearchProfile(ProxProfile): """ @@ -58,6 +66,9 @@ class ProxBinSearchProfile(ProxProfile): yield test_value test_value = self.mid_point + def is_ended(self): + return self.done.is_set() + def run_test_with_pkt_size(self, traffic_gen, pkt_size, duration): """Run the test for a single packet size. @@ -85,18 +96,16 @@ class ProxBinSearchProfile(ProxProfile): # success, the binary search will complete on an integer multiple # of the precision, rather than on a fraction of it. - theor_max_thruput = actual_max_thruput = 0 + theor_max_thruput = 0.0 result_samples = {} - # Store one time only value in influxdb - single_samples = { + test_data = { "test_duration": traffic_gen.scenario_helper.scenario_cfg["runner"]["duration"], "test_precision": self.params["traffic_profile"]["test_precision"], "tolerated_loss": self.params["traffic_profile"]["tolerated_loss"], "duration": duration } - self.queue.put(single_samples) self.prev_time = time.time() # throughput and packet loss from the most recent successful test @@ -110,85 +119,88 @@ class ProxBinSearchProfile(ProxProfile): neg_retry = 0 total_retry = 0 - LOG.info("Checking MAX %s MIN %s TEST %s", - self.current_upper, self.lower_bound, test_value) + LOG.info("Checking MAX %s MIN %s TEST %s", self.current_upper, + self.lower_bound, test_value) + while (pos_retry <= ok_retry) and (neg_retry <= ok_retry): total_retry = total_retry + 1 + result, port_samples = self._profile_helper.run_test(pkt_size, duration, test_value, self.tolerated_loss, line_speed) - if (total_retry > (ok_retry * 3)) and (ok_retry is not 0): - LOG.info("Failure.!! .. RETRY EXCEEDED ... decrease lower bound") + if (total_retry > (ok_retry * 3)) and (ok_retry is not 0): + status = STATUS_FAIL + next_step = STEP_DECREASE_LOWER successful_pkt_loss = result.pkt_loss - samples = result.get_samples(pkt_size, successful_pkt_loss, port_samples) - self.current_upper = test_value neg_retry = total_retry elif result.success: if (pos_retry < ok_retry) and (ok_retry is not 0): - neg_retry = 0 - LOG.info("Success! ... confirm retry") - + status = STATUS_SUCCESS + next_step = STEP_CONFIRM successful_pkt_loss = result.pkt_loss - samples = result.get_samples(pkt_size, successful_pkt_loss, port_samples) - + neg_retry = 0 else: - LOG.info("Success! Increasing lower bound") + status = STATUS_SUCCESS + next_step = STEP_INCREASE_LOWER self.current_lower = test_value - successful_pkt_loss = result.pkt_loss - samples = result.get_samples(pkt_size, successful_pkt_loss, port_samples) - - # store results with success tag in influxdb - success_samples = \ - {'Success_' + key: value for key, value in samples.items()} - - success_samples["Success_rx_total"] = int(result.rx_total) - success_samples["Success_tx_total"] = int(result.tx_total) - success_samples["Success_can_be_lost"] = int(result.can_be_lost) - success_samples["Success_drop_total"] = int(result.drop_total) - success_samples["Success_RxThroughput"] = samples["RxThroughput"] - success_samples["Success_RxThroughput_gbps"] = \ - (samples["RxThroughput"] / 1000) * ((pkt_size + 20)* 8) - LOG.info(">>>##>>Collect SUCCESS TG KPIs %s %s", - datetime.datetime.now(), success_samples) - self.queue.put(success_samples, True, overall_constants.QUEUE_PUT_TIMEOUT) - - # Store Actual throughput for result samples - actual_max_thruput = success_samples["Success_RxThroughput"] pos_retry = pos_retry + 1 else: if (neg_retry < ok_retry) and (ok_retry is not 0): - + status = STATUS_FAIL + next_step = STEP_CONFIRM pos_retry = 0 - LOG.info("failure! ... confirm retry") else: - LOG.info("Failure... Decreasing upper bound") + status = STATUS_FAIL + next_step = STEP_DECREASE_UPPER self.current_upper = test_value neg_retry = neg_retry + 1 - samples = result.get_samples(pkt_size, successful_pkt_loss, port_samples) + + LOG.info( + "Status = '%s' Next_Step = '%s'", status, next_step) + + samples = result.get_samples(pkt_size, successful_pkt_loss, port_samples) if theor_max_thruput < samples["TxThroughput"]: theor_max_thruput = samples['TxThroughput'] - self.queue.put({'theor_max_throughput': theor_max_thruput}) - - LOG.info(">>>##>>Collect TG KPIs %s %s", datetime.datetime.now(), samples) + samples['theor_max_throughput'] = theor_max_thruput + + samples["rx_total"] = int(result.rx_total) + samples["tx_total"] = int(result.tx_total) + samples["can_be_lost"] = int(result.can_be_lost) + samples["drop_total"] = int(result.drop_total) + samples["RxThroughput_gbps"] = \ + (samples["RxThroughput"] / 1000) * ((pkt_size + 20) * 8) + samples['Status'] = status + samples['Next_Step'] = next_step samples["MAX_Rate"] = self.current_upper samples["MIN_Rate"] = self.current_lower samples["Test_Rate"] = test_value samples["Step_Id"] = step_id samples["Confirmation_Retry"] = total_retry + + samples.update(test_data) + + if status == STATUS_SUCCESS and next_step == STEP_INCREASE_LOWER: + # Store success samples for result samples + result_samples = samples + + LOG.info(">>>##>>Collect TG KPIs %s %s", datetime.datetime.now(), samples) + self.queue.put(samples, True, overall_constants.QUEUE_PUT_TIMEOUT) - LOG.info(">>>##>> Result Reached PktSize %s Theor_Max_Thruput %s Actual_throughput %s", - pkt_size, theor_max_thruput, actual_max_thruput) - result_samples["Result_pktSize"] = pkt_size - result_samples["Result_theor_max_throughput"] = theor_max_thruput - result_samples["Result_Actual_throughput"] = actual_max_thruput + LOG.info( + ">>>##>> Result Reached PktSize %s Theor_Max_Thruput %s Actual_throughput %s", + pkt_size, theor_max_thruput, result_samples.get("RxThroughput", 0.0)) + result_samples["Status"] = STATUS_RESULT + result_samples["Next_Step"] = "" + result_samples["Actual_throughput"] = result_samples.get("RxThroughput", 0.0) + result_samples["theor_max_throughput"] = theor_max_thruput self.queue.put(result_samples) diff --git a/yardstick/network_services/traffic_profile/prox_profile.py b/yardstick/network_services/traffic_profile/prox_profile.py index 343ef1da2..de4b3f9a0 100644 --- a/yardstick/network_services/traffic_profile/prox_profile.py +++ b/yardstick/network_services/traffic_profile/prox_profile.py @@ -16,6 +16,7 @@ from __future__ import absolute_import import logging +import multiprocessing from yardstick.network_services.traffic_profile.base import TrafficProfile from yardstick.network_services.vnf_generic.vnf.prox_helpers import ProxProfileHelper @@ -56,7 +57,7 @@ class ProxProfile(TrafficProfile): def __init__(self, tp_config): super(ProxProfile, self).__init__(tp_config) self.queue = None - self.done = False + self.done = multiprocessing.Event() self.results = [] # TODO: get init values from tp_config @@ -116,7 +117,7 @@ class ProxProfile(TrafficProfile): try: pkt_size = next(self.pkt_size_iterator) except StopIteration: - self.done = True + self.done.set() return # Adjust packet size upwards if it's less than the minimum diff --git a/yardstick/network_services/traffic_profile/rfc2544.py b/yardstick/network_services/traffic_profile/rfc2544.py index b54fc575f..e33c437c9 100644 --- a/yardstick/network_services/traffic_profile/rfc2544.py +++ b/yardstick/network_services/traffic_profile/rfc2544.py @@ -19,6 +19,7 @@ from trex_stl_lib import trex_stl_client from trex_stl_lib import trex_stl_packet_builder_scapy from trex_stl_lib import trex_stl_streams +from yardstick.common import constants from yardstick.network_services.traffic_profile import trex_traffic_profile @@ -118,7 +119,8 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): ports.append(port_num) port_pg_id.add_port(port_num) profile = self._create_profile(profile_data, - self.rate, port_pg_id) + self.rate, port_pg_id, + self.config.enable_latency) self.generator.client.add_streams(profile, ports=[port_num]) self.generator.client.start(ports=ports, @@ -126,7 +128,7 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): force=True) return ports, port_pg_id - def _create_profile(self, profile_data, rate, port_pg_id): + def _create_profile(self, profile_data, rate, port_pg_id, enable_latency): """Create a STL profile (list of streams) for a port""" streams = [] for packet_name in profile_data: @@ -134,11 +136,13 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): get('outer_l2', {}).get('framesize')) imix_data = self._create_imix_data(imix) self._create_vm(profile_data[packet_name]) - _streams = self._create_streams(imix_data, rate, port_pg_id) + _streams = self._create_streams(imix_data, rate, port_pg_id, + enable_latency) streams.extend(_streams) return trex_stl_streams.STLProfile(streams) - def _create_imix_data(self, imix): + def _create_imix_data(self, imix, + weight_mode=constants.DISTRIBUTION_IN_PACKETS): """Generate the IMIX distribution for a STL profile The input information is the framesize dictionary in a test case @@ -157,6 +161,20 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): E.g.: imix_count = {64: 25, 128: 75} + The weight mode is described in [1]. There are two ways to describe the + weight of the packets: + - Distribution in packets: the weight defines the percentage of + packets sent per packet size. IXIA uses this definition. + - Distribution in bytes: the weight defines the percentage of bytes + sent per packet size. + + Packet size # packets D. in packets Bytes D. in bytes + 40 7 58.33% 280 7% + 576 4 33.33% 2304 56% + 1500 1 8.33% 1500 37% + + [1] https://en.wikipedia.org/wiki/Internet_Mix + :param imix: (dict) IMIX size and weight """ imix_count = {} @@ -171,8 +189,16 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): imix_sum = 100 weight_normalize = float(imix_sum) / 100 - return {size: float(weight) / weight_normalize - for size, weight in imix_count.items()} + imix_dip = {size: float(weight) / weight_normalize + for size, weight in imix_count.items()} + + if weight_mode == constants.DISTRIBUTION_IN_BYTES: + return imix_dip + + byte_total = sum([int(size) * weight + for size, weight in imix_dip.items()]) + return {size: (int(size) * weight * 100) / byte_total + for size, weight in imix_dip.items()} def _create_vm(self, packet_definition): """Create the STL Raw instructions""" @@ -213,7 +239,7 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): return trex_stl_packet_builder_scapy.STLPktBuilder( pkt=base_pkt / pad, vm=self.trex_vm) - def _create_streams(self, imix_data, rate, port_pg_id): + def _create_streams(self, imix_data, rate, port_pg_id, enable_latency): """Create a list of streams per packet size The STL TX mode speed of the generated streams will depend on the frame @@ -237,7 +263,8 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): in imix_data.items() if float(weight) > 0): packet = self._create_single_packet(size) pg_id = port_pg_id.increase_pg_id() - stl_flow = trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) + stl_flow = (trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) if + enable_latency else None) mode = trex_stl_streams.STLTXCont(percentage=weight * rate / 100) streams.append(trex_stl_client.STLStream( packet=packet, flow_stats=stl_flow, mode=mode)) @@ -247,19 +274,16 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): correlated_traffic): """Calculate the drop percentage and run the traffic""" completed = False - tx_rate_fps = 0 - rx_rate_fps = 0 - for sample in samples: - tx_rate_fps += sum( - port['tx_throughput_fps'] for port in sample.values()) - rx_rate_fps += sum( - port['rx_throughput_fps'] for port in sample.values()) - tx_rate_fps = round(float(tx_rate_fps) / len(samples), 2) - rx_rate_fps = round(float(rx_rate_fps) / len(samples), 2) - - # TODO(esm): RFC2544 doesn't tolerate packet loss, why do we? - out_packets = sum(port['out_packets'] for port in samples[-1].values()) - in_packets = sum(port['in_packets'] for port in samples[-1].values()) + out_pkt_end = sum(port['out_packets'] for port in samples[-1].values()) + in_pkt_end = sum(port['in_packets'] for port in samples[-1].values()) + out_pkt_ini = sum(port['out_packets'] for port in samples[0].values()) + in_pkt_ini = sum(port['in_packets'] for port in samples[0].values()) + time_diff = (list(samples[-1].values())[0]['timestamp'] - + list(samples[0].values())[0]['timestamp']).total_seconds() + out_packets = out_pkt_end - out_pkt_ini + in_packets = in_pkt_end - in_pkt_ini + tx_rate_fps = float(out_packets) / time_diff + rx_rate_fps = float(in_packets) / time_diff drop_percent = 100.0 # https://tools.ietf.org/html/rfc2544#section-26.3 |