DPDK patches and discussions
 help / color / mirror / Atom feed
From: Wisam Jaddo <wisamm@mellanox.com>
To: dev@dpdk.org, thomas@monjalon.net, asafp@mellanox.com,
	akozyrev@nvidia.com, akozyrev@mellanox.com,
	arybchenko@solarflare.com, jackmin@mellanox.com
Subject: [dpdk-dev] [PATCH 03/13] app/flow-perf: start supporting user order
Date: Sun, 30 Aug 2020 11:15:34 +0000	[thread overview]
Message-ID: <20200830111544.4190-4-wisamm@mellanox.com> (raw)
In-Reply-To: <20200830111544.4190-1-wisamm@mellanox.com>

The old design was using the bit mask to identify
items, action and attributes.

So it was all based on the order of the code itself,
to place the order of the actions, items & attributes
inside the flows. Such design will lead into many failures
when some PMD support order different than other PMD,
in the end the rules will fail to create. Also sometimes
the user needs to have one action before other actions
and vice versa, so using new design of arrays that
take user order into consideration make more sense.

After this patch, we start supporting inner items
and more than one instance of same action.

Signed-off-by: Wisam Jaddo <wisamm@mellanox.com>
Acked-by: Alexander Kozyrev <akozyrev@nvidia.com>
---
 app/test-flow-perf/actions_gen.c       |  26 +++---
 app/test-flow-perf/actions_gen.h       |   2 +-
 app/test-flow-perf/config.h            |   1 +
 app/test-flow-perf/flow_gen.c          |  25 +++---
 app/test-flow-perf/flow_gen.h          |   6 +-
 app/test-flow-perf/items_gen.c         |  26 +++---
 app/test-flow-perf/items_gen.h         |   2 +-
 app/test-flow-perf/main.c              | 106 ++++++++++++++++---------
 doc/guides/rel_notes/release_20_08.rst |   8 ++
 doc/guides/tools/flow-perf.rst         |  12 ++-
 10 files changed, 135 insertions(+), 79 deletions(-)

diff --git a/app/test-flow-perf/actions_gen.c b/app/test-flow-perf/actions_gen.c
index cb9316f1df..d115cdd723 100644
--- a/app/test-flow-perf/actions_gen.c
+++ b/app/test-flow-perf/actions_gen.c
@@ -182,14 +182,14 @@ add_count(struct rte_flow_action *actions,
 }
 
 void
-fill_actions(struct rte_flow_action *actions, uint64_t flow_actions,
+fill_actions(struct rte_flow_action *actions, uint64_t *flow_actions,
 	uint32_t counter, uint16_t next_table, uint16_t hairpinq)
 {
 	struct additional_para additional_para_data;
 	uint8_t actions_counter = 0;
 	uint16_t hairpin_queues[hairpinq];
 	uint16_t queues[RXQ_NUM];
-	uint16_t i;
+	uint16_t i, j;
 
 	for (i = 0; i < RXQ_NUM; i++)
 		queues[i] = i;
@@ -217,7 +217,7 @@ fill_actions(struct rte_flow_action *actions, uint64_t flow_actions,
 			uint8_t actions_counter,
 			struct additional_para para
 			);
-	} flows_actions[] = {
+	} actions_list[] = {
 		{
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_MARK),
 			.funct = add_mark,
@@ -264,13 +264,19 @@ fill_actions(struct rte_flow_action *actions, uint64_t flow_actions,
 		},
 	};
 
-	for (i = 0; i < RTE_DIM(flows_actions); i++) {
-		if ((flow_actions & flows_actions[i].mask) == 0)
-			continue;
-		flows_actions[i].funct(
-			actions, actions_counter++,
-			additional_para_data
-		);
+	for (j = 0; j < MAX_ACTIONS_NUM; j++) {
+		if (flow_actions[j] == 0)
+			break;
+		for (i = 0; i < RTE_DIM(actions_list); i++) {
+			if ((flow_actions[j] &
+				actions_list[i].mask) == 0)
+				continue;
+			actions_list[i].funct(
+				actions, actions_counter++,
+				additional_para_data
+			);
+			break;
+		}
 	}
 	actions[actions_counter].type = RTE_FLOW_ACTION_TYPE_END;
 }
diff --git a/app/test-flow-perf/actions_gen.h b/app/test-flow-perf/actions_gen.h
index 0defa7c97c..d6918a53a6 100644
--- a/app/test-flow-perf/actions_gen.h
+++ b/app/test-flow-perf/actions_gen.h
@@ -12,7 +12,7 @@
 
 #include "config.h"
 
-void fill_actions(struct rte_flow_action *actions, uint64_t actions_selector,
+void fill_actions(struct rte_flow_action *actions, uint64_t *flow_actions,
 	uint32_t counter, uint16_t next_table, uint16_t hairpinq);
 
 #endif /* FLOW_PERF_ACTION_GEN */
diff --git a/app/test-flow-perf/config.h b/app/test-flow-perf/config.h
index e47d788572..439f3264b4 100644
--- a/app/test-flow-perf/config.h
+++ b/app/test-flow-perf/config.h
@@ -29,3 +29,4 @@
 /* Flow items/acctions max size */
 #define MAX_ITEMS_NUM 32
 #define MAX_ACTIONS_NUM 32
+#define MAX_ATTRS_NUM 16
diff --git a/app/test-flow-perf/flow_gen.c b/app/test-flow-perf/flow_gen.c
index e87276bd14..b2c828c7df 100644
--- a/app/test-flow-perf/flow_gen.c
+++ b/app/test-flow-perf/flow_gen.c
@@ -18,23 +18,28 @@
 
 static void
 fill_attributes(struct rte_flow_attr *attr,
-	uint64_t flow_attrs, uint16_t group)
+	uint64_t *flow_attrs, uint16_t group)
 {
-	if (flow_attrs & INGRESS)
-		attr->ingress = 1;
-	if (flow_attrs & EGRESS)
-		attr->egress = 1;
-	if (flow_attrs & TRANSFER)
-		attr->transfer = 1;
+	uint8_t i;
+	for (i = 0; i < MAX_ATTRS_NUM; i++) {
+		if (flow_attrs[i] == 0)
+			break;
+		if (flow_attrs[i] & INGRESS)
+			attr->ingress = 1;
+		else if (flow_attrs[i] & EGRESS)
+			attr->egress = 1;
+		else if (flow_attrs[i] & TRANSFER)
+			attr->transfer = 1;
+	}
 	attr->group = group;
 }
 
 struct rte_flow *
 generate_flow(uint16_t port_id,
 	uint16_t group,
-	uint64_t flow_attrs,
-	uint64_t flow_items,
-	uint64_t flow_actions,
+	uint64_t *flow_attrs,
+	uint64_t *flow_items,
+	uint64_t *flow_actions,
 	uint16_t next_table,
 	uint32_t outer_ip_src,
 	uint16_t hairpinq,
diff --git a/app/test-flow-perf/flow_gen.h b/app/test-flow-perf/flow_gen.h
index 848331e229..53469c659f 100644
--- a/app/test-flow-perf/flow_gen.h
+++ b/app/test-flow-perf/flow_gen.h
@@ -26,9 +26,9 @@
 struct rte_flow *
 generate_flow(uint16_t port_id,
 	uint16_t group,
-	uint64_t flow_attrs,
-	uint64_t flow_items,
-	uint64_t flow_actions,
+	uint64_t *flow_attrs,
+	uint64_t *flow_items,
+	uint64_t *flow_actions,
 	uint16_t next_table,
 	uint32_t outer_ip_src,
 	uint16_t hairpinq,
diff --git a/app/test-flow-perf/items_gen.c b/app/test-flow-perf/items_gen.c
index 6a8915100c..cc031f24a5 100644
--- a/app/test-flow-perf/items_gen.c
+++ b/app/test-flow-perf/items_gen.c
@@ -312,10 +312,10 @@ add_meta_tag(struct rte_flow_item *items,
 
 void
 fill_items(struct rte_flow_item *items,
-	uint64_t flow_items, uint32_t outer_ip_src)
+	uint64_t *flow_items, uint32_t outer_ip_src)
 {
 	uint8_t items_counter = 0;
-	uint8_t i;
+	uint8_t i, j;
 	struct additional_para additional_para_data = {
 		.src_ip = outer_ip_src,
 	};
@@ -328,7 +328,7 @@ fill_items(struct rte_flow_item *items,
 			uint8_t items_counter,
 			struct additional_para para
 			);
-	} flows_items[] = {
+	} items_list[] = {
 		{
 			.mask = RTE_FLOW_ITEM_TYPE_META,
 			.funct = add_meta_data,
@@ -384,13 +384,19 @@ fill_items(struct rte_flow_item *items,
 
 	};
 
-	for (i = 0; i < RTE_DIM(flows_items); i++) {
-		if ((flow_items & FLOW_ITEM_MASK(flows_items[i].mask)) == 0)
-			continue;
-		flows_items[i].funct(
-			items, items_counter++,
-			additional_para_data
-		);
+	for (j = 0; j < MAX_ITEMS_NUM; j++) {
+		if (flow_items[j] == 0)
+			break;
+		for (i = 0; i < RTE_DIM(items_list); i++) {
+			if ((flow_items[j] &
+				FLOW_ITEM_MASK(items_list[i].mask)) == 0)
+				continue;
+			items_list[i].funct(
+				items, items_counter++,
+				additional_para_data
+			);
+			break;
+		}
 	}
 
 	items[items_counter].type = RTE_FLOW_ITEM_TYPE_END;
diff --git a/app/test-flow-perf/items_gen.h b/app/test-flow-perf/items_gen.h
index 9509d0f11c..d68958e4d3 100644
--- a/app/test-flow-perf/items_gen.h
+++ b/app/test-flow-perf/items_gen.h
@@ -12,7 +12,7 @@
 
 #include "config.h"
 
-void fill_items(struct rte_flow_item *items, uint64_t flow_items,
+void fill_items(struct rte_flow_item *items, uint64_t *flow_items,
 	uint32_t outer_ip_src);
 
 #endif /* FLOW_PERF_ITEMS_GEN */
diff --git a/app/test-flow-perf/main.c b/app/test-flow-perf/main.c
index 3589b316f9..0ff8080aa0 100644
--- a/app/test-flow-perf/main.c
+++ b/app/test-flow-perf/main.c
@@ -45,9 +45,10 @@
 struct rte_flow *flow;
 static uint8_t flow_group;
 
-static uint64_t flow_items;
-static uint64_t flow_actions;
-static uint64_t flow_attrs;
+static uint64_t flow_items[MAX_ITEMS_NUM];
+static uint64_t flow_actions[MAX_ACTIONS_NUM];
+static uint64_t flow_attrs[MAX_ATTRS_NUM];
+static uint8_t items_idx, actions_idx, attrs_idx;
 
 static volatile bool force_quit;
 static bool dump_iterations;
@@ -150,132 +151,159 @@ args_parse(int argc, char **argv)
 	static const struct option_dict {
 		const char *str;
 		const uint64_t mask;
-		uint64_t *bitmap;
+		uint64_t *map;
+		uint8_t *map_idx;
+
 	} flow_options[] = {
 		{
 			.str = "ether",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ETH),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "ipv4",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_IPV4),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "ipv6",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_IPV6),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "vlan",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_VLAN),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "tcp",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_TCP),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "udp",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_UDP),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "vxlan",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_VXLAN),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "vxlan-gpe",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_VXLAN_GPE),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "gre",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_GRE),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "geneve",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_GENEVE),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "gtp",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_GTP),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "meta",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_META),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "tag",
 			.mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_TAG),
-			.bitmap = &flow_items
+			.map = &flow_items[0],
+			.map_idx = &items_idx
 		},
 		{
 			.str = "ingress",
 			.mask = INGRESS,
-			.bitmap = &flow_attrs
+			.map = &flow_attrs[0],
+			.map_idx = &attrs_idx
 		},
 		{
 			.str = "egress",
 			.mask = EGRESS,
-			.bitmap = &flow_attrs
+			.map = &flow_attrs[0],
+			.map_idx = &attrs_idx
 		},
 		{
 			.str = "transfer",
 			.mask = TRANSFER,
-			.bitmap = &flow_attrs
+			.map = &flow_attrs[0],
+			.map_idx = &attrs_idx
 		},
 		{
 			.str = "port-id",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_PORT_ID),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "rss",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_RSS),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "queue",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_QUEUE),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "jump",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_JUMP),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "mark",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_MARK),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "count",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_COUNT),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "set-meta",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_SET_META),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "set-tag",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_SET_TAG),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		},
 		{
 			.str = "drop",
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_DROP),
-			.bitmap = &flow_actions
+			.map = &flow_actions[0],
+			.map_idx = &actions_idx
 		}
 	};
 
@@ -320,9 +348,6 @@ args_parse(int argc, char **argv)
 		{ "hairpin-rss",                1, 0, 0 },
 	};
 
-	flow_items = 0;
-	flow_actions = 0;
-	flow_attrs = 0;
 	hairpin_queues_num = 0;
 	argvopt = argv;
 
@@ -349,7 +374,8 @@ args_parse(int argc, char **argv)
 			for (i = 0; i < RTE_DIM(flow_options); i++)
 				if (strcmp(lgopts[opt_idx].name,
 						flow_options[i].str) == 0) {
-					*flow_options[i].bitmap |=
+					flow_options[i].map[
+					(*flow_options[i].map_idx)++] =
 						flow_options[i].mask;
 					printf("%s / ", flow_options[i].str);
 				}
@@ -363,7 +389,8 @@ args_parse(int argc, char **argv)
 					rte_exit(EXIT_SUCCESS,
 						"Hairpin queues should be > 0\n");
 
-				flow_actions |= HAIRPIN_RSS_ACTION;
+				flow_actions[actions_idx++] =
+					HAIRPIN_RSS_ACTION;
 				printf("hairpin-rss / ");
 			}
 			if (strcmp(lgopts[opt_idx].name,
@@ -375,7 +402,8 @@ args_parse(int argc, char **argv)
 					rte_exit(EXIT_SUCCESS,
 						"Hairpin queues should be > 0\n");
 
-				flow_actions |= HAIRPIN_QUEUE_ACTION;
+				flow_actions[actions_idx++] =
+					HAIRPIN_QUEUE_ACTION;
 				printf("hairpin-queue / ");
 			}
 
@@ -558,6 +586,11 @@ flows_handler(void)
 	int port_id;
 	int iter_id;
 	uint32_t flow_index;
+	uint64_t global_items[MAX_ITEMS_NUM] = { 0 };
+	uint64_t global_actions[MAX_ACTIONS_NUM] = { 0 };
+
+	global_items[0] = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ETH);
+	global_actions[0] = FLOW_ITEM_MASK(RTE_FLOW_ACTION_TYPE_JUMP);
 
 	nr_ports = rte_eth_dev_count_avail();
 
@@ -587,8 +620,7 @@ flows_handler(void)
 			 *
 			 */
 			flow = generate_flow(port_id, 0, flow_attrs,
-				FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ETH),
-				FLOW_ITEM_MASK(RTE_FLOW_ACTION_TYPE_JUMP),
+				global_items, global_actions,
 				flow_group, 0, 0, &error);
 
 			if (flow == NULL) {
diff --git a/doc/guides/rel_notes/release_20_08.rst b/doc/guides/rel_notes/release_20_08.rst
index a19ec6db2b..d3ba4cd1d0 100644
--- a/doc/guides/rel_notes/release_20_08.rst
+++ b/doc/guides/rel_notes/release_20_08.rst
@@ -272,6 +272,14 @@ New Features
   of ingress packets to specific NIC queues.
   See the :doc:`../sample_app_ug/ipsec_secgw` for more details.
 
+* **Add more support for flow-perf application.**
+
+  * Start supporting user order instead of bit mask:
+    Now the user can create any structure of rte_flow
+    using flow performance application with any order,
+    moreover the app also now starts to support inner
+    items matching as well.
+
 
 Removed Items
 -------------
diff --git a/doc/guides/tools/flow-perf.rst b/doc/guides/tools/flow-perf.rst
index ca551aee6e..6941155fee 100644
--- a/doc/guides/tools/flow-perf.rst
+++ b/doc/guides/tools/flow-perf.rst
@@ -16,20 +16,18 @@ After that the application will start producing rules with same pattern
 but increasing the outer IP source address by 1 each time, thus it will
 give different flow each time, and all other items will have open masks.
 
+The application also provide the ability to measure rte flow deletion rate,
+in addition to memory consumption before and after the flows creation.
+
+The app supports single and multi core performance measurements.
+
 
 Known Limitations
 -----------------
 
 The current version has limitations which can be removed in future:
 
-* Support outer items up to tunnel layer only.
 * Single core insertion only.
-* Only one instance of same action can be added in one rule.
-
-The application also provide the ability to measure rte flow deletion rate,
-in addition to memory consumption before and after the flows creation.
-
-The app supports single and multi core performance measurements.
 
 
 Compiling the Application
-- 
2.17.1


  parent reply	other threads:[~2020-08-30 11:19 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-30 11:15 [dpdk-dev] [PATCH 00/13] app/flow-perf: add support for new items/actions Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 01/13] app/flow-perf: fix actions mask macro usage Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 02/13] doc/flow-perf: fix app sections Wisam Jaddo
2020-08-30 11:15 ` Wisam Jaddo [this message]
2020-08-30 11:15 ` [dpdk-dev] [PATCH 04/13] app/flow-perf: add header modify actions support Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 05/13] app/flow-perf: add flag action support Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 06/13] app/flow-perf: fix memory leak from RSS action Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 07/13] app/flow-perf: add raw encap/decap actions support Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 08/13] app/flow-perf: add VXLAN " Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 09/13] app/flow-perf: fix source ipv4 matching Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 10/13] app/flow-perf: add random mark id values Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 11/13] app/flow-perf: add set port mask to options Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 12/13] app/flow-perf: add icmp matching support Wisam Jaddo
2020-08-30 11:15 ` [dpdk-dev] [PATCH 13/13] app/flow-perf: allow fixed values for actions Wisam Jaddo
2020-09-14 18:15 ` [dpdk-dev] [PATCH 00/13] app/flow-perf: add support for new items/actions Ferruh Yigit
2020-09-21 21:36 ` Thomas Monjalon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200830111544.4190-4-wisamm@mellanox.com \
    --to=wisamm@mellanox.com \
    --cc=akozyrev@mellanox.com \
    --cc=akozyrev@nvidia.com \
    --cc=arybchenko@solarflare.com \
    --cc=asafp@mellanox.com \
    --cc=dev@dpdk.org \
    --cc=jackmin@mellanox.com \
    --cc=thomas@monjalon.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).