Currently the TRex Test has a hardcoded set of streams.
In order to make it more generic and extendable, this series first refactors the client and server code into a library that does not depend on other LNST code. That way, a small cli appliation can be implemented to inject traffic using the same code as LNST would. This is useful for early prototyping.
An example of such as tool is introduced: test_tools/tperf. It basically runs the client and server as LNST would and report the aggregated throughput.
Finally, the stream can be modularized into TRex compatible modules so that: - New stream generators can be easily implemented - TRex tools (e.g: stl-sym) can be used for stream generator development
The current stream generation is modularized as an example: UdpSimple
Finally a new stream generator is introduced: UdpMultiflow, that creates multiple UDP streams by modifying the source and destination ports.
Adrian Moreno (4): lnst.Tests.TRex Create lnst-independent library test_tools: Add tperf lnst.TRex Use stl compatible modules to generate streams lnst.Tests.TRex: Add UDPMultiflow
lnst/External/TRex/TRexLib.py | 215 +++++++++++++++++++++++++++++ lnst/External/TRex/UDPMultiflow.py | 39 ++++++ lnst/External/TRex/UDPSimple.py | 38 +++++ lnst/External/TRex/__init__.py | 0 lnst/External/__init__.py | 0 lnst/Tests/TRex.py | 144 ++++--------------- test_tools/tperf/tperf | 201 +++++++++++++++++++++++++++ 7 files changed, 517 insertions(+), 120 deletions(-) create mode 100644 lnst/External/TRex/TRexLib.py create mode 100644 lnst/External/TRex/UDPMultiflow.py create mode 100644 lnst/External/TRex/UDPSimple.py create mode 100644 lnst/External/TRex/__init__.py create mode 100644 lnst/External/__init__.py create mode 100755 test_tools/tperf/tperf
Refactor client and server code into a library that does not depend on LNST code as a first step to add external tools based on TRex.
lnst.External.TRex.TRexLib: Independent TRex client/server library lnst.Tests.TRex.py: LNST Test that wraps the above library
Signed-off-by: Adrian Moreno amorenoz@redhat.com --- lnst/External/TRex/TRexLib.py | 218 +++++++++++++++++++++++++++++++++ lnst/External/TRex/__init__.py | 0 lnst/External/__init__.py | 0 lnst/Tests/TRex.py | 144 ++++------------------ 4 files changed, 242 insertions(+), 120 deletions(-) create mode 100644 lnst/External/TRex/TRexLib.py create mode 100644 lnst/External/TRex/__init__.py create mode 100644 lnst/External/__init__.py
diff --git a/lnst/External/TRex/TRexLib.py b/lnst/External/TRex/TRexLib.py new file mode 100644 index 0000000..68e6289 --- /dev/null +++ b/lnst/External/TRex/TRexLib.py @@ -0,0 +1,218 @@ +import os +import sys +import time +import logging +import subprocess +import tempfile +import signal +import yaml + +TREX_CLI_DEFAULT_PARAMS = { + "warmup_time": 5, + "server_hostname": "localhost", + "trex_stl_path": 'trex_client/interactive', + "msg_size": 64 + } + +class TRexCli: + """ + TRex client. + In its constructor, it accepts any object with the following attributes + - trex_dir (str): Path to the trex directory + - ports (list): List of integer values ranging 0 to len(flows) + - flows (list): A list of tuples of dictionaries each containing the following keys: + mac_addr: Source MAC address of the flow + pci_addr: PCI address of the interface to use + ip_addr: Source IP address of the flow + - duration (int): Integer value of the duration of the test + - warmup_time (int): Time to wait before starting to take measurements. Default: 5 + - server_hostname (str): Host where the server is running. + - msg_size (int): Message size + """ + trex_stl_path = 'trex_client/interactive' + + def __init__(self, params): + self.params = params + self.results = {} + for key in TREX_CLI_DEFAULT_PARAMS: + if key not in params.__dict__: + setattr(self.params, key, TREX_CLI_DEFAULT_PARAMS[key]) + + def get_results(self): + return self.results + + def run(self): + sys.path.insert(0, os.path.join(self.params.trex_dir, + self.trex_stl_path)) + + from trex.stl import api as trex_api + + try: + return self._run(trex_api) + except trex_api.TRexError as e: + raise TRexError(str(e)) + + def _run(self, trex_api): + client = trex_api.STLClient(server=self.params.server_hostname) + client.connect() + + try: + client.acquire(ports=self.params.ports, force=True) + except: + self.results["msg"] = "Failed to acquire ports" + return False + + try: + client.reset(ports=self.params.ports) + except: + client.release(ports=self.params.ports) + self.results["msg"] = "Failed to reset ports" + return False + + for i, (src, dst) in enumerate(self.params.flows): + L2 = trex_api.Ether( + src=str(src["mac_addr"]), + dst=str(dst["mac_addr"])) + L3 = trex_api.IP( + src=str(src["ip_addr"]), + dst=str(dst["ip_addr"])) + L4 = trex_api.UDP() + base_pkt = L2/L3/L4 + + pad = max(0, self.params.msg_size - len(base_pkt)) * 'x' + packet = base_pkt/pad + + trex_packet = trex_api.STLPktBuilder(pkt=packet) + + trex_stream = trex_api.STLStream( + packet=trex_packet, + mode=trex_api.STLTXCont(percentage=100)) + + port = self.params.ports[i] + client.add_streams(trex_stream, ports=[port]) + + client.set_port_attr(ports=self.params.ports, promiscuous=True) + + + measurements = [] + + client.start(ports=self.params.ports) + + time.sleep(self.params.warmup_time) + + client.clear_stats(ports=self.params.ports) + self.results["start_time"] = time.time() + + for i in range(self.params.duration): + time.sleep(1) + measurements.append(dict(timestamp=time.time(), + measurement=client.get_stats( + ports=self.params.ports, + sync_now=True))) + + client.stop(ports=self.params.ports) + client.release(ports=self.params.ports) + + self.results["data"] = measurements + return True + +class TRexSrv: + """ + TRex server. This class runs TRex in server mode and waits for it to be killed + + In its constructor, it accepts any object with the following attributes + - trex_dir (str): Path to the trex directory + - flows (list): A list of tuples of dictionaries each containing the following keys: + mac_addr: Source MAC address of the flow + pci_addr: PCI address of the interface to use + ip_addr: Source IP address of the flow + - cores (list): List of CPU cores to use + """ + def __init__(self, params): + self.params = params + + def get_results(self): + return None + + def run(self): + trex_server_conf = [{'port_limit': len(self.params.flows), + 'version': 2, + 'interfaces': [], + 'platform': { + 'dual_if': [{ + 'socket': 0, + 'threads': self.params.cores}], + 'latency_thread_id': 0, + 'master_thread_id': 1}, + 'port_info': []}] + + for src, dst in self.params.flows: + short_pci_addr = src["pci_addr"].partition(':')[2] + trex_server_conf[0]['interfaces'].append(short_pci_addr) + trex_server_conf[0]['port_info'].append( + {'src_mac': str(src["mac_addr"]), + 'dest_mac': str(dst["mac_addr"])}) + + with tempfile.NamedTemporaryFile(mode="w+") as cfg_file: + yaml.dump(trex_server_conf, cfg_file) + cfg_file.flush() + os.fsync(cfg_file.file.fileno()) + + os.chdir(self.params.trex_dir) + server = subprocess.Popen( + [os.path.join(self.params.trex_dir, "t-rex-64"), + "--cfg", cfg_file.name, "-i"], + stdin=open('/dev/null'), stdout=open('/dev/null','w'), + stderr=subprocess.PIPE, close_fds=True) + + self._wait_for_interrupt() + + server.send_signal(signal.SIGINT) + out, err = server.communicate() + if err: + logging.error(err) + return False + return True + + def _wait_for_interrupt(self): + class InterruptException(Exception): + pass + + def handler(signum, frame): + raise InterruptException + + try: + old_handler = signal.signal(signal.SIGINT, handler) + signal.pause() + except InterruptException: + pass + finally: + signal.signal(signal.SIGINT, old_handler) + +class TRexError(Exception): + pass + +class TRexParams: + """ + TRexParams is a simple class that encapsulates a dictionary as attributes + """ + def __init__(self, **kwargs): + for key in kwargs: + setattr(self, key, kwargs[key]) + + def __str__(self): + string = "" + for key, val in list(self.__dict__.items()): + string += "%s: %s\n" % (key, str(val)) + return string + + def __iter__(self): + for attr, val in list(self.__dict__.items()): + yield (attr, val) + + def __setitem__(self, name, val): + setattr(self, name, val) + + def __getitem__(self, name, val): + getattr(self, name, val) + diff --git a/lnst/External/TRex/__init__.py b/lnst/External/TRex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lnst/External/__init__.py b/lnst/External/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lnst/Tests/TRex.py b/lnst/Tests/TRex.py index e09be00..40da393 100644 --- a/lnst/Tests/TRex.py +++ b/lnst/Tests/TRex.py @@ -1,20 +1,13 @@ -import os -import sys -import yaml -import time -import logging -import subprocess -import tempfile -import signal from lnst.Common.Parameters import Param, StrParam, IntParam, FloatParam from lnst.Common.Parameters import IpParam, DeviceOrIpParam from lnst.Tests.BaseTestModule import BaseTestModule, TestModuleError +from lnst.External.TRex.TRexLib import TRexCli, TRexSrv, TRexError +
class TRexCommon(BaseTestModule): trex_dir = StrParam(mandatory=True)
class TRexClient(TRexCommon): - #make Int List ports = Param(mandatory=True)
flows = Param(mandatory=True) @@ -27,6 +20,10 @@ class TRexClient(TRexCommon): server_hostname = StrParam(default="localhost") trex_stl_path = 'trex_client/interactive'
+ def __init__(self, **kwargs): + super(TRexClient, self).__init__(**kwargs) + self.impl = TRexCli(self.params) + def runtime_estimate(self): _duration_overhead = 5 return (self.params.duration + @@ -34,82 +31,15 @@ class TRexClient(TRexCommon): _duration_overhead)
def run(self): - sys.path.insert(0, os.path.join(self.params.trex_dir, - self.trex_stl_path)) - - from trex.stl import api as trex_api - + self._res_data={} try: - return self._run(trex_api) - except trex_api.TRexError as e: + rc = self.impl.run() + except TRexError as e: #TRex errors aren't picklable so we wrap them like this raise TestModuleError(str(e))
- def _run(self, trex_api): - client = trex_api.STLClient(server=self.params.server_hostname) - client.connect() - - self._res_data = {} - - try: - client.acquire(ports=self.params.ports, force=True) - except: - self._res_data["msg"] = "Failed to acquire ports" - return False - - try: - client.reset(ports=self.params.ports) - except: - client.release(ports=self.params.ports) - self._res_data["msg"] = "Failed to reset ports" - return False - - for i, (src, dst) in enumerate(self.params.flows): - L2 = trex_api.Ether( - src=str(src["mac_addr"]), - dst=str(dst["mac_addr"])) - L3 = trex_api.IP( - src=str(src["ip_addr"]), - dst=str(dst["ip_addr"])) - L4 = trex_api.UDP() - base_pkt = L2/L3/L4 - - pad = max(0, self.params.msg_size - len(base_pkt)) * 'x' - packet = base_pkt/pad - - trex_packet = trex_api.STLPktBuilder(pkt=packet) - - trex_stream = trex_api.STLStream( - packet=trex_packet, - mode=trex_api.STLTXCont(percentage=100)) - - port = self.params.ports[i] - client.add_streams(trex_stream, ports=[port]) - - client.set_port_attr(ports=self.params.ports, promiscuous=True) - - - measurements = [] - - client.start(ports=self.params.ports) - - time.sleep(self.params.warmup_time) - - client.clear_stats(ports=self.params.ports) - self._res_data["start_time"] = time.time() - - for i in range(self.params.duration): - time.sleep(1) - measurements.append(dict(timestamp=time.time(), - measurement=client.get_stats( - ports=self.params.ports, - sync_now=True))) - - client.stop(ports=self.params.ports) - client.release(ports=self.params.ports) - - self._res_data["data"] = measurements - return True + self._res_data = self.impl.get_results() + return rc
class TRexServer(TRexCommon): #TODO make ListParam @@ -117,43 +47,17 @@ class TRexServer(TRexCommon):
cores = Param(mandatory=True)
+ def __init__(self, **kwargs): + super(TRexServer, self).__init__(**kwargs) + self.impl = TRexSrv(self.params) + def run(self): - trex_server_conf = [{'port_limit': len(self.params.flows), - 'version': 2, - 'interfaces': [], - 'platform': { - 'dual_if': [{ - 'socket': 0, - 'threads': self.params.cores}], - 'latency_thread_id': 0, - 'master_thread_id': 1}, - 'port_info': []}] - - for src, dst in self.params.flows: - short_pci_addr = src["pci_addr"].partition(':')[2] - trex_server_conf[0]['interfaces'].append(short_pci_addr) - trex_server_conf[0]['port_info'].append( - {'src_mac': str(src["mac_addr"]), - 'dest_mac': str(dst["mac_addr"])}) - - with tempfile.NamedTemporaryFile(mode="w+") as cfg_file: - yaml.dump(trex_server_conf, cfg_file) - cfg_file.flush() - os.fsync(cfg_file.file.fileno()) - - os.chdir(self.params.trex_dir) - server = subprocess.Popen( - [os.path.join(self.params.trex_dir, "t-rex-64"), - "--cfg", cfg_file.name, "-i"], - stdin=open('/dev/null'), stdout=open('/dev/null','w'), - stderr=subprocess.PIPE, close_fds=True) - - self.wait_for_interrupt() - - server.send_signal(signal.SIGINT) - out, err = server.communicate() - if err: - logging.error(err) - return False - - return True + self._res_data={} + try: + rc = self.impl.run() + except TRexError as e: + #TRex errors aren't picklable so we wrap them like this + raise TestModuleError(str(e)) + + self._res_data = self.impl.get_results() + return rc
Add a TRex Performance Tool
User the same Trex client and server code as LNST to enable quick test prototyping
Signed-off-by: Adrian Moreno amorenoz@redhat.com --- test_tools/tperf/tperf | 194 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100755 test_tools/tperf/tperf
diff --git a/test_tools/tperf/tperf b/test_tools/tperf/tperf new file mode 100755 index 0000000..1464999 --- /dev/null +++ b/test_tools/tperf/tperf @@ -0,0 +1,194 @@ +#! /usr/bin/env python3 + +""" +TPerf is a TRex-based performance tool aimed to quickly inject traffic and measure +performace. + +Usage: + tperf --trex /path/to/trex-dir --server 0000:01:01.{0,1} + (in another terminal) + tperf --trex /path/to/trex-dir --client 0000:01:01.{0,1} + +TODO: + Remove hardcoded cores +""" + +import argparse +import json +import statistics +import os +import sys +import inspect + +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +root_path = os.path.join(currentdir,"..", "..") +sys.path.insert(0, root_path) +from lnst.External.TRex.TRexLib import TRexCli, TRexSrv, TRexParams + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument('iface', nargs=2, help='interfaces to use') + parser.add_argument('--trex', metavar='T', type=str, nargs=1, help='trex directory', required=True) + parser.add_argument('--raw', metavar='R', type=str, nargs=1, help='raw output file. If set, file where the raw output will be stored (in json format)') + parser.add_argument('--server', dest='server', action='store_const', const=True, default=False, help='Run server (default: runs client side)') + args = parser.parse_args() + + flows = get_flows(args.iface) + trex_dir=args.trex[0] + + if args.server: + trex_srv_params = TRexParams ( + trex_dir=trex_dir, + flows=flows, + cores=[6, 8], + ) + + server = TRexSrv(trex_srv_params) + server.run() + else: + trex_cli_params = TRexParams ( + trex_dir=trex_dir, + ports=list(range(len(flows))), + flows=flows, + duration=20, + ) + client = TRexCli(trex_cli_params) + client.run() + + results = client.get_results() + + if args.raw: + with open(args.raw[0], 'w') as f: + json.dump(client.results, f) + + print_stats(stats(digest(client.results))) + + +def get_flows(ifaces): + flow_src1= { + "mac_addr":"ee:af:bf:cf:df:01", + "ip_addr":"192.168.1.1", + "pci_addr":ifaces[0] + } + flow_dest1 = { + "mac_addr":"ff:af:bf:cf:df:01", + "ip_addr":"192.168.1.2", + "pci_addr":"" + } + + flow_src2= { + "mac_addr":"ee:af:bf:cf:df:02", + "ip_addr":"192.168.2.1", + "pci_addr":ifaces[1] + } + flow_dest2 = { + "mac_addr":"ff:af:bf:cf:df:02", + "ip_addr":"192.168.2.2", + "pci_addr":"" + } + + + return [(flow_src1, flow_dest1), (flow_src2, flow_dest2)] + +""" +Print stats +""" +def print_stats(stats): + print("----------- Test Results -----------") + print("Number of samples: %d" % stats["nsamples"]) + for port in stats["result"]: + print("Port %s:" % port) + print(" TX: %.3f Kpps" % stats["result"][port]["TX"]) + print(" RX: %.3f Kpps" % stats["result"][port]["RX"]) + + +def stats(digest): + """ + Given a digested result, calculate mean tx/rx kpps + Args: Digested samples + Returns: a dictionary with the following format + { + "nsamples": 52 + "result": { + 0: { + "TX": 2352.238 + "RX": 4581.312 + }, + 1: ... + } + } + """ + result= {} + for port in digest[0].get("packets"): + result[port]= { + "TX": statistics.mean( + [sample["packets"][port]["tx_delta"]/ + sample["time_delta"] for sample in digest]) / 1000, + "RX": statistics.mean( + [sample["packets"][0]["rx_delta"]/ + sample["time_delta"] for sample in digest]) / 1000 + } + + return { + "nsamples": len(digest), + "result": result + } + +def digest(result): + """ + Chew the results a bit and show a nice summary + Args: raw trex results + Returns: A list of samples with the following format: + [ + { + "time_delta": 0.1 + "packets" + [ + "port0": { + "tx_delta": 12345 + "rx_delta": 12334 + }, + + "port0": { + "tx_delta": 12345 + "rx_delta": 12334 + } + } + ] + """ + prev_time = result["start_time"] + prev_tx_val = {} + prev_rx_val = {} + digested_results=[] + for res in result["data"]: + sample={} + time_delta = res["timestamp"] - prev_time + sample["time_delta"]=time_delta + packets={} + + for port in res["measurement"]: + if port == "global" or port == "total" or port == "flow_stats" or port == "latency": + continue + + tx_delta = res["measurement"][port]["opackets"] - (prev_tx_val.get(port) or 0) + rx_delta = res["measurement"][port]["ipackets"] - (prev_rx_val.get(port) or 0) + + packets[port] = { + "tx_delta": tx_delta, + "rx_delta": rx_delta + } + + prev_tx_val[port] = res["measurement"][port]["opackets"] + prev_rx_val[port] = res["measurement"][port]["ipackets"] + + sample["packets"]=packets + digested_results.append(sample) + + prev_time = res["timestamp"] + + return digested_results + +if __name__ == "__main__": + main()
Use external, dynamically loaded modules to generate the streams that get added to the client.
The module API is compatible with TRex's STL [1] to ease prototyping.
[1] https://trex-tgn.cisco.com/trex/doc/trex_stateless.html
Signed-off-by: Adrian Moreno amorenoz@redhat.com --- lnst/External/TRex/TRexLib.py | 37 +++++++++++++++----------------- lnst/External/TRex/UDPSimple.py | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 lnst/External/TRex/UDPSimple.py
diff --git a/lnst/External/TRex/TRexLib.py b/lnst/External/TRex/TRexLib.py index 68e6289..39b56f9 100644 --- a/lnst/External/TRex/TRexLib.py +++ b/lnst/External/TRex/TRexLib.py @@ -1,3 +1,4 @@ +import importlib import os import sys import time @@ -11,7 +12,8 @@ TREX_CLI_DEFAULT_PARAMS = { "warmup_time": 5, "server_hostname": "localhost", "trex_stl_path": 'trex_client/interactive', - "msg_size": 64 + "msg_size": 64, + "module": "UDPSimple" }
class TRexCli: @@ -28,6 +30,7 @@ class TRexCli: - warmup_time (int): Time to wait before starting to take measurements. Default: 5 - server_hostname (str): Host where the server is running. - msg_size (int): Message size + - module(str): The python module to call for stream creation. Default (UDPSimple) """ trex_stl_path = 'trex_client/interactive'
@@ -69,31 +72,25 @@ class TRexCli: self.results["msg"] = "Failed to reset ports" return False
- for i, (src, dst) in enumerate(self.params.flows): - L2 = trex_api.Ether( - src=str(src["mac_addr"]), - dst=str(dst["mac_addr"])) - L3 = trex_api.IP( - src=str(src["ip_addr"]), - dst=str(dst["ip_addr"])) - L4 = trex_api.UDP() - base_pkt = L2/L3/L4 - - pad = max(0, self.params.msg_size - len(base_pkt)) * 'x' - packet = base_pkt/pad + module = importlib.import_module('.'.join(["lnst", "External", "TRex", self.params.module])) + stream_generator = module.register()
- trex_packet = trex_api.STLPktBuilder(pkt=packet) + for i, (src, dst) in enumerate(self.params.flows): + port = self.params.ports[i] + modkwargs = {} + modkwargs["port_id"] = port + modkwargs["msg_size"] = self.params.msg_size + modkwargs["src_ip"] = src["ip_addr"] + modkwargs["dst_ip"] = dst["ip_addr"] + modkwargs["src_mac"] = src["mac_addr"] + modkwargs["dst_mac"] = dst["mac_addr"]
- trex_stream = trex_api.STLStream( - packet=trex_packet, - mode=trex_api.STLTXCont(percentage=100)) + trex_streams = stream_generator.get_streams(direction=(port%2), **modkwargs)
- port = self.params.ports[i] - client.add_streams(trex_stream, ports=[port]) + client.add_streams(trex_streams, ports=[port])
client.set_port_attr(ports=self.params.ports, promiscuous=True)
- measurements = []
client.start(ports=self.params.ports) diff --git a/lnst/External/TRex/UDPSimple.py b/lnst/External/TRex/UDPSimple.py new file mode 100644 index 0000000..dfa89de --- /dev/null +++ b/lnst/External/TRex/UDPSimple.py @@ -0,0 +1,38 @@ +from trex_stl_lib.api import * + + +class UDPSimple(object): + """ + Generate a simple continuous UDP stream + Port MAC and IP addresses are used + Extra arguments in kwargs: + msg_size: the size of the packet to use (default 64) + port_id: The port the stream will be added to + """ + + def create_stream (self, **kwargs): + # Use port's configured mac and ip addresses + L2 = Ether() + L3 = IP() + L4 = UDP() + + size = kwargs.get("msg_size", 64) + + base_pkt = L2/L3/L4 + + pad = max(0, size - len(base_pkt)) * 'x' + packet = base_pkt/pad + trex_packet = STLPktBuilder(pkt=packet) + + return STLStream( + packet=trex_packet, + mode=STLTXCont(percentage=100)) + + def get_streams (self, direction = 0, **kwargs): + # create 1 stream + return [ self.create_stream(**kwargs) ] + +# dynamic load - used for trex console or simulator +def register(): + return UDPSimple() +
It generates many UDP flows with different source and destination UDP ports
Signed-off-by: Adrian Moreno amorenoz@redhat.com --- lnst/External/TRex/UDPMultiflow.py | 39 ++++++++++++++++++++++++++++++ test_tools/tperf/tperf | 9 ++++++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 lnst/External/TRex/UDPMultiflow.py
diff --git a/lnst/External/TRex/UDPMultiflow.py b/lnst/External/TRex/UDPMultiflow.py new file mode 100644 index 0000000..9c768cb --- /dev/null +++ b/lnst/External/TRex/UDPMultiflow.py @@ -0,0 +1,39 @@ +from trex_stl_lib.api import * +from trex.stl.trex_stl_hltapi import * + +class UDPMultiflow(object): + """ + Generate a many different UDP flows + Port MAC and IP addresses are used + Extra arguments in kwargs: + msg_size: the size of the packet to use (default 64) + port_id: The port the stream will be added to + """ + + def create_stream (self, **kwargs): + size = kwargs.get("msg_size", 64) + L2 = Ether(src=kwargs["src_mac"], dst=kwargs["dst_mac"]) + L3 = IP(src=kwargs["src_ip"], dst=kwargs["dst_ip"]) + L4 = UDP() + + base_pkt = L2/L3/L4 + pad = max(0, size - len(base_pkt)) * 'x' + base_pkt = base_pkt/pad + + vm = STLVM() + vm.var(name = "src_port", min_value=1025, max_value=65000, size=2, op="inc") + vm.var(name = "dst_port", min_value=1025, max_value=65000, size=2, op="dec") + vm.write(fv_name = "src_port", pkt_offset = "UDP.sport") + vm.write(fv_name = "dst_port", pkt_offset = "UDP.dport") + + pkt = STLPktBuilder(pkt = base_pkt, vm = vm) + + return STLStream(packet = pkt, mode = STLTXCont(percentage=100)) + + def get_streams (self, direction = 0, **kwargs): + # create 1 stream + return [self.create_stream(**kwargs)] + +# dynamic load - used for trex console or simulator +def register(): + return UDPMultiflow() diff --git a/test_tools/tperf/tperf b/test_tools/tperf/tperf index 1464999..ed6bfa5 100755 --- a/test_tools/tperf/tperf +++ b/test_tools/tperf/tperf @@ -29,8 +29,9 @@ from lnst.External.TRex.TRexLib import TRexCli, TRexSrv, TRexParams def main(): parser = argparse.ArgumentParser()
- parser.add_argument('iface', nargs=2, help='interfaces to use') + parser.add_argument('iface', nargs=2, help='interfaces to use.') parser.add_argument('--trex', metavar='T', type=str, nargs=1, help='trex directory', required=True) + parser.add_argument('--profile', metavar='P', type=str, nargs=1, help='Traffic profile to use [UDPSimple, UDPMultiflow]. Default: UDPSimple') parser.add_argument('--raw', metavar='R', type=str, nargs=1, help='raw output file. If set, file where the raw output will be stored (in json format)') parser.add_argument('--server', dest='server', action='store_const', const=True, default=False, help='Run server (default: runs client side)') args = parser.parse_args() @@ -48,12 +49,18 @@ def main(): server = TRexSrv(trex_srv_params) server.run() else: + module = "UDPSimple" + if args.profile: + module = args.profile[0] + trex_cli_params = TRexParams ( trex_dir=trex_dir, ports=list(range(len(flows))), flows=flows, duration=20, + module=module ) + client = TRexCli(trex_cli_params) client.run()
On Mon, Jun 29, 2020 at 10:31:47AM +0200, Adrian Moreno wrote:
Currently the TRex Test has a hardcoded set of streams.
In order to make it more generic and extendable, this series first refactors the client and server code into a library that does not depend on other LNST code. That way, a small cli appliation can be implemented to inject traffic using the same code as LNST would. This is useful for early prototyping.
An example of such as tool is introduced: test_tools/tperf. It basically runs the client and server as LNST would and report the aggregated throughput.
Finally, the stream can be modularized into TRex compatible modules so that:
- New stream generators can be easily implemented
- TRex tools (e.g: stl-sym) can be used for stream generator development
The current stream generation is modularized as an example: UdpSimple
Finally a new stream generator is introduced: UdpMultiflow, that creates multiple UDP streams by modifying the source and destination ports.
Adrian Moreno (4): lnst.Tests.TRex Create lnst-independent library test_tools: Add tperf lnst.TRex Use stl compatible modules to generate streams lnst.Tests.TRex: Add UDPMultiflow
lnst/External/TRex/TRexLib.py | 215 +++++++++++++++++++++++++++++ lnst/External/TRex/UDPMultiflow.py | 39 ++++++ lnst/External/TRex/UDPSimple.py | 38 +++++ lnst/External/TRex/__init__.py | 0 lnst/External/__init__.py | 0 lnst/Tests/TRex.py | 144 ++++--------------- test_tools/tperf/tperf | 201 +++++++++++++++++++++++++++ 7 files changed, 517 insertions(+), 120 deletions(-) create mode 100644 lnst/External/TRex/TRexLib.py create mode 100644 lnst/External/TRex/UDPMultiflow.py create mode 100644 lnst/External/TRex/UDPSimple.py create mode 100644 lnst/External/TRex/__init__.py create mode 100644 lnst/External/__init__.py create mode 100755 test_tools/tperf/tperf
-- 2.26.2
Fixed some whitespace errors and pushed.
Thanks, -Ondrej
lnst-developers@lists.fedorahosted.org