diff options
-rwxr-xr-x | doc/trex_stateless.asciidoc | 2 | ||||
-rw-r--r-- | scripts/automation/regression/stateless_tests/stl_rx_test.py | 389 | ||||
-rwxr-xr-x | src/bp_sim.cpp | 4 | ||||
-rwxr-xr-x | src/bp_sim.h | 10 | ||||
-rw-r--r-- | src/debug.cpp | 2 | ||||
-rw-r--r-- | src/flow_stat.cpp | 84 | ||||
-rw-r--r-- | src/flow_stat.h | 10 | ||||
-rw-r--r-- | src/flow_stat_parser.cpp | 29 | ||||
-rw-r--r-- | src/flow_stat_parser.h | 6 | ||||
-rw-r--r-- | src/internal_api/trex_platform_api.h | 9 | ||||
-rw-r--r-- | src/main_dpdk.cpp | 119 | ||||
-rw-r--r-- | src/pkt_gen.cpp | 4 | ||||
-rw-r--r-- | src/rpc-server/commands/trex_rpc_cmd_stream.cpp | 2 | ||||
-rw-r--r-- | src/stateless/cp/trex_exception.h | 1 | ||||
-rw-r--r-- | src/stateless/cp/trex_stateless_port.cpp | 3 | ||||
-rw-r--r-- | src/stateless/dp/trex_stateless_dp_core.cpp | 2 | ||||
-rw-r--r-- | src/stateless/dp/trex_stream_node.h | 8 | ||||
-rw-r--r-- | src/stateless/rx/trex_stateless_rx_port_mngr.cpp | 16 | ||||
-rw-r--r-- | src/stateless/rx/trex_stateless_rx_port_mngr.h | 10 | ||||
-rw-r--r-- | src/trex_defs.h | 6 |
20 files changed, 487 insertions, 229 deletions
diff --git a/doc/trex_stateless.asciidoc b/doc/trex_stateless.asciidoc index 1ca07a02..1a5c62b7 100755 --- a/doc/trex_stateless.asciidoc +++ b/doc/trex_stateless.asciidoc @@ -2839,7 +2839,7 @@ trex> ** IPv6 with one VLAN tag (except 82599 which does not support this type of packet). ** Since version 2.21, also QinQ (two vlan tags) is supported if using ``--software'' command line argument. Details link:trex_manual.html#_command_line_options[here]. -* Maximum number of concurrent streams (with different pg_id) on which statistics may be collected: 127 +* Maximum number of concurrent streams (with different pg_id) on which statistics may be collected: 127. Since version 2.23, if using --software command line flag, maximum supported streams is 1023. * On x710/xl710 cards, all rx bytes counters (rx-bps, rx-bps-L1, ...) are not supported. This is because we use hardware counters which support only packets count on these cards. + Starting from version 2.21, you can specify ``--no-hw-flow-stat'' command line argument in order to make x710 behave like other diff --git a/scripts/automation/regression/stateless_tests/stl_rx_test.py b/scripts/automation/regression/stateless_tests/stl_rx_test.py index a71322f5..ea379242 100644 --- a/scripts/automation/regression/stateless_tests/stl_rx_test.py +++ b/scripts/automation/regression/stateless_tests/stl_rx_test.py @@ -2,6 +2,7 @@ from .stl_general_test import CStlGeneral_Test, CTRexScenario from trex_stl_lib.api import * import os, sys +import copy ERROR_LATENCY_TOO_HIGH = 1 @@ -10,79 +11,96 @@ class STLRX_Test(CStlGeneral_Test): def setUp(self): per_driver_params = { - 'net_vmxnet3': { - 'rate_percent': 1, - 'total_pkts': 50, - 'rate_latency': 1, - 'latency_9k_enable': False, - }, - 'net_ixgbe': { - 'rate_percent': 30, - 'total_pkts': 1000, - 'rate_latency': 1, - 'latency_9k_enable': True, - 'latency_9k_max_average': 300, - 'latency_9k_max_latency': 400, - }, - 'net_ixgbe_vf': { - 'rate_percent': 20, - 'total_pkts': 1000, - 'rate_latency': 1, - 'latency_9k_enable': False, - }, - - 'net_i40e': { - 'rate_percent': 80, - 'total_pkts': 1000, - 'rate_latency': 1, - 'latency_9k_enable': True, - 'latency_9k_max_average': 100, - 'latency_9k_max_latency': 250, - }, - 'net_i40e_vf': { - 'rate_percent': 10, - 'total_pkts': 1000, - 'rate_latency': 1, - 'latency_9k_enable': False, - }, - 'net_e1000_igb': { - 'rate_percent': 80, - 'total_pkts': 500, - 'rate_latency': 1, - 'latency_9k_enable': False, - }, - 'net_e1000_em': { - 'rate_percent': 1, - 'total_pkts': 50, - 'rate_latency': 1, - 'latency_9k_enable': False, - }, - 'net_virtio': { - 'rate_percent': 1, - 'total_pkts': 50, - 'rate_latency': 1, - 'latency_9k_enable': False, - 'allow_packets_drop_num': 1, # allow 1 pkt drop - }, - - 'net_mlx5': { - 'rate_percent': 80 if self.is_vf_nics else 5, - 'total_pkts': 1000, - 'rate_latency': 1, - 'latency_9k_enable': False if self.is_vf_nics else True, - 'latency_9k_max_average': 100, - 'latency_9k_max_latency': 450, #see latency issue trex-261 - }, - - 'net_enic': { - 'rate_percent': 1, - 'total_pkts': 50, - 'rate_latency': 1, - 'latency_9k_enable': False, - }, - - - } + 'net_vmxnet3': { + 'rate_percent': 1, + 'total_pkts': 50, + 'rate_latency': 1, + 'latency_9k_enable': False, + 'no_vlan_even_in_software_mode': True, + }, + 'net_ixgbe': { + 'rate_percent': 30, + 'total_pkts': 1000, + 'rate_latency': 1, + 'latency_9k_enable': True, + 'latency_9k_max_average': 300, + 'latency_9k_max_latency': 400, + 'no_vlan': True, + 'no_ipv6': True, + }, + 'net_ixgbe_vf': { + 'rate_percent': 30, + 'total_pkts': 1000, + 'rate_latency': 1, + 'latency_9k_enable': False, + 'no_vlan': True, + 'no_ipv6': True, + 'no_vlan_even_in_software_mode': True, + 'max_pkt_size': 2000, # temporary, until we fix this + }, + + 'net_i40e': { + 'rate_percent': 80, + 'rate_percent_soft': 10, + 'total_pkts': 1000, + 'rate_latency': 1, + 'latency_9k_enable': True, + 'latency_9k_max_average': 100, + 'latency_9k_max_latency': 250, + }, + 'net_i40e_vf': { + 'rate_percent': 80, + 'rate_percent_soft': 1, + 'total_pkts': 1000, + 'rate_latency': 1, + 'latency_9k_enable': False, + 'no_vlan_even_in_software_mode': True, + 'max_pkt_size': 2000, # temporary, until we fix this + 'allow_packets_drop_num': 5, # todo: fix + }, + 'net_e1000_igb': { + 'rate_percent': 80, + 'total_pkts': 500, + 'rate_latency': 1, + 'latency_9k_enable': False, + + }, + 'net_e1000_em': { + 'rate_percent': 1, + 'total_pkts': 50, + 'rate_latency': 1, + 'latency_9k_enable': False, + 'no_vlan_even_in_software_mode': True, + }, + 'net_virtio': { + 'rate_percent': 1, + 'total_pkts': 50, + 'rate_latency': 1, + 'latency_9k_enable': False, + 'allow_packets_drop_num': 1, # allow 1 pkt drop + }, + + 'net_mlx5': { + 'rate_percent': 80, + 'total_pkts': 1000, + 'rate_latency': 1, + 'latency_9k_enable': False if self.is_vf_nics else True, + 'latency_9k_max_average': 100, + 'latency_9k_max_latency': 450, #see latency issue trex-261 + 'no_vlan_even_in_software_mode': True, # todo: fix - We see 1 or 2 packet drops from time to time with vlan + 'no_ipv6': True, #todo: fix - same issue with ipv6 + 'allow_packets_drop_num': 15, # todo - remove + }, + + 'net_enic': { + 'rate_percent': 1, + 'total_pkts': 50, + 'rate_latency': 1, + 'latency_9k_enable': False, + 'rx_bytes_fix': True, + 'no_vlan_even_in_software_mode': True, + }, + } CStlGeneral_Test.setUp(self) assert 'bi' in CTRexScenario.stl_ports_map @@ -99,23 +117,62 @@ class STLRX_Test(CStlGeneral_Test): self.skip('port {0} does not support RX'.format(self.rx_port)) self.cap = cap + self.max_flow_stats = port_info['rx']['counters'] + if self.max_flow_stats == 1023: + # hack - to identify if --software flag was used on server + software_mode = True + else: + software_mode = False + + software_mode = False # fix: need good way to identify software_mode + drv_name = port_info['driver'] self.drv_name = drv_name - if drv_name == 'net_ixgbe': + if 'no_vlan' in per_driver_params[drv_name] and not software_mode: + self.vlan_support = False + else: + self.vlan_support = True + + if 'no_ipv6' in per_driver_params[drv_name] and not software_mode: self.ipv6_support = False else: self.ipv6_support = True + + if 'max_pkt_size' in per_driver_params[drv_name]: + self.max_pkt_size = per_driver_params[drv_name]['max_pkt_size'] + else: + self.max_pkt_size = 9000 + self.rate_percent = per_driver_params[drv_name]['rate_percent'] self.total_pkts = per_driver_params[drv_name]['total_pkts'] self.rate_lat = per_driver_params[drv_name].get('rate_latency', self.rate_percent) + self.rate_fstat = per_driver_params[drv_name].get('rate_percent_soft', self.rate_percent) self.latency_9k_enable = per_driver_params[drv_name]['latency_9k_enable'] self.latency_9k_max_average = per_driver_params[drv_name].get('latency_9k_max_average') self.latency_9k_max_latency = per_driver_params[drv_name].get('latency_9k_max_latency') self.allow_drop = per_driver_params[drv_name].get('allow_packets_drop_num', 0) - self.lat_pps = 1000 self.drops_expected = False self.c.reset(ports = [self.tx_port, self.rx_port]) + if 'rx_bytes_fix' in per_driver_params[drv_name] and per_driver_params[drv_name]['rx_bytes_fix'] == True: + self.fix_rx_byte_count = True + else: + self.fix_rx_byte_count = False + + if software_mode: + self.qinq_support = True + else: + self.qinq_support = False + + # hack for enic + if 'no_vlan_even_in_software_mode' in per_driver_params[drv_name]: + self.vlan_support = False + self.qinq_support = False + + #trex25 has router which does not pass vlan + if CTRexScenario.setup_name == 'trex25': + self.vlan_support = False + self.qinq_support = False vm = STLScVmRaw( [ STLVmFlowVar ( "ip_src", min_value="10.0.0.1", max_value="10.0.0.255", size=4, step=1,op="inc"), @@ -128,6 +185,8 @@ class STLRX_Test(CStlGeneral_Test): ); self.pkt = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/('Your_paylaod_comes_here')) + self.vlan_pkt = STLPktBuilder(pkt = Ether()/Dot1Q()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/('Your_paylaod_comes_here')) + self.qinq_pkt = STLPktBuilder(pkt = Ether(type=0x88A8)/Dot1Q(vlan=19)/Dot1Q(vlan=11)/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/('Your_paylaod_comes_here')) self.ipv6pkt = STLPktBuilder(pkt = Ether()/IPv6(dst="2001:0:4137:9350:8000:f12a:b9c8:2815",src="2001:4860:0:2001::68") /UDP(dport=12,sport=1025)/('Your_paylaod_comes_here')) self.large_pkt = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/('a'*1000)) @@ -180,8 +239,13 @@ class STLRX_Test(CStlGeneral_Test): return 0 + def __exit_with_error(self, stats, err, pkt_len=0, pkt_type=""): + if pkt_len != 0: + print("Failed with packet: type {0}, len {1}".format(pkt_type, pkt_len)) + pprint.pprint(stats) + assert False, err - def __verify_flow (self, pg_id, total_pkts, pkt_len, stats): + def __verify_flow (self, pg_id, total_pkts, pkt_len, pkt_type, stats): flow_stats = stats['flow_stats'].get(pg_id) latency_stats = stats['latency'].get(pg_id) @@ -189,6 +253,9 @@ class STLRX_Test(CStlGeneral_Test): assert False, "no flow stats available" tx_pkts = flow_stats['tx_pkts'].get(self.tx_port, 0) + # for continues tests, we do not know how many packets were sent + if total_pkts == 0: + total_pkts = tx_pkts tx_bytes = flow_stats['tx_bytes'].get(self.tx_port, 0) rx_pkts = flow_stats['rx_pkts'].get(self.rx_port, 0) if latency_stats is not None: @@ -199,49 +266,62 @@ class STLRX_Test(CStlGeneral_Test): stl = latency_stats['err_cntrs']['seq_too_low'] lat = latency_stats['latency'] if ooo != 0 or dup != 0 or stl != 0: - pprint.pprint(latency_stats) - tmp='Error packets - dropped:{0}, ooo:{1} dup:{2} seq too high:{3} seq too low:{4}'.format(drops, ooo, dup, sth, stl) - assert False, tmp + self.__exit_with_error(latency_stats, + 'Error packets - dropped:{0}, ooo:{1} dup:{2} seq too high:{3} seq too low:{4}'.format(drops, ooo, dup, sth, stl) + , pkt_len, pkt_type) - if (drops > self.allow_drop or sth != 0) and not self.drops_expected: - pprint.pprint(latency_stats) - tmp='Error packets - dropped:{0}, ooo:{1} dup:{2} seq too high:{3} seq too low:{4}'.format(drops, ooo, dup, sth, stl) - assert False, tmp + if (drops > self.allow_drop or sth > self.allow_drop) and not self.drops_expected: + self.__exit_with_error(latency_stats, + 'Error packets - dropped:{0}, ooo:{1} dup:{2} seq too high:{3} seq too low:{4}'.format(drops, ooo, dup, sth, stl) + , pkt_len, pkt_type) if tx_pkts != total_pkts: pprint.pprint(flow_stats) - tmp = 'TX pkts mismatch - got: {0}, expected: {1}'.format(tx_pkts, total_pkts) - assert False, tmp + self.__exit_with_error(flow_stats + , 'TX pkts mismatch - got: {0}, expected: {1}'.format(tx_pkts, total_pkts) + , pkt_len, pkt_type) if tx_bytes != (total_pkts * pkt_len): - pprint.pprint(flow_stats) - tmp = 'TX bytes mismatch - got: {0}, expected: {1}'.format(tx_bytes, (total_pkts * pkt_len)) - assert False, tmp + self.__exit_with_error(flow_stats + , 'TX bytes mismatch - got: {0}, expected: {1}'.format(tx_bytes, (total_pkts * pkt_len)) + , pkt_len, pkt_type) if abs(total_pkts - rx_pkts) > self.allow_drop and not self.drops_expected: - pprint.pprint(flow_stats) - tmp = 'RX pkts mismatch - got: {0}, expected: {1}'.format(rx_pkts, total_pkts) - assert False, tmp + self.__exit_with_error(flow_stats + , 'RX pkts mismatch - got: {0}, expected: {1}'.format(rx_pkts, total_pkts) + , pkt_len, pkt_type) + + rx_pkt_len = pkt_len + if self.fix_rx_byte_count: + # Patch. Vic card always add vlan, so we should expect 4 extra bytes in each packet + rx_pkt_len += 4 if "rx_bytes" in self.cap: rx_bytes = flow_stats['rx_bytes'].get(self.rx_port, 0) - if abs(rx_bytes / pkt_len - total_pkts ) > self.allow_drop and not self.drops_expected: - pprint.pprint(flow_stats) - tmp = 'RX bytes mismatch - got: {0}, expected: {1}'.format(rx_bytes, (total_pkts * pkt_len)) - assert False, tmp - + if abs(rx_bytes / rx_pkt_len - total_pkts ) > self.allow_drop and not self.drops_expected: + self.__exit_with_error(flow_stats + , 'RX bytes mismatch - got: {0}, expected: {1}'.format(rx_bytes, (total_pkts * rx_pkt_len)) + , pkt_len, pkt_type) # RX itreation - def __rx_iteration (self, exp_list): + def __rx_iteration (self, exp_list, duration=0): self.c.clear_stats() - self.c.start(ports = [self.tx_port]) - self.c.wait_on_traffic(ports = [self.tx_port]) + if duration != 0: + self.c.start(ports = [self.tx_port], duration=duration) + self.c.wait_on_traffic(ports = [self.tx_port],timeout = duration+10,rx_delay_ms = 100) + else: + self.c.start(ports = [self.tx_port]) + self.c.wait_on_traffic(ports = [self.tx_port]) stats = self.c.get_stats() for exp in exp_list: - self.__verify_flow(exp['pg_id'], exp['total_pkts'], exp['pkt_len'], stats) + if 'pkt_type' in exp: + pkt_type = exp['pkt_type'] + else: + pkt_type = "not specified" + self.__verify_flow(exp['pg_id'], exp['total_pkts'], exp['pkt_len'], pkt_type, stats) # one stream on TX --> RX @@ -271,42 +351,97 @@ class STLRX_Test(CStlGeneral_Test): def test_multiple_streams(self): + self._test_multiple_streams(False) + + def test_multiple_streams_random(self): + if self.drv_name == 'net_mlx5' or self.drv_name == 'net_i40e_vf': + self.skip('Not running on Mellanox cards currently') + self._test_multiple_streams(True) + + def _test_multiple_streams(self, is_random): if self.is_virt_nics: self.skip('Skip this for virtual NICs') - num_latency_streams = 128 - num_flow_stat_streams = 127 + if is_random: + num_latency_streams = random.randint(1, 128); + num_flow_stat_streams = random.randint(1, self.max_flow_stats); + all_pkts = [self.pkt] + if self.ipv6_support: + all_pkts.append(self.ipv6pkt) + if self.vlan_support: + all_pkts.append(self.vlan_pkt) + if self.qinq_support: + all_pkts.append(self.qinq_pkt) + else: + num_latency_streams = 128 + num_flow_stat_streams = self.max_flow_stats total_pkts = int(self.total_pkts / (num_latency_streams + num_flow_stat_streams)) if total_pkts == 0: total_pkts = 1 - percent = float(self.rate_lat) / (num_latency_streams + num_flow_stat_streams) + percent_lat = float(self.rate_lat) / num_latency_streams + percent_fstat = float(self.rate_fstat) / num_flow_stat_streams + + print("num_latency_streams:{0}".format(num_latency_streams)) + if is_random: + print(" total percent:{0} ({1} per stream)".format(percent_lat * num_latency_streams, percent_lat)) + + print("num_flow_stat_streams:{0}".format(num_flow_stat_streams)) + if is_random: + print(" total percent:{0} ({1} per stream)".format(percent_fstat * num_flow_stat_streams, percent_fstat)) try: streams = [] exp = [] - # 10 identical streams for pg_id in range(1, num_latency_streams): + if is_random: + pkt = copy.deepcopy(all_pkts[random.randint(0, len(all_pkts) - 1)]) + pkt.set_packet(pkt.pkt / ('a' * random.randint(0, self.max_pkt_size - len(pkt.pkt)))) + send_mode = STLTXCont(percentage = percent_lat) + else: + pkt = self.pkt + send_mode = STLTXSingleBurst(total_pkts = total_pkts+pg_id, percentage = percent_lat) + streams.append(STLStream(name = 'rx {0}'.format(pg_id), - packet = self.pkt, + packet = pkt, flow_stats = STLFlowLatencyStats(pg_id = pg_id), - mode = STLTXSingleBurst(total_pkts = total_pkts+pg_id, percentage = percent))) + mode = send_mode)) - exp.append({'pg_id': pg_id, 'total_pkts': total_pkts+pg_id, 'pkt_len': streams[-1].get_pkt_len()}) + if is_random: + exp.append({'pg_id': pg_id, 'total_pkts': 0, 'pkt_len': streams[-1].get_pkt_len() + , 'pkt_type': pkt.pkt.sprintf("%Ether.type%")}) + else: + exp.append({'pg_id': pg_id, 'total_pkts': total_pkts+pg_id, 'pkt_len': streams[-1].get_pkt_len()}) for pg_id in range(num_latency_streams + 1, num_latency_streams + num_flow_stat_streams): + if is_random: + pkt = copy.deepcopy(all_pkts[random.randint(0, len(all_pkts) - 1)]) + pkt.set_packet(pkt.pkt / ('a' * random.randint(0, self.max_pkt_size - len(pkt.pkt)))) + send_mode = STLTXCont(percentage = percent_fstat) + else: + pkt = self.pkt + send_mode = STLTXSingleBurst(total_pkts = total_pkts+pg_id, percentage = percent_fstat) + streams.append(STLStream(name = 'rx {0}'.format(pg_id), - packet = self.pkt, + packet = pkt, flow_stats = STLFlowStats(pg_id = pg_id), - mode = STLTXSingleBurst(total_pkts = total_pkts+pg_id, percentage = percent))) - - exp.append({'pg_id': pg_id, 'total_pkts': total_pkts+pg_id, 'pkt_len': streams[-1].get_pkt_len()}) + mode = send_mode)) + if is_random: + exp.append({'pg_id': pg_id, 'total_pkts': 0, 'pkt_len': streams[-1].get_pkt_len() + , 'pkt_type': pkt.pkt.sprintf("%Ether.type%")}) + else: + exp.append({'pg_id': pg_id, 'total_pkts': total_pkts+pg_id, 'pkt_len': streams[-1].get_pkt_len()}) # add both streams to ports self.c.add_streams(streams, ports = [self.tx_port]) - self.__rx_iteration(exp) + if is_random: + duration = 60 + print("Duration: {0}".format(duration)) + else: + duration = 0 + self.__rx_iteration(exp, duration = duration) except STLError as e: @@ -321,7 +456,15 @@ class STLRX_Test(CStlGeneral_Test): {'name': 'Latency, no field engine', 'pkt': self.pkt, 'lat': True}, {'name': 'Latency, short packet with field engine', 'pkt': self.vm_pkt, 'lat': True}, {'name': 'Latency, large packet field engine', 'pkt': self.vm_large_pkt, 'lat': True} + + # add here more stream types ] + if self.vlan_support: + streams_data.append({'name': 'Flow stat with vlan. No latency', 'pkt': self.vlan_pkt, 'lat': False}) + + if self.qinq_support: + streams_data.append({'name': 'Flow stat qinq. No latency', 'pkt': self.qinq_pkt, 'lat': False}) + if self.latency_9k_enable: streams_data.append({'name': 'Latency, 9k packet with field engine', 'pkt': self.vm_9k_pkt, 'lat': True}) @@ -410,10 +553,7 @@ class STLRX_Test(CStlGeneral_Test): assert False , '{0}'.format(e) - - - - # check low latency when you have stream of 9K stream + # Verify that there is low latency with random packet size,duration and ports def test_9k_stream(self): if self.is_virt_nics: self.skip('Skip this for virtual NICs') @@ -428,8 +568,6 @@ class STLRX_Test(CStlGeneral_Test): pgid=random.randint(1, 65000); pkt_size=random.randint(1000, 9000); all_ports = list(CTRexScenario.stl_ports_map['map'].keys()); - - s_port=random.sample(all_ports, random.randint(1, len(all_ports)) ) s_port=sorted(s_port) @@ -515,7 +653,7 @@ class STLRX_Test(CStlGeneral_Test): self.check_stats(ops["obytes"], bytes, "stats[%s][obytes]" % c_port) self.check_stats(ops["opackets"], pkts, "stats[%s][opackets]" % c_port) - + self.check_stats(ips["ibytes"], bytes, "stats[%s][ibytes]" % s_port) self.check_stats(ips["ipackets"], pkts, "stats[%s][ipackets]" % s_port) @@ -523,10 +661,10 @@ class STLRX_Test(CStlGeneral_Test): ls = stats['flow_stats'][5 + c_port] self.check_stats(ls['rx_pkts']['total'], pkts, "ls['rx_pkts']['total']") self.check_stats(ls['rx_pkts'][s_port], pkts, "ls['rx_pkts'][%s]" % s_port) - + self.check_stats(ls['tx_pkts']['total'], pkts, "ls['tx_pkts']['total']") self.check_stats(ls['tx_pkts'][c_port], pkts, "ls['tx_pkts'][%s]" % c_port) - + self.check_stats(ls['tx_bytes']['total'], bytes, "ls['tx_bytes']['total']") self.check_stats(ls['tx_bytes'][c_port], bytes, "ls['tx_bytes'][%s]" % c_port) @@ -556,9 +694,11 @@ class STLRX_Test(CStlGeneral_Test): # this test sends 1 64 byte packet with latency and check that all counters are reported as 64 bytes def test_fcs_stream(self): - - # in case of VM and vSwitch there are drop of packets in some cases, let retry number of times - # in this case we just want to check functionality that packet of 64 is reported as 64 in all levels + if CTRexScenario.setup_name == 'trex19': + # todo: - fix + self.skip('Test does not run on trex19') + # In case of VM and vSwitch there is packet drop in some cases, so we retry few of times + # In this case we just want to check that packet of 64 bytes is reported correctly as 64 everywhere. is_vm = self.is_virt_nics or self.is_vf_nics tries = 1 @@ -639,4 +779,3 @@ class STLRX_Test(CStlGeneral_Test): exp = {'pg_id': 5, 'total_pkts': total_pkts, 'pkt_len': s1.get_pkt_len()} self.__rx_iteration( [exp] ) - diff --git a/src/bp_sim.cpp b/src/bp_sim.cpp index 43cfed93..9dfb7c3d 100755 --- a/src/bp_sim.cpp +++ b/src/bp_sim.cpp @@ -1687,7 +1687,7 @@ void CFlowPktInfo::do_generate_new_mbuf_rxcheck(rte_mbuf_t * m, uint8_t save_header= ipv6->getNextHdr(); ipv6->setNextHdr(RX_CHECK_V6_OPT_TYPE); ipv6->setHopLimit(TTL_RESERVE_DUPLICATE); - ipv6->setTrafficClass(ipv6->getTrafficClass()|TOS_TTL_RESERVE_DUPLICATE); + ipv6->setTrafficClass(ipv6->getTrafficClass() | TOS_GO_TO_CPU); ipv6->setPayloadLen( ipv6->getPayloadLen() + sizeof(CRx_check_header)); rxhdr->m_option_type = save_header; @@ -1697,7 +1697,7 @@ void CFlowPktInfo::do_generate_new_mbuf_rxcheck(rte_mbuf_t * m, ipv4->setHeaderLength(current_opt_len+opt_len); ipv4->setTotalLength(ipv4->getTotalLength()+opt_len); ipv4->setTimeToLive(TTL_RESERVE_DUPLICATE); - ipv4->setTOS(ipv4->getTOS()|TOS_TTL_RESERVE_DUPLICATE); + ipv4->setTOS(ipv4->getTOS() | TOS_GO_TO_CPU); rxhdr->m_option_type = RX_CHECK_V4_OPT_TYPE; rxhdr->m_option_len = RX_CHECK_V4_OPT_LEN; diff --git a/src/bp_sim.h b/src/bp_sim.h index fd4e6627..c524723a 100755 --- a/src/bp_sim.h +++ b/src/bp_sim.h @@ -78,7 +78,7 @@ class CGenNodePCAP; /* reserve both 0xFF and 0xFE , router will -1 FF */ #define TTL_RESERVE_DUPLICATE 0xff -#define TOS_TTL_RESERVE_DUPLICATE 0x1 +#define TOS_GO_TO_CPU 0x1 /* * Length of string needed to hold the largest port (16-bit) address */ @@ -2832,18 +2832,18 @@ public: void setTOSReserve(){ BP_ASSERT(l3.m_ipv4); if (is_ipv6()) { - l3.m_ipv6->setTrafficClass(l3.m_ipv6->getTrafficClass() | TOS_TTL_RESERVE_DUPLICATE ); + l3.m_ipv6->setTrafficClass(l3.m_ipv6->getTrafficClass() | TOS_GO_TO_CPU ); }else{ - l3.m_ipv4->setTOS(l3.m_ipv4->getTOS()| TOS_TTL_RESERVE_DUPLICATE ); + l3.m_ipv4->setTOS(l3.m_ipv4->getTOS()| TOS_GO_TO_CPU ); } } void clearTOSReserve(){ BP_ASSERT(l3.m_ipv4); if (is_ipv6()) { - l3.m_ipv6->setTrafficClass(l3.m_ipv6->getTrafficClass()& (~TOS_TTL_RESERVE_DUPLICATE) ); + l3.m_ipv6->setTrafficClass(l3.m_ipv6->getTrafficClass()& (~TOS_GO_TO_CPU) ); }else{ - l3.m_ipv4->setTOS(l3.m_ipv4->getTOS() & (~TOS_TTL_RESERVE_DUPLICATE) ); + l3.m_ipv4->setTOS(l3.m_ipv4->getTOS() & (~TOS_GO_TO_CPU) ); } } diff --git a/src/debug.cpp b/src/debug.cpp index ccef8499..d20721bf 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -399,7 +399,7 @@ int CTrexDebug::verify_hw_rules(bool recv_all) { memset(pkt_per_q, 0, sizeof(pkt_per_q)); // We don't know which interfaces connected where, so sum all queue 1 and all queue 0 for (int port = 0; port < m_max_ports; port++) { - for(int queue_id = 0; queue_id <= m_rx_q_num; queue_id++) { + for(int queue_id = 0; queue_id < m_rx_q_num; queue_id++) { lp = &m_ports[port]; uint16_t cnt = lp->rx_burst(queue_id, rx_pkts, 32); pkt_per_q[queue_id] += cnt; diff --git a/src/flow_stat.cpp b/src/flow_stat.cpp index 58d08c44..8d33bf3c 100644 --- a/src/flow_stat.cpp +++ b/src/flow_stat.cpp @@ -63,7 +63,7 @@ stream_del: HW_ID_INIT static const uint16_t HW_ID_INIT = UINT16_MAX; static const uint16_t HW_ID_FREE = UINT16_MAX - 1; static const uint8_t PAYLOAD_RULE_PROTO = 255; -const uint32_t FLOW_STAT_PAYLOAD_IP_ID = IP_ID_RESERVE_BASE + MAX_FLOW_STATS; +const uint32_t FLOW_STAT_PAYLOAD_IP_ID = UINT16_MAX; inline std::string methodName(const std::string& prettyFunction) { @@ -457,8 +457,6 @@ CFlowStatRuleMgr::CFlowStatRuleMgr() { m_parser_ipid = NULL; m_parser_pl = NULL; m_rx_core = NULL; - m_hw_id_map.create(MAX_FLOW_STATS); - m_hw_id_map_payload.create(MAX_FLOW_STATS_PAYLOAD); memset(m_rx_cant_count_err, 0, sizeof(m_rx_cant_count_err)); memset(m_tx_cant_count_err, 0, sizeof(m_tx_cant_count_err)); m_num_ports = 0; // need to call create to init @@ -481,21 +479,24 @@ CFlowStatRuleMgr::~CFlowStatRuleMgr() { } void CFlowStatRuleMgr::create() { - uint16_t num_counters, cap; + uint16_t num_counters, cap, ip_id_base; TrexStateless *tstateless = get_stateless_obj(); assert(tstateless); m_api = tstateless->get_platform_api(); assert(m_api); - m_api->get_interface_stat_info(0, num_counters, cap); + m_api->get_interface_stat_info(0, num_counters, cap, ip_id_base); m_api->get_port_num(m_num_ports); // This initialize m_num_ports for (uint8_t port = 0; port < m_num_ports; port++) { assert(m_api->reset_hw_flow_stats(port) == 0); } + m_hw_id_map.create(num_counters); + m_hw_id_map_payload.create(MAX_FLOW_STATS_PAYLOAD); m_ring_to_rx = CMsgIns::Ins()->getCpRx()->getRingCpToDp(0); assert(m_ring_to_rx); m_rx_core = get_rx_sl_core_obj(); m_cap = cap; + m_ip_id_reserve_base = ip_id_base; if ((CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_ONE_QUEUE) || (CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_RSS)) { @@ -662,6 +663,11 @@ int CFlowStatRuleMgr::del_stream(TrexStream * stream) { return 0; } m_user_id_map.del_stream(stream->m_rx_check.m_pg_id); // Throws exception in case of error + if (m_user_id_map.is_empty()) { + m_max_hw_id = 0; + m_max_hw_id_payload = 0; + } + stream->m_rx_check.m_hw_id = HW_ID_INIT; return 0; @@ -695,16 +701,13 @@ int CFlowStatRuleMgr::start_stream(TrexStream * stream) { return 0; } - uint32_t ip_id; + uint32_t ip_id; // 32 bit, since this also supports IPv6 if (m_parser_ipid->get_ip_id(ip_id) < 0) { return 0; // if we could not find the ip id, no need to fix } // verify no reserved IP_ID used, and change if needed - if (ip_id >= IP_ID_RESERVE_BASE) { - if (m_parser_ipid->set_ip_id(ip_id & 0xefff) < 0) { - throw TrexFStatEx("Stream IP ID in reserved range. Failed changing it" - , TrexException::T_FLOW_STAT_FAILED_CHANGE_IP_ID); - } + if (ip_id >= m_ip_id_reserve_base) { + m_parser_ipid->set_ip_id(ip_id & 0x0000efff); } return 0; } @@ -789,10 +792,12 @@ int CFlowStatRuleMgr::start_stream(TrexStream * stream) { // saving given hw_id on stream for use by tx statistics count if (rule_type == TrexPlatformApi::IF_STAT_IPV4_ID) { - m_parser_ipid->set_ip_id(IP_ID_RESERVE_BASE + hw_id); + m_parser_ipid->set_ip_id(m_ip_id_reserve_base + hw_id); + m_parser_ipid->set_tos_to_cpu(); stream->m_rx_check.m_hw_id = hw_id; } else { m_parser_pl->set_ip_id(FLOW_STAT_PAYLOAD_IP_ID); + m_parser_pl->set_tos_to_cpu(); // for payload rules, we use the range right after ip id rules stream->m_rx_check.m_hw_id = hw_id + MAX_FLOW_STATS; } @@ -1001,7 +1006,9 @@ void CFlowStatRuleMgr::send_start_stop_msg_to_rx(bool is_start) { // s_json - flow statistics json // l_json - latency data json // baseline - If true, send flow statistics fields even if they were not changed since last run -bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, bool baseline) { +// send_all - If true, send data for all pg_ids. This is used for getting statistics in automation API. +// If false, send small amount of pg ids. Used for async interface, for displaying in console +bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, bool baseline, bool send_all) { rx_per_flow_t rx_stats[MAX_FLOW_STATS]; rx_per_flow_t rx_stats_payload[MAX_FLOW_STATS]; tx_per_flow_t tx_stats[MAX_FLOW_STATS]; @@ -1036,13 +1043,39 @@ bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, boo m_api->get_rfc2544_info(rfc2544_info, 0, m_max_hw_id_payload, false); m_api->get_rx_err_cntrs(&rx_err_cntrs); + +#if 0 + // If we want to send all PG_IDs, in groups of 128, enable this, and remove the restrication + // of sending only 8 in loop of building json message below + static int min_to_send = 0; + static int max_to_send = m_max_hw_id; + + min_to_send += 128; + if (min_to_send > m_max_hw_id) { + min_to_send = 0; + } + max_to_send = min_to_send + 128; + if (max_to_send > m_max_hw_id) { + max_to_send = m_max_hw_id; + } + + // If asking for "baseline", send everything always. + if (baseline || send_all) { + min_to_send = 0; + max_to_send = m_max_hw_id; + } +#endif + + int min_to_send = 0; + int max_to_send = m_max_hw_id; + // read hw counters, and update for (uint8_t port = 0; port < m_num_ports; port++) { - m_api->get_flow_stats(port, rx_stats, (void *)tx_stats, 0, m_max_hw_id, false, TrexPlatformApi::IF_STAT_IPV4_ID); - for (int i = 0; i <= m_max_hw_id; i++) { + m_api->get_flow_stats(port, rx_stats, (void *)tx_stats, min_to_send, max_to_send, false, TrexPlatformApi::IF_STAT_IPV4_ID); + for (int i = 0; i <= max_to_send - min_to_send; i++) { if (rx_stats[i].get_pkts() != 0) { rx_per_flow_t rx_pkts = rx_stats[i]; - CFlowStatUserIdInfo *p_user_id = m_user_id_map.find_user_id(m_hw_id_map.get_user_id(i)); + CFlowStatUserIdInfo *p_user_id = m_user_id_map.find_user_id(m_hw_id_map.get_user_id(i + min_to_send)); if (likely(p_user_id != NULL)) { if (p_user_id->get_rx_cntr(port) != rx_pkts) { p_user_id->set_rx_cntr(port, rx_pkts); @@ -1056,7 +1089,7 @@ bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, boo } if (tx_stats[i].get_pkts() != 0) { tx_per_flow_t tx_pkts = tx_stats[i]; - CFlowStatUserIdInfo *p_user_id = m_user_id_map.find_user_id(m_hw_id_map.get_user_id(i)); + CFlowStatUserIdInfo *p_user_id = m_user_id_map.find_user_id(m_hw_id_map.get_user_id(i + min_to_send)); if (likely(p_user_id != NULL)) { if (p_user_id->get_tx_cntr(port) != tx_pkts) { p_user_id->set_tx_cntr(port, tx_pkts); @@ -1125,8 +1158,25 @@ bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, boo l_data_section["global"]["old_flow"] = Json::Value::UInt64(tmp_cnt); } + // TUI only present 4 PG_IDs today, so we send everything only when explicit request comes + // (probably from automation API) + uint16_t max_pg_ids_to_send = 8; + static int times_send_all = 0; + if (send_all) { + times_send_all = 20; + } + if (baseline || (times_send_all > 0)) { + if (times_send_all > 0) + times_send_all--; + max_pg_ids_to_send = UINT16_MAX; + } + flow_stat_user_id_map_it_t it; for (it = m_user_id_map.begin(); it != m_user_id_map.end(); it++) { + if (max_pg_ids_to_send == 0) { + break; + } + max_pg_ids_to_send--; bool send_empty = true; CFlowStatUserIdInfo *user_id_info = it->second; uint32_t user_id = it->first; diff --git a/src/flow_stat.h b/src/flow_stat.h index 25bf421c..b155b917 100644 --- a/src/flow_stat.h +++ b/src/flow_stat.h @@ -32,9 +32,10 @@ #include "internal_api/trex_platform_api.h" // range reserved for rx stat measurement is from IP_ID_RESERVE_BASE to 0xffff -// Do not change this value. In i350 cards, we filter according to first byte of IP ID -// In other places, we identify packets by if (ip_id > IP_ID_RESERVE_BASE) -#define IP_ID_RESERVE_BASE 0xff00 +// This value is used by all NICs, except i350, where value of 0xff00 is used. +// We identify if packet needs handling as flow stat, by: "if (ip_id > IP_ID_RESERVE_BASE)" +#define IP_ID_RESERVE_BASE (0xffff - MAX_FLOW_STATS) + #define FLOW_STAT_PAYLOAD_MAGIC 0xAB #define FLOW_STAT_PAYLOAD_INITIAL_FLOW_SEQ 0x01 extern const uint32_t FLOW_STAT_PAYLOAD_IP_ID; @@ -469,7 +470,7 @@ class CFlowStatRuleMgr { int stop_stream(TrexStream * stream); int get_active_pgids(flow_stat_active_t &result); int set_mode(enum flow_stat_mode_e mode); - bool dump_json(std::string & s_json, std::string & l_json, bool baseline); + bool dump_json(std::string & s_json, std::string & l_json, bool baseline, bool send_all); private: void create(); @@ -493,6 +494,7 @@ class CFlowStatRuleMgr { CFlowStatParser *m_parser_pl; // for payload rules (latency) enum flow_stat_mode_e m_mode; uint16_t m_cap; // capabilities of the NIC driver we are using + uint16_t m_ip_id_reserve_base; // lowest IP ID we use for our needs uint32_t m_rx_cant_count_err[TREX_MAX_PORTS]; uint32_t m_tx_cant_count_err[TREX_MAX_PORTS]; }; diff --git a/src/flow_stat_parser.cpp b/src/flow_stat_parser.cpp index f9ac8c9b..ef9a120b 100644 --- a/src/flow_stat_parser.cpp +++ b/src/flow_stat_parser.cpp @@ -150,6 +150,7 @@ CFlowStatParser_err_t CFlowStatParser::parse(uint8_t *p, uint16_t len) { return FSTAT_PARSER_E_OK; } +// arg is uint32_t in below two functions because we want same function to work for IPv4 and IPv6 int CFlowStatParser::get_ip_id(uint32_t &ip_id) { if (m_ipv4) { ip_id = m_ipv4->getId(); @@ -164,24 +165,34 @@ int CFlowStatParser::get_ip_id(uint32_t &ip_id) { return -1; } -int CFlowStatParser::set_ip_id(uint32_t new_id) { +void CFlowStatParser::set_ip_id(uint32_t new_id) { if (m_ipv4) { + uint16_t ipv4_ip_id = (uint16_t) new_id; // Updating checksum, not recalculating, so if someone put bad checksum on purpose, it will stay bad - m_ipv4->updateCheckSum(PKT_NTOHS(m_ipv4->getFirstWord()), PKT_NTOHS(m_ipv4->getFirstWord() |TOS_TTL_RESERVE_DUPLICATE)); - m_ipv4->updateCheckSum(PKT_NTOHS(m_ipv4->getId()), PKT_NTOHS(new_id)); - m_ipv4->setId(new_id); - m_ipv4->setTOS(m_ipv4->getTOS()|TOS_TTL_RESERVE_DUPLICATE); - return 0; + m_ipv4->updateCheckSum(PKT_NTOHS(m_ipv4->getId()), PKT_NTOHS(ipv4_ip_id)); + m_ipv4->setId(ipv4_ip_id); } if (m_ipv6) { - m_ipv6->setTrafficClass(m_ipv6->getTrafficClass()|TOS_TTL_RESERVE_DUPLICATE); m_ipv6->setFlowLabel(new_id); - return 0; } - return -1; } +// In Mellanox and VIC cards, we use TOS to mark packets to go to CPU +void CFlowStatParser::set_tos_to_cpu() { + if (m_ipv4) { + // Updating checksum, not recalculating, so if someone put bad checksum on purpose, it will stay bad + m_ipv4->updateCheckSum(PKT_NTOHS(m_ipv4->getFirstWord()), PKT_NTOHS(m_ipv4->getFirstWord() + | TOS_GO_TO_CPU)); + m_ipv4->setTOS(m_ipv4->getTOS() | TOS_GO_TO_CPU); + } + + if (m_ipv6) { + m_ipv6->setTrafficClass(m_ipv6->getTrafficClass() | TOS_GO_TO_CPU); + } +} + + int CFlowStatParser::get_l3_proto(uint16_t &proto) { if (m_ipv4) { proto = EthernetHeader::Protocol::IP; diff --git a/src/flow_stat_parser.h b/src/flow_stat_parser.h index c00d7e33..a6e43e0d 100644 --- a/src/flow_stat_parser.h +++ b/src/flow_stat_parser.h @@ -65,7 +65,8 @@ class CFlowStatParser { std::string get_error_str(CFlowStatParser_err_t err); virtual CFlowStatParser_err_t parse(uint8_t *pkt, uint16_t len); virtual int get_ip_id(uint32_t &ip_id); - virtual int set_ip_id(uint32_t ip_id); + virtual void set_ip_id(uint32_t ip_id); + virtual void set_tos_to_cpu(); virtual int get_l3_proto(uint16_t &proto); virtual int get_l4_proto(uint8_t &proto); virtual int get_payload_len(uint8_t *p, uint16_t len, uint16_t &payload_len); @@ -129,8 +130,9 @@ class CPassAllParser : public CFlowStatParser { public: CPassAllParser() : CFlowStatParser (CFlowStatParser::FLOW_STAT_PARSER_MODE_SW) {} virtual CFlowStatParser_err_t parse(uint8_t *pkt, uint16_t len); + void set_tos_to_cpu() {} virtual int get_ip_id(uint32_t &ip_id) { ip_id = 0; return 0;} - virtual int set_ip_id(uint32_t ip_id){return 0;} + virtual void set_ip_id(uint32_t ip_id){} virtual int get_l3_proto(uint16_t &proto){proto = 0; return 0;} virtual int get_l4_proto(uint8_t &proto) {proto = 0; return 0;} virtual int get_payload_len(uint8_t *p, uint16_t len, uint16_t &payload_len) {payload_len = m_len; return 0;} diff --git a/src/internal_api/trex_platform_api.h b/src/internal_api/trex_platform_api.h index 16752652..6cb68690 100644 --- a/src/internal_api/trex_platform_api.h +++ b/src/internal_api/trex_platform_api.h @@ -133,7 +133,8 @@ public: virtual void publish_async_data_now(uint32_t key, bool baseline) const = 0; virtual void publish_async_port_attr_changed(uint8_t port_id) const = 0; virtual uint8_t get_dp_core_count() const = 0; - virtual void get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities) const =0; + virtual void get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities + , uint16_t &ip_id_base) const =0; virtual int get_flow_stats(uint8_t port_id, void *stats, void *tx_stats, int min, int max, bool reset , TrexPlatformApi::driver_stat_cap_e type) const = 0; virtual int get_rfc2544_info(void *rfc2544_info, int min, int max, bool reset) const = 0; @@ -174,7 +175,8 @@ public: void publish_async_data_now(uint32_t key, bool baseline) const; void publish_async_port_attr_changed(uint8_t port_id) const; uint8_t get_dp_core_count() const; - void get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities) const; + void get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities + , uint16_t &ip_id_base) const; int get_flow_stats(uint8_t port_id, void *stats, void *tx_stats, int min, int max, bool reset , TrexPlatformApi::driver_stat_cap_e type) const; int get_rfc2544_info(void *rfc2544_info, int min, int max, bool reset) const; @@ -232,7 +234,8 @@ public: virtual void get_interface_stats(uint8_t interface_id, TrexPlatformInterfaceStats &stats) const { } - virtual void get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities) const {num_counters=128; capabilities=TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_PAYLOAD; } + virtual void get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities + , uint16_t &ip_id_base) const {num_counters=128; capabilities=TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_PAYLOAD; ip_id_base = 0xff00;} virtual void port_id_to_cores(uint8_t port_id, std::vector<std::pair<uint8_t, uint8_t>> &cores_id_list) const { for (int i = 0; i < m_dp_core_count; i++) { diff --git a/src/main_dpdk.cpp b/src/main_dpdk.cpp index 434254a2..c26188d0 100644 --- a/src/main_dpdk.cpp +++ b/src/main_dpdk.cpp @@ -173,8 +173,7 @@ public: , int min, int max) {return -1;} virtual void reset_rx_stats(CPhyEthIF * _if, uint32_t *stats, int min, int len) {} virtual int dump_fdir_global_stats(CPhyEthIF * _if, FILE *fd) { return -1;} - virtual int get_stat_counters_num() {return 0;} - virtual int get_rx_stat_capabilities() {return 0;} + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) = 0; virtual int verify_fw_ver(int i) {return 0;} virtual CFlowStatParser *get_flow_stat_parser(); virtual int set_rcv_all(CPhyEthIF * _if, bool set_on)=0; @@ -230,10 +229,20 @@ public: virtual void get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats); 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 | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) { + flags = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT | TrexPlatformApi::IF_STAT_PAYLOAD; + + if (CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_ONE_QUEUE + || CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_RSS) { + num_counters = MAX_FLOW_STATS; + base_ip_id = IP_ID_RESERVE_BASE; + } else { + num_counters = UINT8_MAX; + // Must be 0xff00, since we configure HW filter for the 0xff byte + // The filter must catch all flow stat packets, and latency packets (having 0xffff in IP ID) + base_ip_id = 0xff00; + } } virtual int wait_for_stable_link(); virtual void wait_after_link_up(); @@ -268,11 +277,13 @@ public: virtual void get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats)=0; 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 | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) { + flags = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT | TrexPlatformApi::IF_STAT_PAYLOAD; + num_counters = MAX_FLOW_STATS; + base_ip_id = IP_ID_RESERVE_BASE; } + virtual int set_rcv_all(CPhyEthIF * _if, bool set_on) {return 0;} CFlowStatParser *get_flow_stat_parser(); }; @@ -376,10 +387,16 @@ public: virtual void get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats); 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 | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) { + flags = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT | TrexPlatformApi::IF_STAT_PAYLOAD; + if ((CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_RSS) + || (CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_ONE_QUEUE)) { + num_counters = MAX_FLOW_STATS; + } else { + num_counters = 127; + } + base_ip_id = IP_ID_RESERVE_BASE; } virtual CFlowStatParser *get_flow_stat_parser(); int add_del_eth_filter(CPhyEthIF * _if, bool is_add, uint16_t ethertype); @@ -389,10 +406,11 @@ public: class CTRexExtendedDriverBase40G : public CTRexExtendedDriverBase { public: CTRexExtendedDriverBase40G(){ - // Since we support only 128 counters per if, it is OK to configure here 4 statically. + // 4 will make us support 127 flow stat counters // If we want to support more counters in case of card having less interfaces, we // Will have to identify the number of interfaces dynamically. m_if_per_card = 4; + m_cap = TREX_DRV_CAP_DROP_Q | TREX_DRV_CAP_MAC_ADDR_CHG | TREX_DRV_CAP_DROP_PKTS_IF_LNK_DOWN; } @@ -419,16 +437,23 @@ public: virtual void reset_rx_stats(CPhyEthIF * _if, uint32_t *stats, int min, int len); virtual int get_rx_stats(CPhyEthIF * _if, uint32_t *pkts, uint32_t *prev_pkts, uint32_t *bytes, uint32_t *prev_bytes, int min, int max); virtual int dump_fdir_global_stats(CPhyEthIF * _if, FILE *fd); - virtual int get_stat_counters_num() {return MAX_FLOW_STATS;} - virtual int get_rx_stat_capabilities() { - uint32_t ret = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_PAYLOAD; + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) { + flags = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_PAYLOAD; // HW counters on x710 does not support coutning bytes. if ( CGlobalInfo::m_options.preview.get_disable_hw_flow_stat() || CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_ONE_QUEUE || CGlobalInfo::get_queues_mode() == CGlobalInfo::Q_MODE_RSS) { - ret |= TrexPlatformApi::IF_STAT_RX_BYTES_COUNT; + flags |= TrexPlatformApi::IF_STAT_RX_BYTES_COUNT; + num_counters = MAX_FLOW_STATS; + } else { + if (m_if_per_card == 4) { + num_counters = MAX_FLOW_STATS_X710; + } else { + num_counters = MAX_FLOW_STATS_XL710; + } } - return ret; + base_ip_id = IP_ID_RESERVE_BASE; + m_max_flow_stats = num_counters; } virtual int wait_for_stable_link(); virtual bool hw_rx_stat_supported(){ @@ -452,6 +477,7 @@ private: private: uint8_t m_if_per_card; + uint16_t m_max_flow_stats; }; class CTRexExtendedDriverBaseVIC : public CTRexExtendedDriverBase { @@ -484,9 +510,13 @@ public: virtual void reset_rx_stats(CPhyEthIF * _if, uint32_t *stats, int min, int len); virtual int get_rx_stats(CPhyEthIF * _if, uint32_t *pkts, uint32_t *prev_pkts, uint32_t *bytes, uint32_t *prev_bytes, int min, int max); virtual int get_stat_counters_num() {return MAX_FLOW_STATS;} - virtual int get_rx_stat_capabilities() { - return TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_PAYLOAD; + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) { + flags = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT + | TrexPlatformApi::IF_STAT_PAYLOAD; + num_counters = MAX_FLOW_STATS; + base_ip_id = IP_ID_RESERVE_BASE; } + virtual CFlowStatParser *get_flow_stat_parser(); virtual int dump_fdir_global_stats(CPhyEthIF * _if, FILE *fd); virtual int set_rcv_all(CPhyEthIF * _if, bool set_on); @@ -541,9 +571,11 @@ public: virtual void reset_rx_stats(CPhyEthIF * _if, uint32_t *stats, int min, int len); virtual int get_rx_stats(CPhyEthIF * _if, uint32_t *pkts, uint32_t *prev_pkts, uint32_t *bytes, uint32_t *prev_bytes, int min, int max); virtual int dump_fdir_global_stats(CPhyEthIF * _if, FILE *fd); - virtual int get_stat_counters_num() {return MAX_FLOW_STATS;} - virtual int get_rx_stat_capabilities() { - return TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_PAYLOAD; + virtual void get_rx_stat_capabilities(uint16_t &flags, uint16_t &num_counters, uint16_t &base_ip_id) { + flags = TrexPlatformApi::IF_STAT_IPV4_ID | TrexPlatformApi::IF_STAT_RX_BYTES_COUNT + | TrexPlatformApi::IF_STAT_PAYLOAD; + num_counters = 127; //With MAX_FLOW_STATS we saw packet failures in rx_test. Need to check. + base_ip_id = IP_ID_RESERVE_BASE; } virtual int wait_for_stable_link(); // disabling flow control on 40G using DPDK API causes the interface to malfunction @@ -1503,12 +1535,6 @@ void CPhyEthIF::dump_stats_extended(FILE *fd){ } } -int CPhyEthIF::get_rx_stat_capabilities() { - return get_ex_drv()->get_rx_stat_capabilities(); -} - - - void CPhyEthIF::configure(uint16_t nb_rx_queue, uint16_t nb_tx_queue, const struct rte_eth_conf *eth_conf){ @@ -4561,7 +4587,7 @@ CGlobalTRex::publish_async_data(bool sync_now, bool baseline) { if (get_is_stateless()) { std::string stat_json; std::string latency_json; - if (m_trex_stateless->m_rx_flow_stat.dump_json(stat_json, latency_json, baseline)) { + if (m_trex_stateless->m_rx_flow_stat.dump_json(stat_json, latency_json, baseline, sync_now)) { m_zmq_publisher.publish_json(stat_json); m_zmq_publisher.publish_json(latency_json); } @@ -6371,19 +6397,26 @@ int CTRexExtendedDriverBase10G::configure_rx_filter_rules(CPhyEthIF * _if) { int CTRexExtendedDriverBase10G::configure_rx_filter_rules_stateless(CPhyEthIF * _if) { uint8_t port_id = _if->get_rte_port_id(); - int ip_id_lsb; + uint8_t ip_id_lsb; - // 0..MAX_FLOW_STATS-1 is for rules using ip_id. - // MAX_FLOW_STATS rule is for the payload rules. Meaning counter value is in the payload - for (ip_id_lsb = 0; ip_id_lsb <= MAX_FLOW_STATS; ip_id_lsb++ ) { + // 0..128-1 is for rules using ip_id. + // 128 rule is for the payload rules. Meaning counter value is in the payload + for (ip_id_lsb = 0; ip_id_lsb <= 128; ip_id_lsb++ ) { struct rte_eth_fdir_filter fdir_filter; int res = 0; memset(&fdir_filter,0,sizeof(fdir_filter)); fdir_filter.input.flow_type = RTE_ETH_FLOW_NONFRAG_IPV4_OTHER; fdir_filter.soft_id = ip_id_lsb; // We can use the ip_id_lsb also as filter soft_id - fdir_filter.input.flow_ext.flexbytes[0] = 0xff; - fdir_filter.input.flow_ext.flexbytes[1] = ip_id_lsb; + if (ip_id_lsb == 128) { + // payload rule is for 0xffff + fdir_filter.input.flow_ext.flexbytes[0] = 0xff; + fdir_filter.input.flow_ext.flexbytes[1] = 0xff; + } else { + // less than 255 flow stats, so only byte 1 changes + fdir_filter.input.flow_ext.flexbytes[0] = 0xff & (IP_ID_RESERVE_BASE >> 8); + fdir_filter.input.flow_ext.flexbytes[1] = ip_id_lsb; + } fdir_filter.action.rx_queue = 1; fdir_filter.action.behavior = RTE_ETH_FDIR_ACCEPT; fdir_filter.action.report_status = RTE_ETH_FDIR_NO_REPORT_STATUS; @@ -6664,10 +6697,10 @@ extern "C" int rte_eth_fdir_stats_reset(uint8_t port_id, uint32_t *stats, uint32 // type - rule type. Currently we only support rules in IP ID. // proto - Packet protocol: UDP or TCP -// id - Counter id in HW. We assume it is in the range 0..MAX_FLOW_STATS +// id - Counter id in HW. We assume it is in the range 0..m_max_flow_stats int CTRexExtendedDriverBase40G::add_del_rx_flow_stat_rule(uint8_t port_id, enum rte_filter_op op, uint16_t l3_proto , uint8_t l4_proto, uint8_t ipv6_next_h, uint16_t id) { - uint32_t rule_id = (port_id % m_if_per_card) * MAX_FLOW_STATS + id; + uint32_t rule_id = (port_id % m_if_per_card) * m_max_flow_stats + id; uint16_t rte_type = RTE_ETH_FLOW_NONFRAG_IPV4_OTHER; uint8_t next_proto; @@ -6765,7 +6798,7 @@ int CTRexExtendedDriverBase40G::configure_rx_filter_rules(CPhyEthIF * _if) { void CTRexExtendedDriverBase40G::reset_rx_stats(CPhyEthIF * _if, uint32_t *stats, int min, int len) { uint32_t port_id = _if->get_port_id(); - uint32_t rule_id = (port_id % m_if_per_card) * MAX_FLOW_STATS + min; + uint32_t rule_id = (port_id % m_if_per_card) * m_max_flow_stats + min; // Since flow dir counters are not wrapped around as promised in the data sheet, but rather get stuck at 0xffffffff // we reset the HW value @@ -6786,9 +6819,9 @@ extern "C" int rte_eth_fdir_stats_get(uint8_t port_id, uint32_t *stats, uint32_t // bytes and prev_bytes are not used. X710 fdir filters do not support byte count. int CTRexExtendedDriverBase40G::get_rx_stats(CPhyEthIF * _if, uint32_t *pkts, uint32_t *prev_pkts ,uint32_t *bytes, uint32_t *prev_bytes, int min, int max) { - uint32_t hw_stats[MAX_FLOW_STATS]; + uint32_t hw_stats[MAX_FLOW_STATS_XL710]; uint32_t port_id = _if->get_port_id(); - uint32_t start = (port_id % m_if_per_card) * MAX_FLOW_STATS + min; + uint32_t start = (port_id % m_if_per_card) * m_max_flow_stats + min; uint32_t len = max - min + 1; uint32_t loop_start = min; @@ -7494,9 +7527,9 @@ TrexDpdkPlatformApi::publish_async_port_attr_changed(uint8_t port_id) const { } void -TrexDpdkPlatformApi::get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities) const { - num_counters = CTRexExtendedDriverDb::Ins()->get_drv()->get_stat_counters_num(); - capabilities = CTRexExtendedDriverDb::Ins()->get_drv()->get_rx_stat_capabilities(); +TrexDpdkPlatformApi::get_interface_stat_info(uint8_t interface_id, uint16_t &num_counters, uint16_t &capabilities + , uint16_t &ip_id_base) const { + CTRexExtendedDriverDb::Ins()->get_drv()->get_rx_stat_capabilities(capabilities, num_counters ,ip_id_base); } int TrexDpdkPlatformApi::get_flow_stats(uint8 port_id, void *rx_stats, void *tx_stats, int min, int max, bool reset diff --git a/src/pkt_gen.cpp b/src/pkt_gen.cpp index 9dc5ade1..e95398d4 100644 --- a/src/pkt_gen.cpp +++ b/src/pkt_gen.cpp @@ -236,7 +236,7 @@ char *CTestPktGen::create_test_pkt(uint16_t l3_type, uint16_t l4_proto, uint8_t case EthernetHeader::Protocol::IP: ip->setTimeToLive(ttl); if (flags & DPF_TOS_1) { - ip->setTOS(TOS_TTL_RESERVE_DUPLICATE); + ip->setTOS(TOS_GO_TO_CPU); }else{ ip->setTOS(0x2); } @@ -246,7 +246,7 @@ char *CTestPktGen::create_test_pkt(uint16_t l3_type, uint16_t l4_proto, uint8_t case EthernetHeader::Protocol::IPv6: ipv6->setHopLimit(ttl); if (flags & DPF_TOS_1) { - ipv6->setTrafficClass(TOS_TTL_RESERVE_DUPLICATE); + ipv6->setTrafficClass(TOS_GO_TO_CPU); }else{ ipv6->setTrafficClass(0x2); } diff --git a/src/rpc-server/commands/trex_rpc_cmd_stream.cpp b/src/rpc-server/commands/trex_rpc_cmd_stream.cpp index 836dc5de..54945f1c 100644 --- a/src/rpc-server/commands/trex_rpc_cmd_stream.cpp +++ b/src/rpc-server/commands/trex_rpc_cmd_stream.cpp @@ -72,7 +72,7 @@ TrexRpcCmdAddStream::_run(const Json::Value ¶ms, Json::Value &result) { /* check packet size */ if ( (pkt_binary.size() < TrexStream::MIN_PKT_SIZE_BYTES) || (pkt_binary.size() > TrexStream::MAX_PKT_SIZE_BYTES) ) { std::stringstream ss; - ss << "bad packet size provided: should be between " << TrexStream::MIN_PKT_SIZE_BYTES << " and " << TrexStream::MAX_PKT_SIZE_BYTES; + ss << "Bad packet size provided: " << pkt_binary.size() << ". Should be between " << TrexStream::MIN_PKT_SIZE_BYTES << " and " << TrexStream::MAX_PKT_SIZE_BYTES; generate_execute_err(result, ss.str()); } diff --git a/src/stateless/cp/trex_exception.h b/src/stateless/cp/trex_exception.h index ffc2f734..f4d7195a 100644 --- a/src/stateless/cp/trex_exception.h +++ b/src/stateless/cp/trex_exception.h @@ -51,7 +51,6 @@ class TrexException : public std::runtime_error 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, diff --git a/src/stateless/cp/trex_stateless_port.cpp b/src/stateless/cp/trex_stateless_port.cpp index 423cd443..8aa45753 100644 --- a/src/stateless/cp/trex_stateless_port.cpp +++ b/src/stateless/cp/trex_stateless_port.cpp @@ -174,7 +174,8 @@ TrexStatelessPort::TrexStatelessPort(uint8_t port_id, const TrexPlatformApi *api api->get_interface_info(port_id, m_api_info); /* get RX caps */ - api->get_interface_stat_info(port_id, m_rx_count_num, m_rx_caps); + uint16_t ip_id_base; + api->get_interface_stat_info(port_id, m_rx_count_num, m_rx_caps, ip_id_base); /* get the DP cores belonging to this port */ api->port_id_to_cores(m_port_id, core_pair_list); diff --git a/src/stateless/dp/trex_stateless_dp_core.cpp b/src/stateless/dp/trex_stateless_dp_core.cpp index b6aa15be..dd5282b8 100644 --- a/src/stateless/dp/trex_stateless_dp_core.cpp +++ b/src/stateless/dp/trex_stateless_dp_core.cpp @@ -998,7 +998,7 @@ TrexStatelessDpCore::add_stream(TrexStatelessDpPerPort * lp_port, if (stream->m_rx_check.m_enabled) { node->set_stat_needed(); - uint8_t hw_id = stream->m_rx_check.m_hw_id; + uint16_t hw_id = stream->m_rx_check.m_hw_id; assert (hw_id < MAX_FLOW_STATS + MAX_FLOW_STATS_PAYLOAD); node->set_stat_hw_id(hw_id); // no support for cache with flow stat payload rules diff --git a/src/stateless/dp/trex_stream_node.h b/src/stateless/dp/trex_stream_node.h index b74e0f62..9accc3f2 100644 --- a/src/stateless/dp/trex_stream_node.h +++ b/src/stateless/dp/trex_stream_node.h @@ -100,10 +100,10 @@ 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_null_stream; + uint16_t m_stat_hw_id; // hw id used to count rx and tx stats uint16_t m_cache_array_cnt; - uint16_t m_pad12; + uint8_t m_null_stream; + uint8_t m_pad12; stream_state_t m_state; uint8_t m_port_id; @@ -301,7 +301,7 @@ public: m_stat_hw_id = hw_id; } - socket_id_t get_stat_hw_id() { + uint16_t get_stat_hw_id() { return ( m_stat_hw_id ); } diff --git a/src/stateless/rx/trex_stateless_rx_port_mngr.cpp b/src/stateless/rx/trex_stateless_rx_port_mngr.cpp index 0beeae69..17aecb03 100644 --- a/src/stateless/rx/trex_stateless_rx_port_mngr.cpp +++ b/src/stateless/rx/trex_stateless_rx_port_mngr.cpp @@ -24,6 +24,7 @@ #include "common/Network/Packet/Arp.h" #include "pkt_gen.h" #include "trex_stateless_capture.h" +#include "stateless/cp/trex_stateless.h" /************************************** * latency RX feature @@ -36,6 +37,8 @@ RXLatency::RXLatency() { for (int i = 0; i < MAX_FLOW_STATS; i++) { m_rx_pg_stat[i].clear(); + } + for (int i = 0; i < MAX_FLOW_STATS_PAYLOAD; i++) { m_rx_pg_stat_payload[i].clear(); } } @@ -50,6 +53,15 @@ RXLatency::create(CRFC2544Info *rfc2544, CRxCoreErrCntrs *err_cntrs) { } else { m_rcv_all = false; } + + uint16_t num_counters, cap, ip_id_base; + TrexStateless *tstateless = get_stateless_obj(); + assert(tstateless); + + const TrexPlatformApi *api = tstateless->get_platform_api(); + assert(api); + api->get_interface_stat_info(0, num_counters, cap, ip_id_base); + m_ip_id_base = ip_id_base; } void @@ -58,7 +70,7 @@ RXLatency::handle_pkt(const rte_mbuf_t *m) { int ret = parser.parse(rte_pktmbuf_mtod(m, uint8_t *), m->pkt_len); if (m_rcv_all || (ret == 0)) { - uint32_t ip_id; + uint32_t ip_id = 0; int ret2 = parser.get_ip_id(ip_id); if (m_rcv_all || ( ret2 == 0)) { if (m_rcv_all || is_flow_stat_id(ip_id)) { @@ -157,7 +169,7 @@ RXLatency::handle_pkt(const rte_mbuf_t *m) { curr_rfc2544->add_sample(ctime); } } else { - hw_id = get_hw_id(ip_id); + hw_id = get_hw_id((uint16_t)ip_id); if (hw_id < MAX_FLOW_STATS) { m_rx_pg_stat[hw_id].add_pkts(1); m_rx_pg_stat[hw_id].add_bytes(m->pkt_len + 4); // +4 for ethernet CRC diff --git a/src/stateless/rx/trex_stateless_rx_port_mngr.h b/src/stateless/rx/trex_stateless_rx_port_mngr.h index ea360490..b6d929ed 100644 --- a/src/stateless/rx/trex_stateless_rx_port_mngr.h +++ b/src/stateless/rx/trex_stateless_rx_port_mngr.h @@ -55,9 +55,12 @@ public: void reset_stats(); private: + // below functions for both IP v4 and v6, so they need uint32_t id bool is_flow_stat_id(uint32_t id) { - if ((id & 0x000fff00) == IP_ID_RESERVE_BASE) return true; - return false; + if ((uint16_t) id >= m_ip_id_base) + return true; + else + return false; } bool is_flow_stat_payload_id(uint32_t id) { @@ -66,7 +69,7 @@ private: } uint16_t get_hw_id(uint16_t id) { - return (0x00ff & id); + return (~m_ip_id_base & (uint16_t )id); } public: @@ -77,6 +80,7 @@ public: bool m_rcv_all; CRFC2544Info *m_rfc2544; CRxCoreErrCntrs *m_err_cntrs; + uint16_t m_ip_id_base; }; diff --git a/src/trex_defs.h b/src/trex_defs.h index 60a60df9..c4d6531e 100644 --- a/src/trex_defs.h +++ b/src/trex_defs.h @@ -23,8 +23,10 @@ limitations under the License. #define TREX_MAX_PORTS 16 -// maximum number of IP ID type flow stats we support -#define MAX_FLOW_STATS 127 +// maximum number of IP ID type flow stats we support. Must be in the form 2^x - 1 +#define MAX_FLOW_STATS 1023 +#define MAX_FLOW_STATS_X710 127 +#define MAX_FLOW_STATS_XL710 255 // maximum number of payload type flow stats we support #define MAX_FLOW_STATS_PAYLOAD 128 |