* [PATCH v2] usertools: add tool to generate balanced rss traffic flows
@ 2023-06-28 13:47 Robin Jarry
2023-07-20 4:50 ` Thomas Monjalon
0 siblings, 1 reply; 4+ messages in thread
From: Robin Jarry @ 2023-06-28 13:47 UTC (permalink / raw)
To: dev
Cc: Robin Jarry, Olivier Matz, Jean-Mickael Guerin, 6WIND, Anatoly Burakov
usage: dpdk-rss-flows.py [-h] [-s SPORT_RANGE] [-d DPORT_RANGE] [-r]
[-k RSS_KEY] [-t RETA_SIZE] [-a] [-j]
RX_QUEUES SRC DST
Craft IP{v6}/{TCP/UDP} traffic flows that will evenly spread over a
given number of RX queues according to the RSS algorithm.
positional arguments:
RX_QUEUES The number of RX queues to fill.
SRC The source IP network/address.
DST The destination IP network/address.
options:
-h, --help show this help message and exit
-s SPORT_RANGE, --sport-range SPORT_RANGE
The layer 4 (TCP/UDP) source port range. Can
be a single fixed value or a range
<start>-<end>.
-d DPORT_RANGE, --dport-range DPORT_RANGE
The layer 4 (TCP/UDP) destination port range.
Can be a single fixed value or a range
<start>-<end>.
-r, --check-reverse-traffic
The reversed traffic (source <-> dest) should
also be evenly balanced in the queues.
-k RSS_KEY, --rss-key RSS_KEY
The random 40-bytes key used to compute the
RSS hash. This option supports either a well-
known name or the hex value of the key (well-
known names: "intel", "mlx", default:
"intel").
-t RETA_SIZE, --reta-size RETA_SIZE
Size of the redirection table or "RETA"
(default: 128).
-a, --all-flows Output ALL flows that can be created based on
source and destination address/port ranges
along their matched queue number. ATTENTION:
this option can produce very long outputs
depending on the address and port range sizes.
-j, --json Output in parseable JSON format.
Examples:
~$ dpdk-rss-flows.py 8 28.0.0.0/24 40.0.0.0/24
SRC_IP DST_IP QUEUE
28.0.0.1 40.0.0.1 5
28.0.0.1 40.0.0.2 4
28.0.0.1 40.0.0.3 2
28.0.0.1 40.0.0.6 3
28.0.0.1 40.0.0.8 0
28.0.0.1 40.0.0.9 6
28.0.0.1 40.0.0.10 7
28.0.0.1 40.0.0.11 1
~$ dpdk-rss-flows.py 8 28.0.0.0/24 40.0.0.0/24 -r
SRC_IP DST_IP QUEUE QUEUE_REVERSE
28.0.0.1 40.0.0.1 5 3
28.0.0.1 40.0.0.2 4 2
28.0.0.1 40.0.0.8 0 6
28.0.0.1 40.0.0.9 6 7
28.0.0.1 40.0.0.16 2 4
28.0.0.1 40.0.0.19 3 5
28.0.0.1 40.0.0.24 1 0
28.0.0.1 40.0.0.25 7 1
~$ dpdk-rss-flows.py 8 28.0.0.0/24 40.0.0.0/24 -s 32000-64000 -d 53
SRC_IP SPORT DST_IP DPORT QUEUE
28.0.0.1 32000 40.0.0.1 53 0
28.0.0.1 32001 40.0.0.1 53 1
28.0.0.1 32004 40.0.0.1 53 4
28.0.0.1 32005 40.0.0.1 53 5
28.0.0.1 32008 40.0.0.1 53 2
28.0.0.1 32009 40.0.0.1 53 3
28.0.0.1 32012 40.0.0.1 53 6
28.0.0.1 32013 40.0.0.1 53 7
~$ dpdk-rss-flows.py 4 2a01:cb00:f8b:9700::/64 2620:52:0:2592::/64 -rj
[
{
"queue": 0,
"queue_reverse": 3,
"src_ip": "2a01:cb00:f8b:9700::1",
"dst_ip": "2620:52:0:2592::1",
"src_port": 0,
"dst_port": 0
},
{
"queue": 3,
"queue_reverse": 0,
"src_ip": "2a01:cb00:f8b:9700::1",
"dst_ip": "2620:52:0:2592::2",
"src_port": 0,
"dst_port": 0
},
{
"queue": 2,
"queue_reverse": 1,
"src_ip": "2a01:cb00:f8b:9700::1",
"dst_ip": "2620:52:0:2592::3",
"src_port": 0,
"dst_port": 0
},
{
"queue": 1,
"queue_reverse": 2,
"src_ip": "2a01:cb00:f8b:9700::1",
"dst_ip": "2620:52:0:2592::1a",
"src_port": 0,
"dst_port": 0
}
]
Cc: Olivier Matz <olivier.matz@6wind.com>
Cc: Jean-Mickael Guerin <jean-mickael.guerin@6wind.com>
Signed-off-by: Robin Jarry <rjarry@redhat.com>
Signed-off-by: 6WIND <grumly@6wind.com>
Acked-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v2:
* fixed author
* added i40e default key
* allow 52 bytes keys (for i40e, iavf and ice drivers)
* added -a/--all-flows option
| 418 ++++++++++++++++++++++++++++++++++++
usertools/meson.build | 1 +
2 files changed, 419 insertions(+)
create mode 100755 usertools/dpdk-rss-flows.py
--git a/usertools/dpdk-rss-flows.py b/usertools/dpdk-rss-flows.py
new file mode 100755
index 000000000000..4cdc524ddcb4
--- /dev/null
+++ b/usertools/dpdk-rss-flows.py
@@ -0,0 +1,418 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2014 6WIND S.A.
+# Copyright (c) 2023 Robin Jarry
+
+"""
+Craft IP{v6}/{TCP/UDP} traffic flows that will evenly spread over a given
+number of RX queues according to the RSS algorithm.
+"""
+
+import argparse
+import binascii
+import ctypes
+import ipaddress
+import json
+import struct
+import typing
+
+
+Address = typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
+Network = typing.Union[ipaddress.IPv4Network, ipaddress.IPv6Network]
+PortList = typing.Iterable[int]
+
+
+class Packet:
+ def __init__(self, ip_src: Address, ip_dst: Address, l4_sport: int, l4_dport: int):
+ self.ip_src = ip_src
+ self.ip_dst = ip_dst
+ self.l4_sport = l4_sport
+ self.l4_dport = l4_dport
+
+ def reverse(self):
+ return Packet(
+ ip_src=self.ip_dst,
+ l4_sport=self.l4_dport,
+ ip_dst=self.ip_src,
+ l4_dport=self.l4_sport,
+ )
+
+ def hash_data(self, use_l4_port: bool = False) -> bytes:
+ data = self.ip_src.packed + self.ip_dst.packed
+ if use_l4_port:
+ data += struct.pack(">H", self.l4_sport)
+ data += struct.pack(">H", self.l4_dport)
+ return data
+
+
+class TrafficTemplate:
+ def __init__(
+ self,
+ ip_src: Network,
+ ip_dst: Network,
+ l4_sport_range: PortList,
+ l4_dport_range: PortList,
+ ):
+ self.ip_src = ip_src
+ self.ip_dst = ip_dst
+ self.l4_sport_range = l4_sport_range
+ self.l4_dport_range = l4_dport_range
+
+ def __iter__(self) -> typing.Iterator[Packet]:
+ for ip_src in self.ip_src.hosts():
+ for ip_dst in self.ip_dst.hosts():
+ if ip_src == ip_dst:
+ continue
+ for sport in self.l4_sport_range:
+ for dport in self.l4_dport_range:
+ yield Packet(ip_src, ip_dst, sport, dport)
+
+
+class RSSAlgo:
+ def __init__(
+ self,
+ queues_count: int,
+ key: bytes,
+ reta_size: int,
+ use_l4_port: bool,
+ ):
+ self.queues_count = queues_count
+ self.reta = tuple(i % queues_count for i in range(reta_size))
+ self.key = key
+ self.use_l4_port = use_l4_port
+
+ def toeplitz_hash(self, data: bytes) -> int:
+ # see rte_softrss_* in lib/hash/rte_thash.h
+ hash_value = ctypes.c_uint32(0)
+
+ for i, byte in enumerate(data):
+ for j in range(8):
+ bit = (byte >> (7 - j)) & 0x01
+
+ if bit == 1:
+ keyword = ctypes.c_uint32(0)
+ keyword.value |= self.key[i] << 24
+ keyword.value |= self.key[i + 1] << 16
+ keyword.value |= self.key[i + 2] << 8
+ keyword.value |= self.key[i + 3]
+
+ if j > 0:
+ keyword.value <<= j
+ keyword.value |= self.key[i + 4] >> (8 - j)
+
+ hash_value.value ^= keyword.value
+
+ return hash_value.value
+
+ def get_queue_index(self, packet: Packet) -> int:
+ bytes_to_hash = packet.hash_data(self.use_l4_port)
+
+ # get the 32bit hash of the packet
+ hash_value = self.toeplitz_hash(bytes_to_hash)
+
+ # determine the offset in the redirection table
+ offset = hash_value & (len(self.reta) - 1)
+
+ return self.reta[offset]
+
+
+def balanced_traffic(
+ algo: RSSAlgo,
+ traffic_template: TrafficTemplate,
+ check_reverse_traffic: bool = False,
+ all_flows: bool = False,
+) -> typing.Iterator[typing.Tuple[int, int, Packet]]:
+ queues = set()
+ if check_reverse_traffic:
+ queues_reverse = set()
+
+ for pkt in traffic_template:
+ q = algo.get_queue_index(pkt)
+
+ # check if q is already filled
+ if not all_flows and q in queues:
+ continue
+
+ qr = algo.get_queue_index(pkt.reverse())
+
+ if check_reverse_traffic:
+ # check if q is already filled
+ if not all_flows and qr in queues_reverse:
+ continue
+ # mark this queue as matched
+ queues_reverse.add(qr)
+
+ # mark this queue as filled
+ queues.add(q)
+
+ yield q, qr, pkt
+
+ # stop when all queues have been filled
+ if not all_flows and len(queues) == algo.queues_count:
+ break
+
+
+NO_PORT = (0,)
+
+# fmt: off
+# rss_intel_key, see drivers/net/ixgbe/ixgbe_rxtx.c
+RSS_KEY_INTEL = bytes(
+ (
+ 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
+ 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
+ 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
+ 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
+ 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa,
+ )
+)
+# rss_hash_default_key, see drivers/net/mlx5/mlx5_rxq.c
+RSS_KEY_MLX = bytes(
+ (
+ 0x2c, 0xc6, 0x81, 0xd1, 0x5b, 0xdb, 0xf4, 0xf7,
+ 0xfc, 0xa2, 0x83, 0x19, 0xdb, 0x1a, 0x3e, 0x94,
+ 0x6b, 0x9e, 0x38, 0xd9, 0x2c, 0x9c, 0x03, 0xd1,
+ 0xad, 0x99, 0x44, 0xa7, 0xd9, 0x56, 0x3d, 0x59,
+ 0x06, 0x3c, 0x25, 0xf3, 0xfc, 0x1f, 0xdc, 0x2a,
+ )
+)
+# rss_key_default, see drivers/net/i40e/i40e_ethdev.c
+# i40e is the only driver that takes 52 bytes keys
+RSS_KEY_I40E = bytes(
+ (
+ 0x6b, 0x79, 0x39, 0x44, 0x23, 0x50, 0x4c, 0xb5,
+ 0x5b, 0xea, 0x75, 0xb6, 0x30, 0x9f, 0x4f, 0x12,
+ 0x3d, 0xc0, 0xa2, 0xb8, 0x02, 0x4d, 0xdc, 0xdf,
+ 0x33, 0x9b, 0x8c, 0xa0, 0x4c, 0x4a, 0xf6, 0x4a,
+ 0x34, 0xfa, 0xc6, 0x05, 0x55, 0xd8, 0x58, 0x39,
+ 0x3a, 0x58, 0x99, 0x7d, 0x2e, 0xc9, 0x38, 0xe1,
+ 0x66, 0x03, 0x15, 0x81,
+ )
+)
+# fmt: on
+DEFAULT_DRIVER_KEYS = {
+ "intel": RSS_KEY_INTEL,
+ "mlx": RSS_KEY_MLX,
+ "i40e": RSS_KEY_I40E,
+}
+
+
+def rss_key(value):
+ if value in DEFAULT_DRIVER_KEYS:
+ return DEFAULT_DRIVER_KEYS[value]
+ try:
+ key = binascii.unhexlify(value)
+ if len(key) not in (40, 52):
+ raise argparse.ArgumentTypeError("The key must be 40 or 52 bytes long")
+ return key
+ except (TypeError, ValueError) as e:
+ raise argparse.ArgumentTypeError(str(e)) from e
+
+
+def port_range(value):
+ try:
+ if "-" in value:
+ start, stop = value.split("-")
+ res = tuple(range(int(start), int(stop)))
+ else:
+ res = (int(value),)
+ return res or NO_PORT
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(str(e)) from e
+
+
+def positive_int(value):
+ try:
+ i = int(value)
+ if i <= 0:
+ raise argparse.ArgumentTypeError("must be strictly positive")
+ return i
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(str(e)) from e
+
+
+def power_of_two(value):
+ i = positive_int(value)
+ if i & (i - 1) != 0:
+ raise argparse.ArgumentTypeError("must be a power of two")
+ return i
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description=__doc__)
+
+ parser.add_argument(
+ "rx_queues",
+ metavar="RX_QUEUES",
+ type=positive_int,
+ help="""
+ The number of RX queues to fill.
+ """,
+ )
+ parser.add_argument(
+ "ip_src",
+ metavar="SRC",
+ type=ipaddress.ip_network,
+ help="""
+ The source IP network/address.
+ """,
+ )
+ parser.add_argument(
+ "ip_dst",
+ metavar="DST",
+ type=ipaddress.ip_network,
+ help="""
+ The destination IP network/address.
+ """,
+ )
+ parser.add_argument(
+ "-s",
+ "--sport-range",
+ type=port_range,
+ default=NO_PORT,
+ help="""
+ The layer 4 (TCP/UDP) source port range.
+ Can be a single fixed value or a range <start>-<end>.
+ """,
+ )
+ parser.add_argument(
+ "-d",
+ "--dport-range",
+ type=port_range,
+ default=NO_PORT,
+ help="""
+ The layer 4 (TCP/UDP) destination port range.
+ Can be a single fixed value or a range <start>-<end>.
+ """,
+ )
+ parser.add_argument(
+ "-r",
+ "--check-reverse-traffic",
+ action="store_true",
+ help="""
+ The reversed traffic (source <-> dest) should also be evenly balanced
+ in the queues.
+ """,
+ )
+ parser.add_argument(
+ "-k",
+ "--rss-key",
+ default=RSS_KEY_INTEL,
+ type=rss_key,
+ help="""
+ The random 40-bytes key used to compute the RSS hash. This option
+ supports either a well-known name or the hex value of the key
+ (well-known names: "intel", "mlx", default: "intel").
+ """,
+ )
+ parser.add_argument(
+ "-t",
+ "--reta-size",
+ default=128,
+ type=power_of_two,
+ help="""
+ Size of the redirection table or "RETA" (default: 128).
+ """,
+ )
+ parser.add_argument(
+ "-a",
+ "--all-flows",
+ action="store_true",
+ help="""
+ Output ALL flows that can be created based on source and destination
+ address/port ranges along their matched queue number. ATTENTION: this
+ option can produce very long outputs depending on the address and port
+ range sizes.
+ """,
+ )
+ parser.add_argument(
+ "-j",
+ "--json",
+ action="store_true",
+ help="""
+ Output in parseable JSON format.
+ """,
+ )
+
+ args = parser.parse_args()
+
+ if args.ip_src.version != args.ip_dst.version:
+ parser.error(
+ f"{args.ip_src} and {args.ip_dst} don't have the same protocol version"
+ )
+ if args.reta_size < args.rx_queues:
+ parser.error("RETA_SIZE must be greater than or equal to RX_QUEUES")
+
+ return args
+
+
+def main():
+ args = parse_args()
+ use_l4_port = args.sport_range != NO_PORT or args.dport_range != NO_PORT
+
+ algo = RSSAlgo(
+ queues_count=args.rx_queues,
+ key=args.rss_key,
+ reta_size=args.reta_size,
+ use_l4_port=use_l4_port,
+ )
+ template = TrafficTemplate(
+ args.ip_src,
+ args.ip_dst,
+ args.sport_range,
+ args.dport_range,
+ )
+
+ results = balanced_traffic(
+ algo, template, args.check_reverse_traffic, args.all_flows
+ )
+
+ if args.json:
+ flows = []
+ for q, qr, pkt in results:
+ flows.append(
+ {
+ "queue": q,
+ "queue_reverse": qr,
+ "src_ip": str(pkt.ip_src),
+ "dst_ip": str(pkt.ip_dst),
+ "src_port": pkt.l4_sport,
+ "dst_port": pkt.l4_dport,
+ }
+ )
+ print(json.dumps(flows, indent=2))
+ return
+
+ if use_l4_port:
+ header = ["SRC_IP", "SPORT", "DST_IP", "DPORT", "QUEUE"]
+ else:
+ header = ["SRC_IP", "DST_IP", "QUEUE"]
+ if args.check_reverse_traffic:
+ header.append("QUEUE_REVERSE")
+
+ rows = [tuple(header)]
+ widths = [len(h) for h in header]
+
+ for q, qr, pkt in results:
+ if use_l4_port:
+ row = [pkt.ip_src, pkt.l4_sport, pkt.ip_dst, pkt.l4_dport, q]
+ else:
+ row = [pkt.ip_src, pkt.ip_dst, q]
+ if args.check_reverse_traffic:
+ row.append(qr)
+ cells = []
+ for i, r in enumerate(row):
+ r = str(r)
+ if len(r) > widths[i]:
+ widths[i] = len(r)
+ cells.append(r)
+ rows.append(tuple(cells))
+
+ fmt = [f"%-{w}s" for w in widths]
+ fmt[-1] = "%s" # avoid trailing whitespace
+ fmt = " ".join(fmt)
+ for row in rows:
+ print(fmt % row)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/usertools/meson.build b/usertools/meson.build
index b6271a207cce..0efa4a86d97c 100644
--- a/usertools/meson.build
+++ b/usertools/meson.build
@@ -6,5 +6,6 @@ install_data([
'dpdk-pmdinfo.py',
'dpdk-telemetry.py',
'dpdk-hugepages.py',
+ 'dpdk-rss-flows.py',
],
install_dir: 'bin')
--
2.41.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] usertools: add tool to generate balanced rss traffic flows
2023-06-28 13:47 [PATCH v2] usertools: add tool to generate balanced rss traffic flows Robin Jarry
@ 2023-07-20 4:50 ` Thomas Monjalon
2023-07-20 8:00 ` Robin Jarry
0 siblings, 1 reply; 4+ messages in thread
From: Thomas Monjalon @ 2023-07-20 4:50 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev, Olivier Matz, Jean-Mickael Guerin, Anatoly Burakov
28/06/2023 15:47, Robin Jarry:
> usage: dpdk-rss-flows.py [-h] [-s SPORT_RANGE] [-d DPORT_RANGE] [-r]
> [-k RSS_KEY] [-t RETA_SIZE] [-a] [-j]
> RX_QUEUES SRC DST
>
> Craft IP{v6}/{TCP/UDP} traffic flows that will evenly spread over a
> given number of RX queues according to the RSS algorithm.
[...]
> usertools/dpdk-rss-flows.py | 418 ++++++++++++++++++++++++++++++++++++
> usertools/meson.build | 1 +
Applied, thanks.
You have great skills for user tools in Python,
and this file as other ones have no official maintainer:
usertools/dpdk-devbind.py
usertools/dpdk-hugepages.py
usertools/dpdk-rss-flows.py
Would like to take care of them?
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] usertools: add tool to generate balanced rss traffic flows
2023-07-20 4:50 ` Thomas Monjalon
@ 2023-07-20 8:00 ` Robin Jarry
2023-07-20 14:31 ` Thomas Monjalon
0 siblings, 1 reply; 4+ messages in thread
From: Robin Jarry @ 2023-07-20 8:00 UTC (permalink / raw)
To: Thomas Monjalon; +Cc: dev, Olivier Matz, Jean-Mickael Guerin, Anatoly Burakov
Hi Thomas,
Thomas Monjalon, Jul 20, 2023 at 06:50:
> Applied, thanks.
Thanks! I think there's a byte order issue with the i40e rss key. If
I submit a fix today, can it make it for GA?
> You have great skills for user tools in Python,
> and this file as other ones have no official maintainer:
> usertools/dpdk-devbind.py
> usertools/dpdk-hugepages.py
> usertools/dpdk-rss-flows.py
>
> Would like to take care of them?
Thanks for the offer. I'll gladly adopt anything python related in DPDK.
Cheers,
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] usertools: add tool to generate balanced rss traffic flows
2023-07-20 8:00 ` Robin Jarry
@ 2023-07-20 14:31 ` Thomas Monjalon
0 siblings, 0 replies; 4+ messages in thread
From: Thomas Monjalon @ 2023-07-20 14:31 UTC (permalink / raw)
To: Robin Jarry
Cc: dev, Olivier Matz, Jean-Mickael Guerin, Anatoly Burakov, techboard
20/07/2023 10:00, Robin Jarry:
> Hi Thomas,
>
> Thomas Monjalon, Jul 20, 2023 at 06:50:
> > Applied, thanks.
>
> Thanks! I think there's a byte order issue with the i40e rss key. If
> I submit a fix today, can it make it for GA?
Yes you can.
> > You have great skills for user tools in Python,
> > and this file as other ones have no official maintainer:
> > usertools/dpdk-devbind.py
> > usertools/dpdk-hugepages.py
> > usertools/dpdk-rss-flows.py
> >
> > Would like to take care of them?
>
> Thanks for the offer. I'll gladly adopt anything python related in DPDK.
OK good, let's discuss this in a techboard meeting.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2023-07-20 14:31 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-28 13:47 [PATCH v2] usertools: add tool to generate balanced rss traffic flows Robin Jarry
2023-07-20 4:50 ` Thomas Monjalon
2023-07-20 8:00 ` Robin Jarry
2023-07-20 14:31 ` Thomas Monjalon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).