From: yufengmx <yufengx.mo@intel.com>
To: dts@dpdk.org
Cc: yufengmx <yufengx.mo@intel.com>
Subject: [dts] [next][PATCH V1 8/14] framework/pktgen: packet generator base class
Date: Sun, 28 Apr 2019 10:49:05 +0800 [thread overview]
Message-ID: <1556419751-41723-9-git-send-email-yufengx.mo@intel.com> (raw)
In-Reply-To: <1556419751-41723-1-git-send-email-yufengx.mo@intel.com>
packet generator base class
Move class PacketGenerator and stream macro definition from pktgen.py to here.
PacketGenerator is the base class of IxiaPacketGenerator/TrexPacketGenerator.
Add rfc2544/latency/loss measure methods to support more testing scenario. Use
measure() as an unify interface api for all measure methods. Add clear_streams()
to clear streams data managed by pktgen. Use dts port mapping process to take the
place of pktgen port mapping process to reduce redundancy source code. Add
_get_stream() to make subclass possible to get streams option.
Signed-off-by: yufengmx <yufengx.mo@intel.com>
---
framework/pktgen_base.py | 400 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 400 insertions(+)
create mode 100644 framework/pktgen_base.py
diff --git a/framework/pktgen_base.py b/framework/pktgen_base.py
new file mode 100644
index 0000000..e9d3fcb
--- /dev/null
+++ b/framework/pktgen_base.py
@@ -0,0 +1,400 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2019 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import time
+import logging
+from abc import abstractmethod
+from copy import deepcopy
+from logger import getLogger
+from pprint import pformat
+
+from config import PktgenConf
+# packet generator name
+from settings import PKTGEN_DPDK, PKTGEN_TREX, PKTGEN_IXIA, PKTGEN
+
+# macro definition
+TRANSMIT_CONT = 'continuous'
+TRANSMIT_M_BURST = 'multi_burst'
+TRANSMIT_S_BURST = 'single_burst'
+# set logger
+FORMAT = '%(message)s'
+logging.basicConfig(format=FORMAT)
+logger = logging.getLogger(os.path.basename(__file__)[:-3].upper())
+logger.setLevel(logging.INFO)
+
+
+class PacketGenerator(object):
+ """
+ Basic class for packet generator, define basic function for each kinds of
+ generators
+ """
+ def __init__(self, tester):
+ self.logger = getLogger(PKTGEN)
+ self.tester = tester
+ self.__streams = []
+ self._ports_map = []
+
+ def prepare_generator(self):
+ self._prepare_generator()
+
+ def _convert_pktgen_port(self, port_id):
+ '''
+ :param port_id:
+ index of a port in packet generator tool
+ '''
+ try:
+ gen_pci = self._get_port_pci(port_id)
+ if not gen_pci:
+ msg = "can't get port {0} pci address".format(port_id)
+ raise Exception(msg)
+ for port_idx, info in enumerate(self.tester.ports_info):
+ if 'pci' not in info or info['pci'] == 'N/A':
+ return -1
+ tester_pci = info['pci']
+ if tester_pci == gen_pci:
+ msg = "gen port {0} map test port {1}".format(
+ port_id, port_idx)
+ self.logger.info(msg)
+ return port_idx
+ else:
+ port = -1
+ except:
+ port = -1
+
+ return port
+
+ def _convert_tester_port(self, port_id):
+ '''
+ :param port_id:
+ index of a port in dts tester ports info
+ '''
+ try:
+ info = self.tester.ports_info[port_id]
+ # limit to nic port, not including ixia port
+ if 'pci' not in info or info['pci'] == 'N/A':
+ return -1
+ tester_pci = info['pci']
+ port = self._get_gen_port(tester_pci)
+ msg = "test port {0} map gen port {1}".format(port_id, port)
+ self.logger.info(msg)
+ except:
+ port = -1
+
+ return port
+
+ def add_stream(self, tx_port, rx_port, pcap_file):
+ pktgen_tx_port = self._convert_tester_port(tx_port)
+ pktgen_rx_port = self._convert_tester_port(rx_port)
+
+ stream_id = len(self.__streams)
+ stream = {'tx_port': pktgen_tx_port,
+ 'rx_port': pktgen_rx_port,
+ 'pcap_file': pcap_file}
+ self.__streams.append(stream)
+
+ return stream_id
+
+ def add_streams(self, streams):
+ '''' a group of streams '''
+ raise NotImplementedError
+
+ def config_stream(self, stream_id=0, opts={}):
+ if self._check_options(opts) is not True:
+ self.logger.error("Failed to configure stream[%d]" % stream_id)
+ return
+ stream = self.__streams[stream_id]
+ stream['options'] = opts
+
+ def config_streams(self, stream_ids, nic, frame_size, port_num):
+ ''' all streams using the default option '''
+ raise NotImplementedError
+
+ def get_streams(self):
+ return self.__streams
+
+ def clear_streams(self):
+ ''' clear streams '''
+ self._clear_streams()
+ self.__streams = []
+
+ def _set_stream_rate_percent(self, rate_percent):
+ ''' set all streams' rate percent '''
+ if not self.__streams:
+ return
+ for stream in self.__streams:
+ stream['rate'] = rate_percent
+
+ def _set_stream_pps(self, pps):
+ ''' set all streams' pps '''
+ if not self.__streams:
+ return
+ for stream in self.__streams:
+ stream['pps'] = pps
+
+ def reset_streams(self):
+ self.__streams = []
+
+ def measure_throughput(self, stream_ids=[], options={}):
+ """
+ Measure throughput on each tx ports
+ """
+ bps_rx = []
+ pps_rx = []
+ self._prepare_transmission(stream_ids=stream_ids)
+ self._start_transmission(stream_ids)
+
+ delay = options.get('delay') or 5
+ time.sleep(delay)
+ used_rx_port = []
+ for stream_id in stream_ids:
+ if self.__streams[stream_id]['rx_port'] not in used_rx_port:
+ rxbps_rates, rxpps_rates = self._retrieve_port_statistic(
+ stream_id, 'throughput')
+ used_rx_port.append(self.__streams[stream_id]['rx_port'])
+ bps_rx.append(rxbps_rates)
+ pps_rx.append(rxpps_rates)
+ self._stop_transmission(stream_id)
+
+ bps_rx_total = self._summary_statistic(bps_rx)
+ pps_rx_total = self._summary_statistic(pps_rx)
+ self.logger.info("throughput: pps_rx %f, bps_rx %f" % (pps_rx_total, bps_rx_total))
+
+ return bps_rx_total, pps_rx_total
+
+ def _measure_loss(self, stream_ids=[], options={}):
+ """
+ Measure lost rate on each tx/rx ports
+ """
+ self._prepare_transmission(stream_ids=stream_ids)
+ self._start_transmission(stream_ids, options)
+ self._stop_transmission(None)
+ result = {}
+ used_rx_port = []
+ for stream_id in stream_ids:
+ port_id = self.__streams[stream_id]['rx_port']
+ if port_id in used_rx_port:
+ continue
+ stats = self._retrieve_port_statistic(stream_id, 'loss')
+ tx_pkts, rx_pkts = stats
+ lost_p = tx_pkts - rx_pkts
+ if tx_pkts <= 0:
+ loss_rate = 0
+ else:
+ loss_rate = float(lost_p) / float(tx_pkts)
+ if loss_rate < 0:
+ loss_rate = 0
+ result[port_id] = (loss_rate, tx_pkts, rx_pkts)
+ return result
+
+ def measure_loss(self, stream_ids=[], options={}):
+ result = self._measure_loss(stream_ids, options)
+ # here only to make sure that return value is the same as dts/etgen format
+ # In real testing scenario, this method can offer more data than it
+ return result.values()[0]
+
+ def measure_latency(self, stream_ids=[], options={}):
+ """
+ Measure latency on each tx/rx ports
+ """
+ self._prepare_transmission(stream_ids=stream_ids, latency=True)
+ self._start_transmission(stream_ids, options)
+ self._stop_transmission(None)
+
+ result = {}
+ used_rx_port = []
+ for stream_id in stream_ids:
+ port_id = self.__streams[stream_id]['rx_port']
+ if port_id in used_rx_port:
+ continue
+ stats = self._retrieve_port_statistic(stream_id, 'latency')
+ result[port_id] = stats
+ self.logger.info(result)
+
+ return result
+
+ def _check_loss_rate(self, result, permit_loss_rate):
+ '''
+ support multiple link peer, if any link peer loss rate happen set
+ return value to False
+ '''
+ for port_id, _result in result.iteritems():
+ loss_rate, _, _ = _result
+ if loss_rate > permit_loss_rate:
+ return False
+ else:
+ return True
+
+ def measure_rfc2544(self, stream_ids=[], options={}):
+ """ check loss rate with rate percent dropping """
+ loss_rate_table = []
+ rate_percent = float(100)
+ permit_loss_rate = options.get('pdr') or 0
+ self.logger.info("allow loss rate: %f " % permit_loss_rate)
+ rate_step = options.get('drop_step') or 1
+ result = self._measure_loss(stream_ids, options)
+ status = self._check_loss_rate(result, permit_loss_rate)
+ loss_rate_table.append(
+ [options.get('rate') or rate_percent, result])
+ # if first time loss rate is ok, ignore left flow
+ if status:
+ # return data is the same with dts/etgen format
+ # In fact, multiple link peer have multiple loss rate value,
+ # here only pick one
+ return result.values()[0]
+ _options = deepcopy(options)
+ while not status and rate_percent > 0:
+ rate_percent = rate_percent - rate_step
+ if rate_percent <= 0:
+ msg = "rfc2544 run under zero rate"
+ self.logger.warning(msg)
+ break
+ # set stream rate percent to custom value
+ self._set_stream_rate_percent(rate_percent)
+ # run loss rate testing
+ result = self._measure_loss(stream_ids, _options)
+ loss_rate_table.append([rate_percent, result])
+ status = self._check_loss_rate(result, permit_loss_rate)
+ self.logger.info(pformat(loss_rate_table))
+ self.logger.info("zero loss rate percent is %f" % rate_percent)
+ # use last result as return data to keep the same with dts/etgen format
+ # In fact, multiple link peer have multiple loss rate value,
+ # here only pick one
+ return loss_rate_table[-1][1].values()[0]
+
+ def measure_rfc2544_with_pps(self, stream_ids=[], options={}):
+ """
+ check loss rate with pps bisecting.(not implemented)
+
+ Currently, ixia/trex use rate percent to control port flow rate,
+ pps not supported.
+ """
+ max_pps = options.get('max_pps')
+ min_pps = options.get('min_pps')
+ step = options.get('step') or 10000
+ permit_loss_rate = options.get('permit_loss_rate') or 0.0001
+ # traffic parameters
+ loss_pps_table = []
+ pps = traffic_pps_max = max_pps
+ traffic_pps_min = min_pps
+
+ while True:
+ # set stream rate percent to custom value
+ self._set_stream_pps(pps)
+ # run loss rate testing
+ _options = deepcopy(options)
+ result = self._measure_loss(stream_ids, _options)
+ loss_pps_table.append([pps, result])
+ status = self._check_loss_rate(result, permit_loss_rate)
+ if status:
+ traffic_pps_max = pps
+ else:
+ traffic_pps_min = pps
+ if traffic_pps_max - traffic_pps_min < step:
+ break
+ pps = (traffic_pps_max - traffic_pps_min)/2 + traffic_pps_min
+
+ self.logger.info("zero loss pps is %f" % last_no_lost_mult)
+ # use last result as return data to keep the same with dts/etgen format
+ # In fact, multiple link peer have multiple loss rate value,
+ # here only pick one
+ return loss_pps_table[-1][1].values()[0]
+
+ def measure(self, stream_ids, traffic_opt):
+ '''
+ use as an unify interface method for packet generator
+ '''
+ method = traffic_opt.get('method')
+ if method == 'throughput':
+ result = self.measure_throughput(stream_ids, traffic_opt)
+ elif method == 'latency':
+ result = self.measure_latency(stream_ids, traffic_opt)
+ elif method == 'loss':
+ result = self.measure_loss(stream_ids, traffic_opt)
+ elif method == 'rfc2544':
+ result = self.measure_rfc2544(stream_ids, traffic_opt)
+ elif method == 'rfc2544_with_pps':
+ result = self.measure_rfc2544_with_pps(stream_ids, traffic_opt)
+ else:
+ result = None
+
+ self.logger.info(result)
+
+ return result
+
+ def _summary_statistic(self, array=[]):
+ """
+ Summary all values in statistic array
+ """
+ summary = 0.000
+ for value in array:
+ summary += value
+
+ return summary
+
+ def _get_stream(self, stream_id):
+ return self.__streams[stream_id]
+
+ def _get_generator_conf_instance(self):
+ conf_inst = PktgenConf(self.pktgen_type)
+ pktgen_inst_type = conf_inst.pktgen_conf.get_sections()
+ if len(pktgen_inst_type) < 1:
+ msg = ("packet generator <{0}> has no configuration "
+ "in pktgen.cfg").format(self.pktgen_type)
+ raise Exception(msg)
+ return conf_inst
+
+ @abstractmethod
+ def _prepare_transmission(self, stream_ids=[], latency=False):
+ pass
+
+ @abstractmethod
+ def _start_transmission(self, stream_ids, options={}):
+ pass
+
+ @abstractmethod
+ def _stop_transmission(self, stream_id):
+ pass
+
+ @abstractmethod
+ def _retrieve_port_statistic(self, stream_id, mode):
+ pass
+
+ @abstractmethod
+ def _check_options(self, opts={}):
+ pass
+
+ @abstractmethod
+ def quit_generator(self):
+ pass
+
+
+class DpdkPacketGenerator(PacketGenerator): pass # not implemented
\ No newline at end of file
--
1.9.3
next prev parent reply other threads:[~2019-04-28 2:45 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-04-28 2:48 [dts] [next][PATCH V1 0/14] [dts/pktgen]: dts packet generator api yufengmx
2019-04-28 2:48 ` [dts] [next][PATCH V1 1/14] conf/pktgen: add pktgen key word in crbs.cfg yufengmx
2019-04-28 2:48 ` [dts] [next][PATCH V1 2/14] conf/pktgen: packet generator configure file definition yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 3/14] doc/pktgen: trex tool known issues yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 4/14] doc/pktgen: migrate from etgen api to pktgen api yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 5/14] doc/pktgen: pktgen api program guide document yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 6/14] framework/pktgen: packet generator configure file parse yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 7/14] framework/pktgen: initialize pktgen logger yufengmx
2019-04-28 2:49 ` yufengmx [this message]
2019-04-28 2:49 ` [dts] [next][PATCH V1 9/14] framework/pktgen: ixia packet generator relevant classes yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 0/14] framework/pktgen: trex " yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 1/14] framework/pktgen: pktgen instance creation and helper yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 2/14] framework/pktgen: packet generator types definition yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 3/14] framework/pktgen: packet generator init and trex port yufengmx
2019-04-28 2:49 ` [dts] [next][PATCH V1 4/14] framework/pktgen: utils methods yufengmx
2019-05-29 2:45 ` [dts] [next][PATCH V1 0/14] [dts/pktgen]: dts packet generator api Tu, Lijuan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1556419751-41723-9-git-send-email-yufengx.mo@intel.com \
--to=yufengx.mo@intel.com \
--cc=dts@dpdk.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).