diff options
40 files changed, 733 insertions, 125 deletions
diff --git a/scripts/automation/regression/functional_tests/stl_basic_tests.py b/scripts/automation/regression/functional_tests/stl_basic_tests.py index 2bf97307..ecb7b465 100644 --- a/scripts/automation/regression/functional_tests/stl_basic_tests.py +++ b/scripts/automation/regression/functional_tests/stl_basic_tests.py @@ -9,8 +9,16 @@ from nose.plugins.attrib import attr from trex import CTRexScenario from trex_stl_lib import trex_stl_sim from trex_stl_lib.trex_stl_streams import STLProfile -from trex_stl_lib.trex_stl_packet_builder_scapy import RawPcapReader, RawPcapWriter +from trex_stl_lib.trex_stl_packet_builder_scapy import RawPcapReader, RawPcapWriter, Ether +from trex_stl_lib.utils.text_opts import * + import sys + +if sys.version_info > (3,0): + from io import StringIO +else: + from cStringIO import StringIO + import os import subprocess import shlex @@ -64,9 +72,18 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): raise Exception("cannot find '{0}'".format(name)) - def compare_caps (self, cap1, cap2, max_diff_sec = 0.01): - pkts1 = list(RawPcapReader(cap1)) - pkts2 = list(RawPcapReader(cap2)) + def scapy_pkt_show_to_str (self, scapy_pkt): + capture = StringIO() + save_stdout = sys.stdout + sys.stdout = capture + scapy_pkt.show() + sys.stdout = save_stdout + return capture.getvalue() + + + def compare_caps (self, output, golden, max_diff_sec = 0.01): + pkts1 = list(RawPcapReader(output)) + pkts2 = list(RawPcapReader(golden)) assert_equal(len(pkts1), len(pkts2)) @@ -75,11 +92,29 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): ts2 = float(pkt2[1][0]) + (float(pkt2[1][1]) / 1e6) if abs(ts1-ts2) > 0.000005: # 5 nsec - raise AssertionError("TS error: cap files '{0}', '{1}' differ in cap #{2} - '{3}' vs. '{4}'".format(cap1, cap2, i, ts1, ts2)) + raise AssertionError("TS error: cap files '{0}', '{1}' differ in cap #{2} - '{3}' vs. '{4}'".format(output, golden, i, ts1, ts2)) if pkt1[0] != pkt2[0]: - raise AssertionError("RAW error: cap files '{0}', '{1}' differ in cap #{2}".format(cap1, cap2, i)) + errmsg = "RAW error: output file '{0}', differs from golden '{1}' in cap #{2}".format(output, golden, i) + print(errmsg) + + print(format_text("\ndifferent fields for packet #{0}:".format(i), 'underline')) + scapy_pkt1_info = self.scapy_pkt_show_to_str(Ether(pkt1[0])).split('\n') + scapy_pkt2_info = self.scapy_pkt_show_to_str(Ether(pkt2[0])).split('\n') + + print(format_text("\nGot:\n", 'bold', 'underline')) + for line, ref in zip(scapy_pkt1_info, scapy_pkt2_info): + if line != ref: + print(format_text(line, 'bold')) + + print(format_text("\nExpected:\n", 'bold', 'underline')) + for line, ref in zip(scapy_pkt2_info, scapy_pkt1_info): + if line != ref: + print(format_text(line, 'bold')) + + print("\n") + raise AssertionError(errmsg) def run_sim (self, yaml, output, options = "", silent = False, obj = None): @@ -262,3 +297,14 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): + def test_multicore_scheduling (self): + mc_tests = ['stl/tests/single_cont.py', + 'stl/tests/single_burst.py', + 'stl/tests/multi_burst.py', + 'stl/tests/many_streams.py', + ] + + for mc_test in mc_tests: + rc = self.run_sim(mc_test, output = None, options = '--test_multi_core --limit=3840 -m 27kpps', silent = True) + assert_equal(rc, True) + diff --git a/scripts/automation/regression/setups/kiwi02/benchmark.yaml b/scripts/automation/regression/setups/kiwi02/benchmark.yaml index 2d47f8f8..60febc8f 100644 --- a/scripts/automation/regression/setups/kiwi02/benchmark.yaml +++ b/scripts/automation/regression/setups/kiwi02/benchmark.yaml @@ -122,26 +122,31 @@ test_rx_check_sfr: multiplier : 25 cores : 4 rx_sample_rate : 32 + error_tolerance : 0.01 test_rx_check_http: multiplier : 40000 cores : 2 rx_sample_rate : 32 + error_tolerance : 0.01 test_rx_check_sfr_ipv6: multiplier : 25 cores : 4 rx_sample_rate : 32 + error_tolerance : 0.01 test_rx_check_http_ipv6: multiplier : 40000 cores : 2 rx_sample_rate : 32 + error_tolerance : 0.01 test_rx_check_http_negative: multiplier : 40000 cores : 2 rx_sample_rate : 32 + error_tolerance : 0.01 test_jumbo: multiplier : 55 diff --git a/scripts/automation/regression/setups/trex-dan/benchmark.yaml b/scripts/automation/regression/setups/trex-dan/benchmark.yaml index 4b47bd8e..a31d070c 100644 --- a/scripts/automation/regression/setups/trex-dan/benchmark.yaml +++ b/scripts/automation/regression/setups/trex-dan/benchmark.yaml @@ -4,7 +4,7 @@ test_nbar_simple : multiplier : 1.5 - cores : 1 + cores : 2 exp_gbps : 0.5 cpu_to_core_ratio : 20800000 cpu2core_custom_dev: YES diff --git a/scripts/automation/regression/stateless_tests/stl_client_test.py b/scripts/automation/regression/stateless_tests/stl_client_test.py index 01a90250..3ef4713f 100644 --- a/scripts/automation/regression/stateless_tests/stl_client_test.py +++ b/scripts/automation/regression/stateless_tests/stl_client_test.py @@ -240,22 +240,29 @@ class STLClient_Test(CStlGeneral_Test): def test_all_profiles (self): - # need promiscious for this one... - if self.is_virt_nics or not self.is_loopback: - self.skip('skipping profile tests for virtual NICs') - return try: - self.c.set_port_attr(ports = [self.tx_port, self.rx_port], promiscuous = True) - + for profile in self.profiles: + print("now testing profile {0}...\n").format(profile) p1 = STLProfile.load(profile, port_id = self.tx_port) p2 = STLProfile.load(profile, port_id = self.rx_port) + # if profile contains custom MAC addrs we need promiscuous mode + # but virtual NICs does not support promiscuous mode + self.c.set_port_attr(ports = [self.tx_port, self.rx_port], promiscuous = False) + + if p1.has_custom_mac_addr(): + if not self.is_virt_nics: + self.c.set_port_attr(ports = [self.tx_port, self.rx_port], promiscuous = True) + else: + print("\n*** profile needs promiscuous mode but running on virtual NICs - skipping... ***\n") + continue + if p1.has_flow_stats(): - print("profile needs RX caps - skipping...") + print("\n*** profile needs RX caps - skipping... ***\n") continue self.c.add_streams(p1, ports = self.tx_port) @@ -280,9 +287,8 @@ class STLClient_Test(CStlGeneral_Test): assert self.tx_port in stats, '{0} - no stats for TX port'.format(profile) assert self.rx_port in stats, '{0} - no stats for RX port'.format(profile) - assert stats[self.tx_port]['opackets'] == stats[self.rx_port]['ipackets'], '{0} - number of TX packets differ from RX packets'.format(profile) - - assert stats[self.rx_port]['opackets'] == stats[self.tx_port]['ipackets'], '{0} - number of TX packets differ from RX packets'.format(profile) + self.verify(stats[self.tx_port]['opackets'], stats[self.rx_port]['ipackets']) + self.verify(stats[self.rx_port]['opackets'], stats[self.tx_port]['ipackets']) self.c.remove_all_streams(ports = [self.tx_port, self.rx_port]) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py index 1d89a599..11e80b9a 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py @@ -22,6 +22,7 @@ from .trex_stl_streams import * from .utils import parsing_opts from .trex_stl_client import STLClient from .utils import pcap +from trex_stl_lib.trex_stl_packet_builder_scapy import RawPcapReader, RawPcapWriter, hexdump from yaml import YAMLError @@ -291,13 +292,13 @@ class STLSim(object): return - print("Mering cores output to a single pcap file...\n") + if not self.silent: + print("Mering cores output to a single pcap file...\n") inputs = ["{0}-{1}".format(self.outfile, index) for index in range(0, self.dp_core_count)] pcap.merge_cap_files(inputs, self.outfile, delete_src = True) - def is_valid_file(filename): if not os.path.isfile(filename): raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename) @@ -421,6 +422,11 @@ def setParserOptions(): action = "store_true", default = False) + group.add_argument("--test_multi_core", + help = "runs the profile with c=1-8", + action = "store_true", + default = False) + return parser @@ -435,6 +441,110 @@ def validate_args (parser, options): parser.error("limit cannot be lower than number of DP cores") +# a more flexible check +def compare_caps (cap1, cap2, max_diff_sec = (5 * 1e-6)): + pkts1 = list(RawPcapReader(cap1)) + pkts2 = list(RawPcapReader(cap2)) + + if len(pkts1) != len(pkts2): + print('{0} contains {1} packets vs. {2} contains {3} packets'.format(cap1, len(pkts1), cap2, len(pkts2))) + return False + + # to be less strict we define equality if all packets from cap1 exists and in cap2 + # and vice versa + # 'exists' means the same packet with abs(TS1-TS2) < 5nsec + # its O(n^2) but who cares, right ? + for i, pkt1 in enumerate(pkts1): + ts1 = float(pkt1[1][0]) + (float(pkt1[1][1]) / 1e6) + found = None + for j, pkt2 in enumerate(pkts2): + ts2 = float(pkt2[1][0]) + (float(pkt2[1][1]) / 1e6) + + if abs(ts1-ts2) > max_diff_sec: + break + + if pkt1[0] == pkt2[0]: + found = j + break + + + if found is None: + print(format_text("cannot find packet #{0} from {1} in {2}\n".format(i, cap1, cap2), 'bold')) + return False + else: + del pkts2[found] + + return True + + + + +# a more strict comparsion 1 <--> 1 +def compare_caps_strict (cap1, cap2, max_diff_sec = (5 * 1e-6)): + pkts1 = list(RawPcapReader(cap1)) + pkts2 = list(RawPcapReader(cap2)) + + if len(pkts1) != len(pkts2): + print('{0} contains {1} packets vs. {1} contains {2} packets'.format(cap1, len(pkts1), cap2, len(pkts2))) + return False + + # a strict check + for pkt1, pkt2, i in zip(pkts1, pkts2, range(1, len(pkts1))): + ts1 = float(pkt1[1][0]) + (float(pkt1[1][1]) / 1e6) + ts2 = float(pkt2[1][0]) + (float(pkt2[1][1]) / 1e6) + + if abs(ts1-ts2) > 0.000005: # 5 nsec + print(format_text("TS error: cap files '{0}', '{1}' differ in cap #{2} - '{3}' vs. '{4}'\n".format(cap1, cap2, i, ts1, ts2), 'bold')) + return False + + if pkt1[0] != pkt2[0]: + print(format_text("RAW error: cap files '{0}', '{1}' differ in cap #{2}\n".format(cap1, cap2, i), 'bold')) + print(hexdump(pkt1[0])) + print("") + print(hexdump(pkt2[0])) + print("") + return False + + return True + +# +def test_multi_core (r, options): + + for core_count in [1, 2, 4, 6, 8]: + r.run(input_list = options.input_file, + outfile = '{0}.cap'.format(core_count), + dp_core_count = core_count, + is_debug = (not options.release), + pkt_limit = options.limit, + mult = options.mult, + duration = options.duration, + mode = 'none', + silent = True, + tunables = options.tunables) + + print("") + + print(format_text("comparing 2 cores to 1 core:\n", 'underline')) + rc = compare_caps('1.cap', '2.cap') + if rc: + print("[Passed]\n") + + print(format_text("comparing 4 cores to 1 core:\n", 'underline')) + rc = compare_caps('1.cap', '4.cap') + if rc: + print("[Passed]\n") + + print(format_text("comparing 6 cores to 1 core:\n", 'underline')) + rc = compare_caps('1.cap', '6.cap') + if rc: + print("[Passed]\n") + + print(format_text("comparing 8 cores to 1 core:\n", 'underline')) + rc = compare_caps('1.cap', '8.cap') + if rc: + print("[Passed]\n") + + def main (args = None): parser = setParserOptions() options = parser.parse_args(args = args) @@ -455,23 +565,28 @@ def main (args = None): mode = 'native' elif options.pkt: mode = 'pkt' + elif options.test_multi_core: + mode = 'test_multi_core' else: mode = 'none' try: r = STLSim(bp_sim_path = options.bp_sim_path, port_id = options.port_id) - r.run(input_list = options.input_file, - outfile = options.output_file, - dp_core_count = options.dp_core_count, - dp_core_index = options.dp_core_index, - is_debug = (not options.release), - pkt_limit = options.limit, - mult = options.mult, - duration = options.duration, - mode = mode, - silent = options.silent, - tunables = options.tunables) + if mode == 'test_multi_core': + test_multi_core(r, options) + else: + r.run(input_list = options.input_file, + outfile = options.output_file, + dp_core_count = options.dp_core_count, + dp_core_index = options.dp_core_index, + is_debug = (not options.release), + pkt_limit = options.limit, + mult = options.mult, + duration = options.duration, + mode = mode, + silent = options.silent, + tunables = options.tunables) except KeyboardInterrupt as e: print("\n\n*** Caught Ctrl + C... Exiting...\n\n") diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py index 3ce876ad..165942d8 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py @@ -361,6 +361,8 @@ class STLStream(object): int_mac_dst_override_mode = int(mac_dst_override_mode); + self.is_default_mac = not (int_mac_src_override_by_pkt or int_mac_dst_override_mode) + self.fields['flags'] = (int_mac_src_override_by_pkt&1) + ((int_mac_dst_override_mode&3)<<1) self.fields['action_count'] = action_count @@ -421,6 +423,10 @@ class STLStream(object): return self.id + def has_custom_mac_addr (self): + """ Return True if src or dst MAC were set as custom """ + return not self.is_default_mac + def get_name (self): """ Get the stream name """ return self.name @@ -835,6 +841,9 @@ class STLProfile(object): def is_pauseable (self): return all([x.get_mode() == "Continuous" for x in self.get_streams()]) + def has_custom_mac_addr (self): + return any([x.has_custom_mac_addr() for x in self.get_streams()]) + def has_flow_stats (self): return any([x.has_flow_stats() for x in self.get_streams()]) diff --git a/scripts/exp/imix.pcap b/scripts/exp/imix.pcap Binary files differindex aec8dac8..dff21f25 100644 --- a/scripts/exp/imix.pcap +++ b/scripts/exp/imix.pcap diff --git a/scripts/exp/imix_3pkt.pcap b/scripts/exp/imix_3pkt.pcap Binary files differindex 29e84d63..065a23fa 100644 --- a/scripts/exp/imix_3pkt.pcap +++ b/scripts/exp/imix_3pkt.pcap diff --git a/scripts/exp/imix_3pkt_vm.pcap b/scripts/exp/imix_3pkt_vm.pcap Binary files differindex 5af466d9..4a97280d 100644 --- a/scripts/exp/imix_3pkt_vm.pcap +++ b/scripts/exp/imix_3pkt_vm.pcap diff --git a/scripts/exp/pcap_with_vm.pcap b/scripts/exp/pcap_with_vm.pcap Binary files differindex b9476261..afc4bbef 100644 --- a/scripts/exp/pcap_with_vm.pcap +++ b/scripts/exp/pcap_with_vm.pcap diff --git a/scripts/exp/udp_1pkt_range_clients_split.pcap b/scripts/exp/udp_1pkt_range_clients_split.pcap Binary files differindex fc5572a8..fb5037cc 100644 --- a/scripts/exp/udp_1pkt_range_clients_split.pcap +++ b/scripts/exp/udp_1pkt_range_clients_split.pcap diff --git a/scripts/exp/udp_1pkt_simple_test.pcap b/scripts/exp/udp_1pkt_simple_test.pcap Binary files differindex 2eeec462..a1d3a2e0 100644 --- a/scripts/exp/udp_1pkt_simple_test.pcap +++ b/scripts/exp/udp_1pkt_simple_test.pcap diff --git a/scripts/exp/udp_1pkt_simple_test2.pcap b/scripts/exp/udp_1pkt_simple_test2.pcap Binary files differindex 002d77dc..2cf16a8f 100644 --- a/scripts/exp/udp_1pkt_simple_test2.pcap +++ b/scripts/exp/udp_1pkt_simple_test2.pcap diff --git a/scripts/exp/udp_1pkt_tuple_gen_split.pcap b/scripts/exp/udp_1pkt_tuple_gen_split.pcap Binary files differindex 08377c6d..873ab47f 100644 --- a/scripts/exp/udp_1pkt_tuple_gen_split.pcap +++ b/scripts/exp/udp_1pkt_tuple_gen_split.pcap diff --git a/scripts/stl/imix.py b/scripts/stl/imix.py index 65e35108..82edbfa5 100644 --- a/scripts/stl/imix.py +++ b/scripts/stl/imix.py @@ -8,8 +8,8 @@ class STLImix(object): def __init__ (self): # default IP range - self.ip_range = {'src': {'start': "10.0.0.1", 'end': "10.0.0.254"}, - 'dst': {'start': "8.0.0.1", 'end': "8.0.0.254"}} + self.ip_range = {'src': {'start': "16.0.0.1", 'end': "16.0.0.254"}, + 'dst': {'start': "48.0.0.1", 'end': "48.0.0.254"}} # default IMIX properties self.imix_table = [ {'size': 60, 'pps': 28, 'isg':0 }, diff --git a/scripts/stl/pcap_with_vm.py b/scripts/stl/pcap_with_vm.py index 7cf2906b..4e85bdf4 100644 --- a/scripts/stl/pcap_with_vm.py +++ b/scripts/stl/pcap_with_vm.py @@ -34,7 +34,7 @@ class STLPcap(object): ipg_usec = 10.0, loop_count = 5, ip_src_range = None, - ip_dst_range = {'start' : '10.0.0.1', 'end': '10.0.0.254'}, + ip_dst_range = {'start' : '16.0.0.1', 'end': '16.0.0.254'}, **kwargs): vm = self.create_vm(ip_src_range, ip_dst_range) diff --git a/scripts/stl/tests/many_streams.py b/scripts/stl/tests/many_streams.py new file mode 100644 index 00000000..a8713a26 --- /dev/null +++ b/scripts/stl/tests/many_streams.py @@ -0,0 +1,50 @@ +from trex_stl_lib.api import * + +class STLS1(object): + + def get_streams (self, direction = 0, **kwargs): + s1 = STLStream(name = 's1', + packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXSingleBurst(pps = 100, total_pkts = 7), + next = 's2' + + ) + s2 = STLStream(name = 's2', + self_start = False, + packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.2")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXSingleBurst(pps = 317, total_pkts = 13), + next = 's3' + ) + + + s3 = STLStream(name = 's3', + self_start = False, + packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.3")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXMultiBurst(pps = 57, pkts_per_burst = 3, count = 5, ibg = 12), + next = 's4' + ) + + s4 = STLStream(name = 's4', + self_start = False, + packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.3")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXSingleBurst(pps = 4, total_pkts = 22), + next = 's5' + ) + + s5 = STLStream(name = 's5', + self_start = False, + packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.3")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXSingleBurst(pps = 17, total_pkts = 27), + action_count = 17, + next = 's1' + ) + + return [ s1, s2, s3, s4, s5 ] + + +# dynamic load - used for trex console or simulator +def register(): + return STLS1() + + + diff --git a/scripts/stl/tests/multi_burst.py b/scripts/stl/tests/multi_burst.py new file mode 100644 index 00000000..68a239f5 --- /dev/null +++ b/scripts/stl/tests/multi_burst.py @@ -0,0 +1,17 @@ +from trex_stl_lib.api import * + +class STLS1(object): + + def get_streams (self, direction = 0, **kwargs): + s1 = STLStream(packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXMultiBurst(pkts_per_burst = 9, count = 2, ibg = 13)) + + return [s1] + + +# dynamic load - used for trex console or simulator +def register(): + return STLS1() + + + diff --git a/scripts/stl/tests/single_burst.py b/scripts/stl/tests/single_burst.py new file mode 100644 index 00000000..c46ebf87 --- /dev/null +++ b/scripts/stl/tests/single_burst.py @@ -0,0 +1,17 @@ +from trex_stl_lib.api import * + +class STLS1(object): + + def get_streams (self, direction = 0, **kwargs): + s1 = STLStream(packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXSingleBurst(total_pkts = 27)) + + return [s1] + + +# dynamic load - used for trex console or simulator +def register(): + return STLS1() + + + diff --git a/scripts/stl/tests/single_cont.py b/scripts/stl/tests/single_cont.py new file mode 100644 index 00000000..19563105 --- /dev/null +++ b/scripts/stl/tests/single_cont.py @@ -0,0 +1,17 @@ +from trex_stl_lib.api import * + +class STLS1(object): + + def get_streams (self, direction = 0, **kwargs): + s1 = STLStream(packet = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/(10*'x')), + mode = STLTXCont(pps = 2000)) + + return [s1] + + +# dynamic load - used for trex console or simulator +def register(): + return STLS1() + + + diff --git a/scripts/stl/udp_1pkt_simple_test.py b/scripts/stl/udp_1pkt_simple_test.py index 3915412d..5f08af9d 100644 --- a/scripts/stl/udp_1pkt_simple_test.py +++ b/scripts/stl/udp_1pkt_simple_test.py @@ -15,7 +15,7 @@ class STLS1(object): base_pkt_a = Ether()/IP(dst="48.0.0.1",options=IPOption(b'\x01\x01\x01\x00'))/UDP(dport=12,sport=1025) vm1 = STLScVmRaw([ - STLVmFlowVar(name="src",min_value="10.0.0.1",max_value="10.0.0.10",size=4,op="inc"), + STLVmFlowVar(name="src",min_value="16.0.0.1",max_value="16.0.0.10",size=4,op="inc"), STLVmWrFlowVar(fv_name="src",pkt_offset= "IP.src"), # checksum STLVmFixIpv4(offset = "IP") diff --git a/scripts/stl/udp_1pkt_simple_test2.py b/scripts/stl/udp_1pkt_simple_test2.py index 617d98b3..190e5439 100644 --- a/scripts/stl/udp_1pkt_simple_test2.py +++ b/scripts/stl/udp_1pkt_simple_test2.py @@ -15,7 +15,7 @@ class STLS1(object): base_pkt_a = Ether()/IP()/IPv6()/IP(dst="48.0.0.1",options=IPOption(b'\x01\x01\x01\x00'))/UDP(dport=12,sport=1025) vm1 = STLScVmRaw([ - STLVmFlowVar(name="src",min_value="10.0.0.1",max_value="10.0.0.10",size=4,op="inc"), + STLVmFlowVar(name="src",min_value="16.0.0.1",max_value="16.0.0.10",size=4,op="inc"), STLVmWrFlowVar(fv_name="src",pkt_offset= "IP:1.src"), # checksum STLVmFixIpv4(offset = "IP:1") diff --git a/src/bp_sim.cpp b/src/bp_sim.cpp index 0e87fbeb..2732548c 100755 --- a/src/bp_sim.cpp +++ b/src/bp_sim.cpp @@ -3263,6 +3263,7 @@ int CNodeGenerator::open_file(std::string file_name, m_v_if->set_review_mode(preview_mode); m_v_if->open_file(file_name); m_cnt = 0; + m_non_active = 0; m_limit = 0; return (0); } @@ -3277,10 +3278,13 @@ int CNodeGenerator::close_file(CFlowGenListPerThread * thread){ int CNodeGenerator::update_stl_stats(CGenNodeStateless *node_sl){ m_cnt++; - + if (!node_sl->is_node_active()) { + m_non_active++; + } #ifdef _DEBUG if ( m_preview_mode.getVMode() >2 ){ fprintf(stdout," %4lu ,", (ulong)m_cnt); + fprintf(stdout," %4lu ,", (ulong)m_non_active); node_sl->Dump(stdout); } #endif diff --git a/src/bp_sim.h b/src/bp_sim.h index 371015d4..1ec036c0 100755 --- a/src/bp_sim.h +++ b/src/bp_sim.h @@ -63,12 +63,6 @@ limitations under the License. #undef NAT_TRACE_ -static inline double -usec_to_sec(double usec) { - return (usec / (1000 * 1000)); -} - - #define FORCE_NO_INLINE __attribute__ ((noinline)) /* IP address, last 32-bits of IPv6 remaps IPv4 */ @@ -2033,6 +2027,7 @@ public: CFlowGenListPerThread * m_parent; CPreviewMode m_preview_mode; uint64_t m_cnt; + uint64_t m_non_active; uint64_t m_limit; CTimeHistogram m_realtime_his; }; diff --git a/src/flow_stat.cpp b/src/flow_stat.cpp index 13f8eb16..10b0c3ea 100644 --- a/src/flow_stat.cpp +++ b/src/flow_stat.cpp @@ -845,7 +845,8 @@ bool CFlowStatRuleMgr::dump_json(std::string & json, bool baseline) { if (user_id_info->need_to_send_rx(port) || baseline) { user_id_info->set_no_need_to_send_rx(port); data_section[str_user_id]["rx_pkts"][str_port] = Json::Value::UInt64(user_id_info->get_rx_counter(port).get_pkts()); - data_section[str_user_id]["rx_bytes"][str_port] = Json::Value::UInt64(user_id_info->get_rx_counter(port).get_bytes()); + if (m_capabilities & TrexPlatformApi::IF_STAT_RX_BYTES_COUNT) + data_section[str_user_id]["rx_bytes"][str_port] = Json::Value::UInt64(user_id_info->get_rx_counter(port).get_bytes()); send_empty = false; } if (user_id_info->need_to_send_tx(port) || baseline) { diff --git a/src/flow_stat_parser.cpp b/src/flow_stat_parser.cpp index 8cb41fb7..e83f8a51 100644 --- a/src/flow_stat_parser.cpp +++ b/src/flow_stat_parser.cpp @@ -19,8 +19,10 @@ limitations under the License. */ +#include <netinet/in.h> #include <common/basic_utils.h> #include <common/Network/Packet/IPHeader.h> +#include <common/Network/Packet/TcpHeader.h> #include <common/Network/Packet/IPv6Header.h> #include <common/Network/Packet/EthernetHeader.h> #include <flow_stat_parser.h> @@ -97,8 +99,54 @@ int CFlowStatParser::get_l4_proto(uint8_t &proto) { return 0; } +// calculate the payload len. Do not want to do this in parse(), since this is required only in +// specific cases, while parse is used in many places (including on packet RX path, where we want to bo as fast as possible) +int CFlowStatParser::get_payload_len(uint8_t *p, uint16_t len, uint16_t &payload_len) { + uint16_t l2_header_len; + uint16_t l3_header_len; + uint16_t l4_header_len; + uint8_t *p_l4 = NULL; + TCPHeader *p_tcp = NULL; + if (!m_ipv4) { + payload_len = 0; + return -1; + } + + l2_header_len = ((uint8_t *)m_ipv4) - p; + l3_header_len = m_ipv4->getHeaderLength(); + switch (m_ipv4->getProtocol()) { + case IPPROTO_UDP: + l4_header_len = 8; + break; + case IPPROTO_TCP: + p_l4 = ((uint8_t *)m_ipv4) + l3_header_len; + if ((p_l4 + TCP_HEADER_LEN) > (p + len)) { + //Not enough space for TCP header + payload_len = 0; + return -1; + } + p_tcp = (TCPHeader *)p_l4; + l4_header_len = p_tcp->getHeaderLength(); + break; + case IPPROTO_ICMP: + l4_header_len = 8; + break; + default: + l4_header_len = 0; + } + + if (len < l2_header_len + l3_header_len + l4_header_len) { + payload_len = 0; + return -1; + } + + payload_len = len - l2_header_len - l3_header_len - l4_header_len; + + return 0; +} + static const uint16_t TEST_IP_ID = 0xabcd; -static const uint8_t TEST_L4_PROTO = 0x11; +static const uint8_t TEST_L4_PROTO = IPPROTO_UDP; int CFlowStatParser::test() { uint16_t ip_id = 0; @@ -115,6 +163,13 @@ int CFlowStatParser::test() { 0xff, TEST_L4_PROTO, 0xbd,0x04, 0x10,0x0,0x0,0x1, 0x30,0x0,0x0,0x1, + // TCP heaader + 0xab, 0xcd, 0x00, 0x80, // src, dst ports + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // seq num, ack num + 0x50, 0x00, 0xff, 0xff, // Header size, flags, window size + 0x00, 0x00, 0x00, 0x00, // checksum ,urgent pointer + // some extra bytes + 0x1, 0x2, 0x3, 0x4 }; // good packet @@ -130,9 +185,36 @@ int CFlowStatParser::test() { assert(l4_proto == TEST_L4_PROTO); assert(m_stat_supported == true); + // payload len test + uint16_t payload_len; + int ret; + ret = get_payload_len(test_pkt, sizeof(test_pkt), payload_len); + // UDP packet. + assert(ret == 0); + assert(payload_len == 16); + reset(); + // ICMP packet + test_pkt[27] = IPPROTO_ICMP; + assert (parse(test_pkt, sizeof(test_pkt)) == 0); + ret = get_payload_len(test_pkt, sizeof(test_pkt), payload_len); + assert(ret == 0); + assert(payload_len == 16); + // TCP packet + test_pkt[27] = IPPROTO_TCP; + assert (parse(test_pkt, sizeof(test_pkt)) == 0); + ret = get_payload_len(test_pkt, sizeof(test_pkt), payload_len); + assert(ret == 0); + assert(payload_len == 4); + // Other protocol + test_pkt[27] = 0xaa; + assert (parse(test_pkt, sizeof(test_pkt)) == 0); + ret = get_payload_len(test_pkt, sizeof(test_pkt), payload_len); + assert(ret == 0); + assert(payload_len == 24); + reset(); - // bad packet + // bad packet. change eth protocol test_pkt[16] = 0xaa; assert (parse(test_pkt, sizeof(test_pkt)) == -1); assert(m_stat_supported == false); diff --git a/src/flow_stat_parser.h b/src/flow_stat_parser.h index 8c9e1418..0c0655ee 100644 --- a/src/flow_stat_parser.h +++ b/src/flow_stat_parser.h @@ -34,6 +34,7 @@ class CFlowStatParser { virtual int get_ip_id(uint16_t &ip_id); virtual int set_ip_id(uint16_t ip_id); virtual int get_l4_proto(uint8_t &proto); + virtual int get_payload_len(uint8_t *p, uint16_t len, uint16_t &payload_len); virtual int test(); protected: diff --git a/src/internal_api/trex_platform_api.h b/src/internal_api/trex_platform_api.h index 90eaa7c7..b8f40df2 100644 --- a/src/internal_api/trex_platform_api.h +++ b/src/internal_api/trex_platform_api.h @@ -107,6 +107,7 @@ public: IF_STAT_IPV4_ID = 1, IF_STAT_PAYLOAD = 2, IF_STAT_IPV6_FLOW_LABEL = 4, + IF_STAT_RX_BYTES_COUNT = 8, // Card support counting rx bytes }; enum driver_speed_e { diff --git a/src/main_dpdk.cpp b/src/main_dpdk.cpp index 78aee6ab..a9799540 100644 --- a/src/main_dpdk.cpp +++ b/src/main_dpdk.cpp @@ -191,7 +191,7 @@ public: virtual void clear_extended_stats(CPhyEthIF * _if); virtual int dump_fdir_global_stats(CPhyEthIF * _if, FILE *fd) {return 0;} virtual int get_stat_counters_num() {return MAX_FLOW_STATS;} - virtual int get_rx_stat_capabilities() {return TrexPlatformApi::IF_STAT_IPV4_ID;} + virtual int get_rx_stat_capabilities() {return TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT;} virtual int wait_for_stable_link(); virtual void wait_after_link_up(); }; @@ -244,7 +244,7 @@ public: virtual int wait_for_stable_link(); virtual int get_stat_counters_num() {return MAX_FLOW_STATS;} - virtual int get_rx_stat_capabilities() {return TrexPlatformApi::IF_STAT_IPV4_ID;} + virtual int get_rx_stat_capabilities() {return TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT;} }; @@ -281,7 +281,7 @@ public: virtual void clear_extended_stats(CPhyEthIF * _if); virtual int wait_for_stable_link(); virtual int get_stat_counters_num() {return MAX_FLOW_STATS;} - virtual int get_rx_stat_capabilities() {return TrexPlatformApi::IF_STAT_IPV4_ID;} + virtual int get_rx_stat_capabilities() {return TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT;} virtual CFlowStatParser *get_flow_stat_parser(); }; @@ -5139,11 +5139,12 @@ int CTRexExtendedDriverBase40G::configure_rx_filter_rules(CPhyEthIF * _if) { int CTRexExtendedDriverBase40G::reset_rx_stats(CPhyEthIF * _if, uint32_t *stats) { uint32_t diff_stats[MAX_FLOW_STATS]; + uint32_t diff_bytes[MAX_FLOW_STATS]; // The HW counters start from some random values. The driver give us the diffs from previous, // each time we do get_rx_stats. We need to make one first call, at system startup, // and ignore the returned diffs - return get_rx_stats(_if, diff_stats, stats, NULL, NULL, 0, MAX_FLOW_STATS - 1); + return get_rx_stats(_if, diff_stats, stats, diff_bytes, NULL, 0, MAX_FLOW_STATS - 1); } // instead of adding this to rte_ethdev.h @@ -5170,6 +5171,7 @@ int CTRexExtendedDriverBase40G::get_rx_stats(CPhyEthIF * _if, uint32_t *pkts, ui pkts[i] = (uint64_t)((hw_stats[i - min] + ((uint64_t)1 << 32)) - prev_pkts[i]); } prev_pkts[i] = hw_stats[i - min]; + bytes[i] = 0; } return 0; diff --git a/src/os_time.h b/src/os_time.h index 0e732abf..42be576f 100755 --- a/src/os_time.h +++ b/src/os_time.h @@ -31,6 +31,11 @@ typedef double dsec_t; //time in sec double uint32_t os_get_time_msec(); uint32_t os_get_time_freq(); +static inline double +usec_to_sec(double usec) { + return (usec / (1000 * 1000)); +} + #ifdef LINUX diff --git a/src/sim/trex_sim.h b/src/sim/trex_sim.h index 5aeeb226..0c343261 100644 --- a/src/sim/trex_sim.h +++ b/src/sim/trex_sim.h @@ -26,12 +26,14 @@ limitations under the License. #include <bp_sim.h> #include <json/json.h> #include <trex_stateless.h> +#include <vector> int gtest_main(int argc, char **argv); class TrexStateless; class TrexPublisher; class DpToCpHandler; +class DPCoreStats; void set_stateless_obj(TrexStateless *obj); @@ -146,8 +148,8 @@ private: void run_dp_core(int core_index, const std::string &out_filename, - uint64_t &simulated_pkts, - uint64_t &written_pkts); + std::vector<DPCoreStats> &stats, + DPCoreStats &total); void cleanup(); void flush_dp_to_cp_messages_core(int core_index); diff --git a/src/sim/trex_sim_stateless.cpp b/src/sim/trex_sim_stateless.cpp index fa13401d..acbeef69 100644 --- a/src/sim/trex_sim_stateless.cpp +++ b/src/sim/trex_sim_stateless.cpp @@ -30,6 +30,23 @@ limitations under the License. using namespace std; +class DPCoreStats { +public: + DPCoreStats() { + m_simulated_pkts = 0; + m_non_active_pkts = 0; + m_written_pkts = 0; + } + + uint64_t get_on_wire_count() { + return (m_simulated_pkts - m_non_active_pkts); + } + + uint64_t m_simulated_pkts; + uint64_t m_non_active_pkts; + uint64_t m_written_pkts; +}; + /****** utils ******/ static string format_num(double num, const string &suffix = "") { const char x[] = {' ','K','M','G','T','P'}; @@ -330,21 +347,21 @@ SimStateless::show_intro(const std::string &out_filename) { void SimStateless::run_dp(const std::string &out_filename) { - uint64_t simulated_pkts_cnt = 0; - uint64_t written_pkts_cnt = 0; + std::vector<DPCoreStats> core_stats(m_dp_core_count); + DPCoreStats total; - show_intro(out_filename); + show_intro(out_filename); if (is_multiple_capture()) { for (int i = 0; i < m_dp_core_count; i++) { std::stringstream ss; ss << out_filename << "-" << i; - run_dp_core(i, ss.str(), simulated_pkts_cnt, written_pkts_cnt); + run_dp_core(i, ss.str(), core_stats, total); } } else { for (int i = 0; i < m_dp_core_count; i++) { - run_dp_core(i, out_filename, simulated_pkts_cnt, written_pkts_cnt); + run_dp_core(i, out_filename, core_stats, total); } } @@ -354,12 +371,25 @@ SimStateless::run_dp(const std::string &out_filename) { std::cout << "\n\nSimulation summary:\n"; std::cout << "-------------------\n\n"; - std::cout << "simulated " << simulated_pkts_cnt << " packets\n"; + + for (int i = 0; i < m_dp_core_count; i++) { + std::cout << "core index " << i << "\n"; + std::cout << "-----------------\n\n"; + std::cout << " simulated packets : " << core_stats[i].m_simulated_pkts << "\n"; + std::cout << " non active packets : " << core_stats[i].m_non_active_pkts << "\n"; + std::cout << " on-wire packets : " << core_stats[i].get_on_wire_count() << "\n\n"; + } + + std::cout << "Total:" << "\n"; + std::cout << "-----------------\n\n"; + std::cout << " simulated packets : " << total.m_simulated_pkts << "\n"; + std::cout << " non active packets : " << total.m_non_active_pkts << "\n"; + std::cout << " on-wire packets : " << total.get_on_wire_count() << "\n\n"; if (m_is_dry_run) { std::cout << "*DRY RUN* - no packets were written\n"; } else { - std::cout << "written " << written_pkts_cnt << " packets " << "to '" << out_filename << "'\n\n"; + std::cout << "written " << total.m_written_pkts << " packets " << "to '" << out_filename << "'\n\n"; } std::cout << "\n"; @@ -395,8 +425,8 @@ SimStateless::get_limit_per_core(int core_index) { void SimStateless::run_dp_core(int core_index, const std::string &out_filename, - uint64_t &simulated_pkts, - uint64_t &written_pkts) { + std::vector<DPCoreStats> &stats, + DPCoreStats &total) { CFlowGenListPerThread *lpt = m_fl.m_threads_info[core_index]; @@ -406,10 +436,17 @@ SimStateless::run_dp_core(int core_index, flush_dp_to_cp_messages_core(core_index); - simulated_pkts += lpt->m_node_gen.m_cnt; + /* core */ + stats[core_index].m_simulated_pkts = lpt->m_node_gen.m_cnt; + stats[core_index].m_non_active_pkts = lpt->m_node_gen.m_non_active; + + /* total */ + total.m_simulated_pkts += lpt->m_node_gen.m_cnt; + total.m_non_active_pkts += lpt->m_node_gen.m_non_active; if (should_capture_core(core_index)) { - written_pkts += lpt->m_node_gen.m_cnt; + stats[core_index].m_written_pkts = (lpt->m_node_gen.m_cnt - lpt->m_node_gen.m_non_active); + total.m_written_pkts += (lpt->m_node_gen.m_cnt - lpt->m_node_gen.m_non_active); } } diff --git a/src/stateless/cp/trex_exception.h b/src/stateless/cp/trex_exception.h index b9e20761..e184fa30 100644 --- a/src/stateless/cp/trex_exception.h +++ b/src/stateless/cp/trex_exception.h @@ -1,22 +1,22 @@ /* - Itay Marom - Cisco Systems, Inc. + Itay Marom + Cisco Systems, Inc. */ /* -Copyright (c) 2015-2015 Cisco Systems, Inc. + Copyright (c) 2015-2016 Cisco Systems, 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 + 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 + 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. + 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. */ #ifndef __TREX_EXCEPTION_H__ #define __TREX_EXCEPTION_H__ @@ -28,14 +28,46 @@ limitations under the License. * generic exception for errors * TODO: move this to a better place */ -class TrexException : public std::runtime_error + +class TrexException : public std::runtime_error { -public: - TrexException() : std::runtime_error("") { + public: + enum TrexExceptionTypes_t { + T_FLOW_STAT_PG_ID_DIFF_L4, + T_FLOW_STAT_ADD_FAIL, + T_FLOW_STAT_DEL_NON_EXIST, + T_FLOW_STAT_ASSOC_NON_EXIST_ID, + T_FLOW_STAT_ASSOC_OCC_ID, + T_FLOW_STAT_NON_EXIST_ID, + T_FLOW_STAT_BAD_PKT_FORMAT, + T_FLOW_STAT_UNSUPP_PKT_FORMAT, + T_FLOW_STAT_BAD_RULE_TYPE, + T_FLOW_STAT_BAD_RULE_TYPE_FOR_IF, + T_FLOW_STAT_FAILED_FIND_L4, + T_FLOW_STAT_PAYLOAD_TOO_SHORT, + T_FLOW_STAT_NO_STREAMS_EXIST, + T_FLOW_STAT_ALREADY_STARTED, + T_FLOW_STAT_ALREADY_EXIST, + T_FLOW_STAT_FAILED_CHANGE_IP_ID, + T_FLOW_STAT_NO_FREE_HW_ID, + T_FLOW_STAT_RX_CORE_START_FAIL, + T_FLOW_STAT_BAD_HW_ID, + T_INVALID + }; + + TrexException() : std::runtime_error(""), m_type(T_INVALID) { + } + TrexException(enum TrexExceptionTypes_t type=T_INVALID) : std::runtime_error(""), m_type(type) { } - TrexException(const std::string &what) : std::runtime_error(what) { + + TrexException(const std::string &what, enum TrexExceptionTypes_t type=T_INVALID) : std::runtime_error(what), m_type(type) { } + + enum TrexExceptionTypes_t type() {return m_type;} + + private: + enum TrexExceptionTypes_t m_type; }; #endif /* __TREX_EXCEPTION_H__ */ diff --git a/src/stateless/cp/trex_stream.cpp b/src/stateless/cp/trex_stream.cpp index e3f0ba7c..4325858c 100644 --- a/src/stateless/cp/trex_stream.cpp +++ b/src/stateless/cp/trex_stream.cpp @@ -129,11 +129,14 @@ TrexStream::TrexStream(uint8_t type, uint8_t port_id, uint32_t stream_id) : m_port_id(port_id), m_stream_id(stream_id) , m_rate(*this) { /* default values */ - m_type = type; - m_isg_usec = 0; - m_next_stream_id = -1; - m_enabled = false; - m_self_start = false; + m_type = type; + m_isg_usec = 0; + m_next_stream_id = -1; + m_enabled = false; + m_self_start = false; + + m_mc_phase_pre_sec = 0; + m_mc_phase_post_sec = 0; m_pkt.binary = NULL; m_pkt.len = 0; @@ -148,6 +151,7 @@ TrexStream::TrexStream(uint8_t type, m_vm_dp = NULL; m_flags=0; m_action_count=0; + m_null_stream = false; } TrexStream::~TrexStream() { diff --git a/src/stateless/cp/trex_stream.h b/src/stateless/cp/trex_stream.h index ded6363e..c5bfdb98 100644 --- a/src/stateless/cp/trex_stream.h +++ b/src/stateless/cp/trex_stream.h @@ -22,6 +22,9 @@ limitations under the License. #ifndef __TREX_STREAM_H__ #define __TREX_STREAM_H__ +#include <stdio.h> +#include <string.h> + #include <unordered_map> #include <vector> #include <stdint.h> @@ -29,9 +32,8 @@ limitations under the License. #include <json/json.h> -#include <trex_stream_vm.h> -#include <stdio.h> -#include <string.h> +#include "os_time.h" +#include "trex_stream_vm.h" #include <common/captureFile.h> #include <common/bitMan.h> @@ -356,6 +358,10 @@ public: m_type = type; } + void set_null_stream(bool enable) { + m_null_stream = enable; + } + uint8_t get_type(void) const { return ( m_type ); } @@ -393,6 +399,7 @@ public: set_multi_burst(burst_total_pkts,1,0.0); } + /* create new stream */ TrexStream * clone(bool full = false) const { @@ -413,8 +420,13 @@ public: dp->m_vm_dp = NULL; } - dp->m_isg_usec = m_isg_usec; - dp->m_next_stream_id = m_next_stream_id; + dp->m_isg_usec = m_isg_usec; + + /* multi core phase paramters */ + dp->m_mc_phase_pre_sec = m_mc_phase_pre_sec; + dp->m_mc_phase_post_sec = m_mc_phase_post_sec; + + dp->m_next_stream_id = m_next_stream_id; dp->m_enabled = m_enabled; dp->m_self_start = m_self_start; @@ -448,7 +460,25 @@ public: return ( (m_burst_total_pkts / get_pps()) * 1000 * 1000); } + double get_ipg_sec() { + return (1.0 / get_pps()); + } + /* return the delay before starting a stream */ + inline double get_start_delay_sec() { + return usec_to_sec(m_isg_usec) + m_mc_phase_pre_sec; + } + + /* return the delay before starting the next stream */ + inline double get_next_stream_delay_sec() { + return m_mc_phase_post_sec; + } + + /* return the delay between scheduling a new burst in a multi burst stream */ + inline double get_next_burst_delay_sec() { + return usec_to_sec(m_ibg_usec) + m_mc_phase_post_sec + m_mc_phase_pre_sec; + } + void Dump(FILE *fd); StreamVmDp * getDpVm(){ @@ -490,6 +520,9 @@ public: /* config fields */ + double m_mc_phase_pre_sec; + double m_mc_phase_post_sec; + double m_isg_usec; int m_next_stream_id; @@ -497,6 +530,8 @@ public: bool m_enabled; bool m_self_start; + /* null stream (a dummy stream) */ + bool m_null_stream; /* VM CP and DP */ StreamVm m_vm; diff --git a/src/stateless/cp/trex_streams_compiler.cpp b/src/stateless/cp/trex_streams_compiler.cpp index d6971d68..d1cffbb7 100644 --- a/src/stateless/cp/trex_streams_compiler.cpp +++ b/src/stateless/cp/trex_streams_compiler.cpp @@ -379,6 +379,8 @@ TrexStreamsCompiler::compile(uint8_t port_id, double factor, std::string *fail_msg) { + assert(dp_core_count > 0); + try { return compile_internal(port_id,streams,objs,dp_core_count,factor,fail_msg); } catch (const TrexException &ex) { @@ -415,12 +417,87 @@ TrexStreamsCompiler::compile_internal(uint8_t por /* check if all are cont. streams */ bool all_continues = true; + int non_splitable_count = 0; for (const auto stream : streams) { if (stream->get_type() != TrexStream::stCONTINUOUS) { all_continues = false; - break; } + if (!stream->is_splitable(dp_core_count)) { + non_splitable_count++; + } + } + + /* if all streams are not splitable - all should go to core 0 */ + if (non_splitable_count == streams.size()) { + compile_on_single_core(port_id, + streams, + objs, + dp_core_count, + factor, + nodes, + all_continues); + } else { + compile_on_all_cores(port_id, + streams, + objs, + dp_core_count, + factor, + nodes, + all_continues); + } + + return true; + +} + +/** + * compile a list of streams on a single core (pinned to core 0) + * + */ +void +TrexStreamsCompiler::compile_on_single_core(uint8_t port_id, + const std::vector<TrexStream *> &streams, + std::vector<TrexStreamsCompiledObj *> &objs, + uint8_t dp_core_count, + double factor, + GraphNodeMap &nodes, + bool all_continues) { + + /* allocate object only for core 0 */ + TrexStreamsCompiledObj *obj = new TrexStreamsCompiledObj(port_id); + obj->m_all_continues = all_continues; + objs.push_back(obj); + + /* put NULL for the rest */ + for (uint8_t i = 1; i < dp_core_count; i++) { + objs.push_back(NULL); + } + + /* compile all the streams */ + for (auto stream : streams) { + + /* skip non-enabled streams */ + if (!stream->m_enabled) { + continue; + } + + /* compile the stream for only one core */ + compile_stream(stream, factor, 1, objs, nodes); } +} + +/** + * compile a list of streams on all DP cores + * + */ +void +TrexStreamsCompiler::compile_on_all_cores(uint8_t port_id, + const std::vector<TrexStream *> &streams, + std::vector<TrexStreamsCompiledObj *> &objs, + uint8_t dp_core_count, + double factor, + GraphNodeMap &nodes, + bool all_continues) { /* allocate objects for all DP cores */ for (uint8_t i = 0; i < dp_core_count; i++) { @@ -441,15 +518,6 @@ TrexStreamsCompiler::compile_internal(uint8_t por compile_stream(stream, factor, dp_core_count, objs, nodes); } - /* some objects might be empty - no streams were assigned */ - for (uint8_t i = 0; i < dp_core_count; i++) { - if (objs[i]->is_empty()) { - delete objs[i]; - objs[i] = NULL; - } - } - - return true; } /** @@ -482,16 +550,17 @@ TrexStreamsCompiler::compile_stream(TrexStream *stream, // change the packet kept in the stream). We want the state to be saved in the original stream. get_stateless_obj()->m_rx_flow_stat.copy_state(fixed_rx_flow_stat_stream, stream); + fixed_rx_flow_stat_stream->update_rate_factor(factor); + /* can this stream be split to many cores ? */ - if (!stream->is_splitable(dp_core_count)) { + if ( (dp_core_count == 1) || (!stream->is_splitable(dp_core_count)) ) { compile_stream_on_single_core(fixed_rx_flow_stat_stream, - factor, - objs[0], + dp_core_count, + objs, new_id, new_next_id); } else { compile_stream_on_all_cores(fixed_rx_flow_stat_stream, - factor, dp_core_count, objs, new_id, @@ -507,7 +576,6 @@ TrexStreamsCompiler::compile_stream(TrexStream *stream, */ void TrexStreamsCompiler::compile_stream_on_all_cores(TrexStream *stream, - double factor, uint8_t dp_core_count, std::vector<TrexStreamsCompiledObj *> &objs, int new_id, @@ -515,8 +583,13 @@ TrexStreamsCompiler::compile_stream_on_all_cores(TrexStream *stream, std::vector<TrexStream *> core_streams(dp_core_count); - double per_core_factor = (factor / dp_core_count); int per_core_burst_total_pkts = (stream->m_burst_total_pkts / dp_core_count); + const int burst_remainder = (stream->m_burst_total_pkts % dp_core_count); + int remainder_left = burst_remainder; + + /* this is the stream base IPG (pre split) */ + double base_ipg_sec = stream->get_ipg_sec(); + /* for each core - creates its own version of the stream */ for (uint8_t i = 0; i < dp_core_count; i++) { @@ -525,18 +598,31 @@ TrexStreamsCompiler::compile_stream_on_all_cores(TrexStream *stream, /* fix stream ID */ dp_stream->fix_dp_stream_id(new_id, new_next_id); + /* some phase is added to avoid all the cores TXing at once */ + dp_stream->m_mc_phase_pre_sec = base_ipg_sec * i; - /* adjust rate and packets count */ - dp_stream->update_rate_factor(per_core_factor); + + /* each core gets a share of the packets */ dp_stream->m_burst_total_pkts = per_core_burst_total_pkts; + + /* rate is slower * dp_core_count */ + dp_stream->update_rate_factor(1.0 / dp_core_count); + + + if (remainder_left > 0) { + dp_stream->m_burst_total_pkts++; + remainder_left--; + /* this core needs to wait to the rest of the cores that will participate in the last round */ + dp_stream->m_mc_phase_post_sec = base_ipg_sec * remainder_left; + } else { + /* this core did not participate in the last round so it will wait its current round's left + burst_remainder */ + dp_stream->m_mc_phase_post_sec = base_ipg_sec * (dp_core_count - 1 - i + burst_remainder); + } + core_streams[i] = dp_stream; } - /* take care of remainder from a burst on core 0 */ - int burst_remainder = stream->m_burst_total_pkts - (per_core_burst_total_pkts * dp_core_count); - core_streams[0]->m_burst_total_pkts += burst_remainder; - /* handle VM (split if needed) */ TrexVmSplitter vm_splitter; vm_splitter.split( (TrexStream *)stream, core_streams); @@ -554,8 +640,8 @@ TrexStreamsCompiler::compile_stream_on_all_cores(TrexStream *stream, */ void TrexStreamsCompiler::compile_stream_on_single_core(TrexStream *stream, - double factor, - TrexStreamsCompiledObj *obj, + uint8_t dp_core_count, + std::vector<TrexStreamsCompiledObj *> &objs, int new_id, int new_next_id) { @@ -570,8 +656,21 @@ TrexStreamsCompiler::compile_stream_on_single_core(TrexStream *stream, dp_stream->m_vm_dp = stream->m_vm_dp->clone(); } - /* update the core */ - obj->add_compiled_stream(dp_stream); + /* update core 0 with the real stream */ + objs[0]->add_compiled_stream(dp_stream); + + + /* create dummy streams for the other cores */ + for (uint8_t i = 1; i < dp_core_count; i++) { + TrexStream *null_dp_stream = stream->clone(); + + /* fix stream ID */ + null_dp_stream->fix_dp_stream_id(new_id, new_next_id); + + /* mark as null stream and add */ + null_dp_stream->set_null_stream(true); + objs[i]->add_compiled_stream(null_dp_stream); + } } /************************************** diff --git a/src/stateless/cp/trex_streams_compiler.h b/src/stateless/cp/trex_streams_compiler.h index b8b0be37..23b06d06 100644 --- a/src/stateless/cp/trex_streams_compiler.h +++ b/src/stateless/cp/trex_streams_compiler.h @@ -123,6 +123,23 @@ private: void add_warning(const std::string &warning); void err(const std::string &err); + void compile_on_single_core(uint8_t port_id, + const std::vector<TrexStream *> &streams, + std::vector<TrexStreamsCompiledObj *> &objs, + uint8_t dp_core_count, + double factor, + GraphNodeMap &nodes, + bool all_continues); + + void compile_on_all_cores(uint8_t port_id, + const std::vector<TrexStream *> &streams, + std::vector<TrexStreamsCompiledObj *> &objs, + uint8_t dp_core_count, + double factor, + GraphNodeMap &nodes, + bool all_continues); + + void compile_stream(TrexStream *stream, double factor, uint8_t dp_core_count, @@ -130,13 +147,12 @@ private: GraphNodeMap &nodes); void compile_stream_on_single_core(TrexStream *stream, - double factor, - TrexStreamsCompiledObj *obj, + uint8_t dp_core_count, + std::vector<TrexStreamsCompiledObj *> &objs, int new_id, int new_next_id); void compile_stream_on_all_cores(TrexStream *stream, - double factor, uint8_t dp_core_count, std::vector<TrexStreamsCompiledObj *> &objs, int new_id, diff --git a/src/stateless/dp/trex_stateless_dp_core.cpp b/src/stateless/dp/trex_stateless_dp_core.cpp index f125a46a..d3d49a34 100644 --- a/src/stateless/dp/trex_stateless_dp_core.cpp +++ b/src/stateless/dp/trex_stateless_dp_core.cpp @@ -602,7 +602,7 @@ TrexStatelessDpCore::add_stream(TrexStatelessDpPerPort * lp_port, node->m_state =CGenNodeStateless::ss_INACTIVE; } - node->m_time = m_core->m_cur_time_sec + usec_to_sec(stream->m_isg_usec); + node->m_time = m_core->m_cur_time_sec + stream->get_start_delay_sec(); pkt_dir_t dir = m_core->m_node_gen.m_v_if->port_id_to_dir(stream->m_port_id); node->m_flags = 0; @@ -627,6 +627,7 @@ TrexStatelessDpCore::add_stream(TrexStatelessDpPerPort * lp_port, node->m_pause =0; node->m_stream_type = stream->m_type; node->m_next_time_offset = 1.0 / stream->get_pps(); + node->m_null_stream = (stream->m_null_stream ? 1 : 0); /* stateless specific fields */ switch ( stream->m_type ) { diff --git a/src/stateless/dp/trex_stream_node.h b/src/stateless/dp/trex_stream_node.h index 104e4d3b..c85bf8b5 100644 --- a/src/stateless/dp/trex_stream_node.h +++ b/src/stateless/dp/trex_stream_node.h @@ -84,7 +84,7 @@ private: double m_next_time_offset; /* in sec */ uint16_t m_action_counter; uint8_t m_stat_hw_id; // hw id used to count rx and tx stats - uint8_t m_pad11; + uint8_t m_null_stream; uint32_t m_pad12; stream_state_t m_state; @@ -144,7 +144,7 @@ public: /* we restart the stream, schedule it using stream isg */ inline void update_refresh_time(double cur_time){ - m_time = cur_time + usec_to_sec(m_ref_stream_info->m_isg_usec); + m_time = cur_time + usec_to_sec(m_ref_stream_info->m_isg_usec) + m_ref_stream_info->m_mc_phase_pre_sec; } inline bool is_mask_for_free(){ @@ -170,6 +170,11 @@ public: } } + bool is_node_active() { + /* bitwise or - faster instead of two IFs */ + return ((m_pause | m_null_stream) == 0); + } + inline uint8_t get_stream_type(){ return (m_stream_type); } @@ -199,7 +204,7 @@ public: inline void handle_continues(CFlowGenListPerThread *thread) { - if (unlikely (is_pause()==false)) { + if (likely (is_node_active())) { thread->m_node_gen.m_v_if->send_node( (CGenNode *)this); } @@ -211,7 +216,9 @@ public: } inline void handle_multi_burst(CFlowGenListPerThread *thread) { - thread->m_node_gen.m_v_if->send_node( (CGenNode *)this); + if (likely (is_node_active())) { + thread->m_node_gen.m_v_if->send_node( (CGenNode *)this); + } m_single_burst--; if (m_single_burst > 0 ) { @@ -224,8 +231,8 @@ public: if ( m_multi_bursts == 0 ) { set_state(CGenNodeStateless::ss_INACTIVE); if ( thread->set_stateless_next_node(this,m_next_stream) ){ - /* update the next stream time using isg */ - m_next_stream->update_refresh_time(m_time); + /* update the next stream time using isg and post phase */ + m_next_stream->update_refresh_time(m_time + m_ref_stream_info->get_next_stream_delay_sec()); thread->m_node_gen.m_p_queue.push( (CGenNode *)m_next_stream); }else{ @@ -234,7 +241,8 @@ public: } }else{ - m_time += get_multi_ibg_sec(); + /* next burst is like starting a new stream - add pre and post phase */ + m_time += m_ref_stream_info->get_next_burst_delay_sec(); m_single_burst = m_single_burst_refill; thread->m_node_gen.m_p_queue.push( (CGenNode *)this); } diff --git a/src/stateless/rx/trex_stateless_rx_core.cpp b/src/stateless/rx/trex_stateless_rx_core.cpp index 26f537f8..2132fe9f 100644 --- a/src/stateless/rx/trex_stateless_rx_core.cpp +++ b/src/stateless/rx/trex_stateless_rx_core.cpp @@ -120,6 +120,7 @@ void CRxCoreStateless::handle_rx_pkt(CLatencyManagerPerPort *lp, rte_mbuf_t *m) } } } + rte_pktmbuf_free(m); } // In VM setup, handle packets coming as messages from DP cores. |