DPDK patches and discussions
 help / color / mirror / Atom feed
From: Vamsi Krishna Attunuru <vattunuru@marvell.com>
To: "Yan, Zhirun" <zhirun.yan@intel.com>,
	"dev@dpdk.org" <dev@dpdk.org>,
	"thomas@monjalon.net" <thomas@monjalon.net>,
	Jerin Jacob Kollanukkaran <jerinj@marvell.com>
Cc: Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>,
	"Liang, Cunming" <cunming.liang@intel.com>,
	"Wang, Haiyue" <haiyue.wang@intel.com>,
	Sunil Kumar Kori <skori@marvell.com>
Subject: RE: [PATCH v2 4/4] app: add testgraph application
Date: Mon, 22 May 2023 07:07:02 +0000	[thread overview]
Message-ID: <MW4PR18MB5244614BD3F23CE1734B498EA6439@MW4PR18MB5244.namprd18.prod.outlook.com> (raw)
In-Reply-To: <SN7PR11MB6775BFEE43A4AE9A22CC30E885759@SN7PR11MB6775.namprd11.prod.outlook.com>

Thanks Yan for the review comments.

> -----Original Message-----
> From: Yan, Zhirun <zhirun.yan@intel.com>
> Sent: Friday, May 12, 2023 4:38 PM
> To: Vamsi Krishna Attunuru <vattunuru@marvell.com>; dev@dpdk.org;
> thomas@monjalon.net; Jerin Jacob Kollanukkaran <jerinj@marvell.com>
> Cc: Nithin Kumar Dabilpuram <ndabilpuram@marvell.com>; Liang, Cunming
> <cunming.liang@intel.com>; Wang, Haiyue <haiyue.wang@intel.com>; Sunil
> Kumar Kori <skori@marvell.com>
> Subject: [EXT] RE: [PATCH v2 4/4] app: add testgraph application
> 
> External Email
> 
> ----------------------------------------------------------------------
> 
> 
> > -----Original Message-----
> > From: Vamsi Attunuru <vattunuru@marvell.com>
> > Sent: Tuesday, April 25, 2023 9:15 PM
> > To: dev@dpdk.org; thomas@monjalon.net; jerinj@marvell.com
> > Cc: vattunuru@marvell.com; ndabilpuram@marvell.com
> > Subject: [PATCH v2 4/4] app: add testgraph application
> >
> > Patch adds test-graph application to validate graph
> > and node libraries.
> >
> > Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
> > ---
> >  app/meson.build                |    1 +
> >  app/test-graph/cmdline.c       |  211 +++++
> >  app/test-graph/cmdline_graph.c |  294 +++++++
> >  app/test-graph/cmdline_graph.h |   19 +
> >  app/test-graph/meson.build     |   14 +
> >  app/test-graph/parameters.c    |  157 ++++
> >  app/test-graph/testgraph.c     | 1426
> ++++++++++++++++++++++++++++++++
> >  app/test-graph/testgraph.h     |   91 ++
> >  doc/guides/tools/index.rst     |    1 +
> >  doc/guides/tools/testgraph.rst |  131 +++
> >  10 files changed, 2345 insertions(+)
> >
> > diff --git a/app/meson.build b/app/meson.build
> > index 74d2420f67..6c7b24e604 100644
> > --- a/app/meson.build
> > +++ b/app/meson.build
> > @@ -22,6 +22,7 @@ apps = [
> >          'test-eventdev',
> >          'test-fib',
> >          'test-flow-perf',
> > +        'test-graph',
> >          'test-gpudev',
> >          'test-mldev',
> >          'test-pipeline',
> > diff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c
> > new file mode 100644
> > index 0000000000..d9474d827a
> > --- /dev/null
> > +++ b/app/test-graph/cmdline.c
> > @@ -0,0 +1,211 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <stdlib.h>
> > +
> > +#include <cmdline.h>
> > +#include <cmdline_parse.h>
> > +#include <cmdline_parse_etheraddr.h>
> > +#include <cmdline_parse_ipaddr.h>
> > +#include <cmdline_parse_num.h>
> > +#include <cmdline_parse_string.h>
> > +#include <cmdline_rdline.h>
> > +#include <cmdline_socket.h>
> > +
> > +#include "cmdline_graph.h"
> > +#include "testgraph.h"
> > +
> > +static struct cmdline *testgraph_cl;
> > +static cmdline_parse_ctx_t *main_ctx;
> > +
> > +/* *** Help command with introduction. *** */
> > +struct cmd_help_brief_result {
> > +	cmdline_fixed_string_t help;
> > +};
> > +
> > +static void
> > +cmd_help_brief_parsed(__rte_unused void *parsed_result, struct
> cmdline *cl,
> > __rte_unused void *data)
> > +{
> > +	cmdline_printf(cl,
> > +		       "\n"
> > +		       "Help is available for the following sections:\n\n"
> > +		       "    help control                    : Start and stop graph walk.\n"
> > +		       "    help display                    : Displaying port, stats and config
> > "
> > +		       "information.\n"
> > +		       "    help config                     : Configuration information.\n"
> > +		       "    help all                        : All of the above sections.\n\n");
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_help_brief_help =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help,
> "help");
> > +
> > +static cmdline_parse_inst_t cmd_help_brief = {
> > +	.f = cmd_help_brief_parsed,
> > +	.data = NULL,
> > +	.help_str = "help: Show help",
> > +	.tokens = {
> > +			(void *)&cmd_help_brief_help,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Help command with help sections. *** */
> > +struct cmd_help_long_result {
> > +	cmdline_fixed_string_t help;
> > +	cmdline_fixed_string_t section;
> > +};
> > +
> > +static void
> > +cmd_help_long_parsed(void *parsed_result, struct cmdline *cl,
> __rte_unused
> > void *data)
> > +{
> > +	int show_all = 0;
> > +	struct cmd_help_long_result *res = parsed_result;
> > +
> > +	if (!strcmp(res->section, "all"))
> > +		show_all = 1;
> > +
> > +	if (show_all || !strcmp(res->section, "control")) {
> > +
> > +		cmdline_printf(cl, "\n"
> > +				   "Control forwarding:\n"
> > +				   "-------------------\n\n"
> > +
> > +				   "start graph_walk\n"
> > +				   " Start graph_walk on worker threads.\n\n"
> > +
> > +				   "stop graph_walk\n"
> > +				   " Stop worker threads from running
> > graph_walk.\n\n"
> > +
> > +				   "quit\n"
> > +				   "    Quit to prompt.\n\n");
> > +	}
> > +
> > +	if (show_all || !strcmp(res->section, "display")) {
> > +
> > +		cmdline_printf(cl,
> > +			       "\n"
> > +			       "Display:\n"
> > +			       "--------\n\n"
> > +
> > +			       "show node_list\n"
> > +			       " Display the list of supported nodes.\n\n"
> > +
> > +			       "show graph_stats\n"
> > +			       " Display the node statistics of graph
> cluster.\n\n");
> > +	}
> > +
> > +	if (show_all || !strcmp(res->section, "config")) {
> > +		cmdline_printf(cl, "\n"
> > +				   "Configuration:\n"
> > +				   "--------------\n"
> > +				   "set lcore_config
> > (port_id0,rxq0,lcore_idX),..."
> > +				   ".....,(port_idX,rxqX,lcoreidY)\n"
> > +				   " Set lcore configuration.\n\n"
> > +
> > +				   "create_graph
> > (node0_name,node1_name,...,nodeX_name)\n"
> > +				   " Create graph instances using the provided
> > node details.\n\n"
> > +
> > +				   "destroy_graph\n"
> > +				   " Destroy the graph instances.\n\n");
> > +	}
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_help_long_help =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help,
> "help");
> > +
> > +static cmdline_parse_token_string_t cmd_help_long_section =
> > TOKEN_STRING_INITIALIZER(
> > +	struct cmd_help_long_result, section, "all#control#display#config");
> > +
> > +static cmdline_parse_inst_t cmd_help_long = {
> > +	.f = cmd_help_long_parsed,
> > +	.data = NULL,
> > +	.help_str = "help all|control|display|config: "
> > +		    "Show help",
> > +	.tokens = {
> > +			(void *)&cmd_help_long_help,
> > +			(void *)&cmd_help_long_section,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** QUIT *** */
> > +struct cmd_quit_result {
> > +	cmdline_fixed_string_t quit;
> > +};
> > +
> > +static void
> > +cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl,
> > __rte_unused void *data)
> > +{
> > +	cmdline_quit(cl);
> > +}
> > +
> > +static cmdline_parse_token_string_t cmd_quit_quit =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, "quit");
> > +
> > +static cmdline_parse_inst_t cmd_quit = {
> > +	.f = cmd_quit_parsed,
> > +	.data = NULL,
> > +	.help_str = "quit: Exit application",
> > +	.tokens = {
> > +			(void *)&cmd_quit_quit,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* list of instructions */
> > +static cmdline_parse_ctx_t builtin_ctx[] = {
> > +	(cmdline_parse_inst_t *)&cmd_help_brief,
> > +	(cmdline_parse_inst_t *)&cmd_help_long,
> > +	(cmdline_parse_inst_t *)&cmd_quit,
> > +	(cmdline_parse_inst_t *)&cmd_show_node_list,
> > +	(cmdline_parse_inst_t *)&cmd_set_lcore_config,
> > +	(cmdline_parse_inst_t *)&cmd_create_graph,
> > +	(cmdline_parse_inst_t *)&cmd_destroy_graph,
> > +	(cmdline_parse_inst_t *)&cmd_start_graph_walk,
> > +	(cmdline_parse_inst_t *)&cmd_stop_graph_walk,
> > +	(cmdline_parse_inst_t *)&cmd_show_graph_stats,
> > +	NULL,
> > +};
> > +
> > +int
> > +init_cmdline(void)
> > +{
> > +	unsigned int count;
> > +	unsigned int i;
> > +
> > +	count = 0;
> > +	for (i = 0; builtin_ctx[i] != NULL; i++)
> > +		count++;
> > +
> > +	/* cmdline expects a NULL terminated array */
> > +	main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
> > +	if (main_ctx == NULL)
> > +		return -1;
> > +
> > +	count = 0;
> > +	for (i = 0; builtin_ctx[i] != NULL; i++, count++)
> > +		main_ctx[count] = builtin_ctx[i];
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +prompt_exit(void)
> > +{
> > +	cmdline_quit(testgraph_cl);
> > +}
> > +
> > +/* prompt function, called from main on MAIN lcore */
> > +void
> > +prompt(void)
> > +{
> > +	testgraph_cl = cmdline_stdin_new(main_ctx, "testgraph> ");
> > +	if (testgraph_cl == NULL) {
> > +		fprintf(stderr, "Failed to create stdin based cmdline
> context\n");
> > +		return;
> > +	}
> > +
> > +	cmdline_interact(testgraph_cl);
> > +	cmdline_stdin_exit(testgraph_cl);
> > +}
> > diff --git a/app/test-graph/cmdline_graph.c b/app/test-
> graph/cmdline_graph.c
> > new file mode 100644
> > index 0000000000..d66149a224
> > --- /dev/null
> > +++ b/app/test-graph/cmdline_graph.c
> > @@ -0,0 +1,294 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <stdlib.h>
> > +#include <string.h>
> > +
> > +#include <cmdline_parse.h>
> > +#include <cmdline_parse_num.h>
> > +#include <cmdline_parse_string.h>
> > +
> > +#include "cmdline_graph.h"
> > +#include "testgraph.h"
> > +
> > +/* *** Show supported node details *** */
> > +struct cmd_show_node_list_result {
> > +	cmdline_fixed_string_t show;
> > +	cmdline_fixed_string_t node_list;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_show_node_list_show =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> show,
> > "show");
> > +static cmdline_parse_token_string_t cmd_show_node_list_node_list =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result,
> > node_list, "node_list");
> > +
> > +static void
> > +cmd_show_node_list_parsed(__rte_unused void *parsed_result,
> __rte_unused
> > struct cmdline *cl,
> > +			  __rte_unused void *data)
> > +{
> > +	rte_node_t node_cnt = rte_node_max_count();
> > +	rte_node_t id;
> > +
> > +	printf("\n**** Supported Graph Nodes ****\n");
> > +	for (id = 0; id < node_cnt; id++)
> > +		printf("%s\n", rte_node_id_to_name(id));
> > +
> > +	printf("********************************\n");
> > +}
> > +
> > +cmdline_parse_inst_t cmd_show_node_list = {
> > +	.f = cmd_show_node_list_parsed,
> > +	.data = NULL,
> > +	.help_str = "show node_list",
> > +	.tokens = {
> > +			(void *)&cmd_show_node_list_show,
> > +			(void *)&cmd_show_node_list_node_list,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Set lcore config *** */
> > +struct cmd_set_lcore_config_result {
> > +	cmdline_fixed_string_t set;
> > +	cmdline_fixed_string_t lcore_config;
> > +	cmdline_multi_string_t token_string;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_set =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> set,
> > "set");
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result,
> > lcore_config, "lcore_config");
> > +static cmdline_parse_token_string_t cmd_set_lcore_config_token_string
> =
> > TOKEN_STRING_INITIALIZER(
> > +	struct cmd_set_lcore_config_result, token_string,
> > TOKEN_STRING_MULTI);
> > +
> > +static void
> > +cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct
> > cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	struct cmd_set_lcore_config_result *res = parsed_result;
> > +	const char *t_str = res->token_string;
> > +	int ret;
> > +
> > +	/* Parse string */
> > +	ret = parse_config(t_str);
> > +	if (ret) {
> > +		printf(" lcore_config string parse error\n");
> > +		return;
> > +	}
> > +
> > +	validate_config();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_set_lcore_config = {
> > +	.f = cmd_set_lcore_config_parsed,
> > +	.data = NULL,
> > +	.help_str = "set lcore_config "
> > +		    "(port,queue,lcore),[(port,queue,lcore) ...
> > (port,queue,lcore)]",
> > +	.tokens = {
> > +			(void *)&cmd_set_lcore_config_set,
> > +			(void *)&cmd_set_lcore_config_lcore_config,
> > +			(void *)&cmd_set_lcore_config_token_string,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/* *** Create graph *** */
> > +struct cmd_create_graph_result {
> > +	cmdline_fixed_string_t create_graph;
> > +	cmdline_multi_string_t token_string;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_create_graph_create_graph =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> > create_graph, "create_graph");
> > +static cmdline_parse_token_string_t cmd_create_graph_token_string =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_create_graph_result,
> > token_string, TOKEN_STRING_MULTI);
> > +
> > +static void
> > +cmd_create_graph_parsed(void *parsed_result, __rte_unused struct
> cmdline
> > *cl,
> > +			__rte_unused void *data)
> > +{
> > +	struct cmd_create_graph_result *res = parsed_result;
> > +	const char *t_str = res->token_string;
> > +	uint64_t valid_nodes = 0;
> > +	int ret;
> > +
> > +	ret = parse_node_patterns(t_str);
> > +	if (ret) {
> > +		printf("parse_node_patterns failed\n");
> > +		cleanup_node_pattern();
> > +		return;
> > +	}
> > +
> > +	ret = validate_node_names(&valid_nodes);
> > +	if (ret) {
> > +		printf("validate_node_names() failed\n");
> > +		cleanup_node_pattern();
> > +		return;
> > +	}
> 
> Hi Vamsi,
> 
> The global node_pattern should be cleaned in destroy stage or make sure it
> populated from cmd line completely.
> In Interactive-mode, user may create and destroy a graph and then create a
> new one.
> For this case, the node name is changed by the previous creation, especially
> for rx/tx node.(ethdev_rx -> ethdev_rx-0-0)
> 
Sure, will fix it next version.

> > +
> > +	nb_conf = ethdev_ports_setup();
> > +
> > +	ethdev_rxq_configure();
> > +	ethdev_txq_configure();
> > +
> > +	ret = configure_graph_nodes(valid_nodes);
> > +	if (ret)
> > +		rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> err=%d\n",
> > ret);
> > +
> > +	ret = create_graph(valid_nodes);
> > +	if (ret)
> > +		rte_exit(EXIT_FAILURE, "create_graph: err=%d\n", ret);
> > +
> > +	stats = create_graph_cluster_stats();
> > +	if (stats == NULL)
> > +		rte_exit(EXIT_FAILURE, "create_graph_cluster_stats()
> failed\n");
> > +
> > +	check_all_ports_link_status(enabled_port_mask);
> > +	start_eth_ports();
> > +}
> 
> Could this create cmd be break down for graph_config and graph_create?
> In graph_config stage, we can set/verify graph topo, chose graph-walk
> models and update node ctx, etc.
> 

How about having a separate cmd to select the graph-walk model, that could be
run before create_graph cmd. Yes it could be split if really required, let me check
what all can be moved out of graph_create().

> > +
> > +cmdline_parse_inst_t cmd_create_graph = {
> > +	.f = cmd_create_graph_parsed,
> > +	.data = NULL,
> > +	.help_str = "create_graph "
> > +		    "[node_name0,node_name1,node_name2 ...
> node_nameX]",
> > +	.tokens = {
> > +			(void *)&cmd_create_graph_create_graph,
> > +			(void *)&cmd_create_graph_token_string,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Destroy graph ****/
> > +struct cmd_destroy_graph_result {
> > +	cmdline_fixed_string_t destroy_graph;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result,
> > destroy_graph, "destroy_graph");
> > +
> > +static void
> > +cmd_destroy_graph_parsed(__rte_unused void *parsed_result,
> __rte_unused
> > struct cmdline *cl,
> > +			 __rte_unused void *data)
> > +{
> > +	uint32_t lcore_id;
> > +
> > +	run_graph_walk = false;
> > +	graph_walk_quit = true;
> > +
> > +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> > +		rte_eal_wait_lcore(lcore_id);
> > +
> > +	destroy_graph();
> > +	stop_eth_ports();
> > +}
> > +
> > +cmdline_parse_inst_t cmd_destroy_graph = {
> > +	.f = cmd_destroy_graph_parsed,
> > +	.data = NULL,
> > +	.help_str = "destroy_graph",
> > +	.tokens = {
> > +			(void *)&cmd_destroy_graph_destroy_graph,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Start graph_walk ****/
> > +struct cmd_start_graph_walk_result {
> > +	cmdline_fixed_string_t start;
> > +	cmdline_fixed_string_t graph_walk;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_start_graph_walk_start =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> start,
> > "start");
> > +static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result,
> > graph_walk, "graph_walk");
> > +
> > +static void
> > +cmd_start_graph_walk_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	static bool launch_graph_walk;
> > +
> > +	if (!launch_graph_walk) {
> > +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> > SKIP_MAIN);
> 
> As run_graph_walk is used to control graph-walk, Why not put the launch in
> create stage?

Yes, will change in next version.

> 
> > +		launch_graph_walk = true;
> > +	}
> > +
> > +	run_graph_walk = true;
> > +}
> > +
> > +cmdline_parse_inst_t cmd_start_graph_walk = {
> > +	.f = cmd_start_graph_walk_parsed,
> > +	.data = NULL,
> > +	.help_str = "start graph_walk",
> > +	.tokens = {
> > +			(void *)&cmd_start_graph_walk_start,
> > +			(void *)&cmd_start_graph_walk_graph_walk,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Stop graph_walk ****/
> > +struct cmd_stop_graph_walk_result {
> > +	cmdline_fixed_string_t stop;
> > +	cmdline_fixed_string_t graph_walk;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> stop,
> > "stop");
> > +static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk
> =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result,
> > graph_walk, "graph_walk");
> > +
> > +static void
> > +cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			   __rte_unused void *data)
> > +{
> > +	run_graph_walk = false;
> > +}
> > +
> > +cmdline_parse_inst_t cmd_stop_graph_walk = {
> > +	.f = cmd_stop_graph_walk_parsed,
> > +	.data = NULL,
> > +	.help_str = "stop graph_walk",
> > +	.tokens = {
> > +			(void *)&cmd_stop_graph_walk_stop,
> > +			(void *)&cmd_stop_graph_walk_graph_walk,
> > +			NULL,
> > +		},
> > +};
> > +
> > +/**** Show graph_stats ****/
> > +struct cmd_show_graph_stats_result {
> > +	cmdline_fixed_string_t show;
> > +	cmdline_fixed_string_t graph_stats;
> > +};
> > +
> > +static cmdline_parse_token_string_t cmd_show_graph_stats_show =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> > show, "show");
> > +static cmdline_parse_token_string_t
> cmd_show_graph_stats_graph_stats =
> > +	TOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result,
> > graph_stats, "graph_stats");
> > +
> > +static void
> > +cmd_show_graph_stats_parsed(__rte_unused void *parsed_result,
> > __rte_unused struct cmdline *cl,
> > +			    __rte_unused void *data)
> > +{
> > +	if (rte_graph_has_stats_feature()) {
> > +		if (stats)
> > +			rte_graph_cluster_stats_get(stats, 0);
> > +	} else {
> > +		printf(" graph stats feature not enabled in rte_config.\n");
> > +	}
> > +}
> > +
> > +cmdline_parse_inst_t cmd_show_graph_stats = {
> > +	.f = cmd_show_graph_stats_parsed,
> > +	.data = NULL,
> > +	.help_str = "show graph_stats",
> > +	.tokens = {
> > +			(void *)&cmd_show_graph_stats_show,
> > +			(void *)&cmd_show_graph_stats_graph_stats,
> > +			NULL,
> > +		},
> > +};
> > diff --git a/app/test-graph/cmdline_graph.h b/app/test-
> graph/cmdline_graph.h
> > new file mode 100644
> > index 0000000000..2846ff5425
> > --- /dev/null
> > +++ b/app/test-graph/cmdline_graph.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef _CMDLINE_GRAPH_H_
> > +#define _CMDLINE_GRAPH_H_
> > +
> > +extern cmdline_parse_inst_t cmd_show_node_list;
> > +extern cmdline_parse_inst_t cmd_set_lcore_config;
> > +
> > +extern cmdline_parse_inst_t cmd_create_graph;
> > +extern cmdline_parse_inst_t cmd_destroy_graph;
> > +
> > +extern cmdline_parse_inst_t cmd_start_graph_walk;
> > +extern cmdline_parse_inst_t cmd_stop_graph_walk;
> > +
> > +extern cmdline_parse_inst_t cmd_show_graph_stats;
> > +
> > +#endif /* _CMDLINE_GRAPH_H_ */
> > diff --git a/app/test-graph/meson.build b/app/test-graph/meson.build
> > new file mode 100644
> > index 0000000000..d93802a975
> > --- /dev/null
> > +++ b/app/test-graph/meson.build
> > @@ -0,0 +1,14 @@
> > +# SPDX-License-Identifier: BSD-3-Clause
> > +# Copyright(C) 2023 Marvell International Ltd.
> > +
> > +# override default name to drop the hyphen
> > +name = 'test-graph'
> > +cflags += '-Wno-deprecated-declarations'
> > +sources = files(
> > +        'cmdline.c',
> > +        'cmdline_graph.c',
> > +        'parameters.c',
> > +        'testgraph.c',
> > +)
> > +
> > +deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']
> > diff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c
> > new file mode 100644
> > index 0000000000..b990ca4a1c
> > --- /dev/null
> > +++ b/app/test-graph/parameters.c
> > @@ -0,0 +1,157 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <getopt.h>
> > +#include <stdlib.h>
> > +
> > +#include "testgraph.h"
> > +
> > +static const char short_options[] = "p:" /* portmask */
> > +				    "P"	 /* promiscuous */
> > +				    "i"	 /* interactive */
> > +	;
> > +
> > +#define CMD_LINE_OPT_CONFIG	   "config"
> > +#define CMD_LINE_OPT_NODE_PATTERN  "node-pattern"
> > +#define CMD_LINE_OPT_INTERACTIVE   "interactive"
> > +#define CMD_LINE_OPT_NO_NUMA	   "no-numa"
> > +#define CMD_LINE_OPT_PER_PORT_POOL "per-port-pool"
> > +enum {
> > +	/* Long options mapped to a short option */
> > +
> > +	/* First long only option value must be >= 256, so that we won't
> > +	 * conflict with short options
> > +	 */
> > +	CMD_LINE_OPT_MIN_NUM = 256,
> > +	CMD_LINE_OPT_CONFIG_NUM,
> > +	CMD_LINE_OPT_NODE_PATTERN_NUM,
> > +	CMD_LINE_OPT_INTERACTIVE_NUM,
> > +	CMD_LINE_OPT_NO_NUMA_NUM,
> > +	CMD_LINE_OPT_PARSE_PER_PORT_POOL,
> > +};
> > +
> > +static const struct option lgopts[] = {
> > +	{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},
> > +	{CMD_LINE_OPT_NODE_PATTERN, 1, 0,
> > CMD_LINE_OPT_NODE_PATTERN_NUM},
> > +	{CMD_LINE_OPT_INTERACTIVE, 0, 0,
> > CMD_LINE_OPT_INTERACTIVE_NUM},
> > +	{CMD_LINE_OPT_NO_NUMA, 0, 0,
> CMD_LINE_OPT_NO_NUMA_NUM},
> > +	{CMD_LINE_OPT_PER_PORT_POOL, 0, 0,
> > CMD_LINE_OPT_PARSE_PER_PORT_POOL},
> > +	{NULL, 0, 0, 0},
> > +};
> > +
> > +/* Display usage */
> > +static void
> > +print_usage(const char *prgname)
> > +{
> > +	fprintf(stderr,
> > +		"%s [EAL options] --"
> > +		" -p PORTMASK"
> > +		" [-P]"
> > +		" [-i]"
> > +		" --config (port,queue,lcore)[,(port,queue,lcore)]"
> > +		" --node-pattern
> (node_name0,node_name1[,node_nameX)]"
> > +		" [--no-numa]"
> > +		" [--per-port-pool]"
> > +		" [--interactive]"
> > +
> > +		"  -p PORTMASK: Hexadecimal bitmask of ports to
> configure\n"
> > +		"  -P : Enable promiscuous mode\n"
> > +		"  -i : Enter interactive mode\n"
> > +		"  --config (port,queue,lcore): Rx queue configuration\n"
> > +		"  --node-pattern (node_names): node patterns to create
> > graph\n"
> > +		"  --no-numa: Disable numa awareness\n"
> > +		"  --per-port-pool: Use separate buffer pool per port\n"
> > +		"  --interactive: Enter interactive mode\n",
> > +		prgname);
> > +}
> > +
> > +static int
> > +parse_portmask(const char *portmask)
> > +{
> > +	char *end = NULL;
> > +	unsigned long pm;
> > +
> > +	/* Parse hexadecimal string */
> > +	pm = strtoul(portmask, &end, 16);
> > +	if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
> > +		return 0;
> > +
> > +	return pm;
> > +}
> > +
> > +/* Parse the argument given in the command line of the application */
> > +int
> > +parse_cmdline_args(int argc, char **argv)
> > +{
> > +	char *prgname = argv[0];
> > +	int option_index;
> > +	char **argvopt;
> > +	int opt, ret;
> > +
> > +	argvopt = argv;
> > +
> > +	/* Error or normal output strings. */
> > +	while ((opt = getopt_long(argc, argvopt, short_options, lgopts,
> > &option_index)) != EOF) {
> > +
> > +		switch (opt) {
> > +		/* Portmask */
> > +		case 'p':
> > +			enabled_port_mask = parse_portmask(optarg);
> > +			if (enabled_port_mask == 0) {
> > +				fprintf(stderr, "Invalid portmask\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +
> > +		case 'P':
> > +			promiscuous_on = 1;
> > +			break;
> > +
> > +		/* Long options */
> > +		case CMD_LINE_OPT_CONFIG_NUM:
> > +			ret = parse_config(optarg);
> > +			if (ret) {
> > +				fprintf(stderr, "Invalid config\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +		case CMD_LINE_OPT_NODE_PATTERN_NUM:
> > +			ret = parse_node_patterns(optarg);
> > +			if (ret) {
> > +				fprintf(stderr, "Invalid node_patterns\n");
> > +				print_usage(prgname);
> > +				return -1;
> > +			}
> > +			break;
> > +
> > +		case CMD_LINE_OPT_INTERACTIVE_NUM:
> > +		case 'i':
> > +			printf("Interactive-mode selected\n");
> > +			interactive = 1;
> > +			break;
> > +
> > +		case CMD_LINE_OPT_NO_NUMA_NUM:
> > +			numa_on = 0;
> > +			break;
> > +
> > +		case CMD_LINE_OPT_PARSE_PER_PORT_POOL:
> > +			printf("Per port buffer pool is enabled\n");
> > +			per_port_pool = 1;
> > +			break;
> > +
> > +		default:
> > +			print_usage(prgname);
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	if (optind >= 0)
> > +		argv[optind - 1] = prgname;
> > +	ret = optind - 1;
> > +	optind = 1; /* Reset getopt lib */
> > +
> > +	return ret;
> > +}
> > diff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c
> > new file mode 100644
> > index 0000000000..aff921acf2
> > --- /dev/null
> > +++ b/app/test-graph/testgraph.c
> > @@ -0,0 +1,1426 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#include <fnmatch.h>
> > +#include <signal.h>
> > +#include <stdlib.h>
> > +
> > +#include <rte_bus.h>
> > +#include <rte_byteorder.h>
> > +#include <rte_common.h>
> > +#include <rte_dev.h>
> > +#include <rte_eal.h>
> > +#include <rte_ethdev.h>
> > +#include <rte_ether.h>
> > +#include <rte_graph_worker.h>
> > +#include <rte_launch.h>
> > +#include <rte_lcore.h>
> > +#include <rte_log.h>
> > +#include <rte_malloc.h>
> > +#include <rte_mempool.h>
> > +#include <rte_per_lcore.h>
> > +#include <ethdev_rx_priv.h>
> > +#include <ethdev_tx_priv.h>
> > +#include <punt_kernel_priv.h>
> > +#include <kernel_recv_priv.h>
> > +
> > +#include "testgraph.h"
> > +
> > +/* Log type */
> > +#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1
> > +
> > +/*
> > + * Configurable number of RX/TX ring descriptors
> > + */
> > +#define RX_DESC_DEFAULT 1024
> > +#define TX_DESC_DEFAULT 1024
> > +
> > +#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS
> > +#define MAX_RX_QUEUE_PER_PORT 128
> > +
> > +#define MAX_RX_QUEUE_PER_LCORE 16
> > +
> > +#define NB_SOCKETS 8
> > +
> > +/* Static global variables used within this file. */
> > +uint16_t nb_rxd = RX_DESC_DEFAULT;
> > +uint16_t nb_txd = TX_DESC_DEFAULT;
> > +
> > +static volatile bool force_quit;
> > +volatile bool graph_walk_quit;
> > +volatile bool run_graph_walk = true;
> > +
> > +char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] =
> {0};
> > +const char **node_patterns;
> > +uint8_t num_patterns;
> > +
> > +uint8_t interactive; /**< interactive mode is off by default */
> > +int promiscuous_on; /**< Ports set in promiscuous mode off by default.
> */
> > +int numa_on = 1;   /**< NUMA is enabled by default. */
> > +int per_port_pool; /**< Use separate buffer pools per port, disabled by
> default
> > */
> > +int testgraph_logtype; /**< Log type for testgraph logs */
> > +
> > +struct rte_graph_cluster_stats *stats;
> > +
> > +uint32_t enabled_port_mask; /**< Mask of enabled ports */
> > +
> > +uint32_t nb_conf;
> > +
> > +struct lcore_rx_queue {
> > +	uint16_t port_id;
> > +	uint8_t queue_id;
> > +	char node_name[RTE_NODE_NAMESIZE];
> > +};
> > +
> > +/* Lcore conf */
> > +struct lcore_conf {
> > +	uint16_t n_rx_queue;
> > +	struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
> > +	char punt_kernel_node_name[RTE_NODE_NAMESIZE];
> > +	char kernel_recv_node_name[RTE_NODE_NAMESIZE];
> > +
> > +	struct rte_graph *graph;
> > +	char name[RTE_GRAPH_NAMESIZE];
> > +	rte_graph_t graph_id;
> > +} __rte_cache_aligned;
> > +
> > +static struct lcore_conf lcore_conf[RTE_MAX_LCORE];
> > +
> > +struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];
> > +static struct lcore_params lcore_params_array_default[] = {
> > +	{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},
> > +	{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},
> > +};
> > +
> > +struct lcore_params *lcore_params = lcore_params_array_default;
> > +uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);
> > +
> > +static struct rte_eth_conf port_conf = {
> > +	.rxmode = {
> > +			.mq_mode = RTE_ETH_MQ_RX_RSS,
> > +		},
> > +	.rx_adv_conf = {
> > +			.rss_conf = {
> > +					.rss_key = NULL,
> > +					.rss_hf = RTE_ETH_RSS_IP,
> > +				},
> > +		},
> > +	.txmode = {
> > +			.mq_mode = RTE_ETH_MQ_TX_NONE,
> > +		},
> > +};
> > +
> > +static const struct node_list test_node_list[] = {{{"ethdev_rx",
> "ethdev_tx"}, 0,
> > 2},
> > +						{{"ethdev_rx",
> "punt_kernel"},
> > 0, 2},
> > +						{{"kernel_recv",
> "ethdev_tx"},
> > 0, 2} };
> > +
> 
> Here we support 3 topo, could you support the original l3fwd nodes first?

Sure, firstly started with simple nodes which are quite independent and does not
require additional configurations like lpm, rewrite etc.

> 
> > +static const struct node_list supported_nodes[] = {{{"ethdev_rx"},
> > TEST_GRAPH_ETHDEV_RX_NODE, 1},
> > +						{{"ethdev_tx"},
> > TEST_GRAPH_ETHDEV_TX_NODE, 1},
> > +						{{"punt_kernel"},
> > TEST_GRAPH_PUNT_KERNEL_NODE, 1},
> > +						{{"kernel_recv"},
> > TEST_GRAPH_KERNEL_RECV_NODE, 1},
> > +						{{"ip4_lookup"},
> > TEST_GRAPH_IP4_LOOKUP_NODE, 1},
> > +						{{"ip4_rewrite"},
> > TEST_GRAPH_IP4_REWRITE_NODE, 1},
> > +						{{"pkt_cls"},
> > TEST_GRAPH_PKT_CLS_NODE, 1},
> > +						{{"pkt_drop"},
> > TEST_GRAPH_PKT_DROP_NODE, 1},
> > +						{{"NULL"},
> > TEST_GRAPH_NULL_NODE, 1} };
> > +
> > +static struct rte_mempool
> > *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];
> > +
> > +struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];
> > +
> > +static int
> > +check_lcore_params(void)
> > +{
> > +	uint8_t queue, lcore;
> > +	int socketid;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		queue = lcore_params[i].queue_id;
> > +		if (queue >= MAX_RX_QUEUE_PER_PORT) {
> > +			printf("Invalid queue number: %hhu\n", queue);
> > +			return -1;
> > +		}
> > +		lcore = lcore_params[i].lcore_id;
> > +		if (!rte_lcore_is_enabled(lcore)) {
> > +			printf("Error: lcore %hhu is not enabled in lcore
> mask\n",
> > lcore);
> > +			return -1;
> > +		}
> > +
> > +		if (lcore == rte_get_main_lcore()) {
> > +			printf("Error: lcore %u is main lcore\n", lcore);
> > +			return -1;
> > +		}
> > +		socketid = rte_lcore_to_socket_id(lcore);
> > +		if ((socketid != 0) && (numa_on == 0)) {
> > +			printf("Warning: lcore %hhu is on socket %d with
> numa
> > off\n", lcore,
> > +			       socketid);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +check_port_config(void)
> > +{
> > +	uint16_t portid;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		portid = lcore_params[i].port_id;
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("Port %u is not enabled in port mask\n",
> portid);
> > +			return -1;
> > +		}
> > +		if (!rte_eth_dev_is_valid_port(portid)) {
> > +			printf("Port %u is not present on the board\n",
> portid);
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static uint8_t
> > +get_port_n_rx_queues(const uint16_t port)
> > +{
> > +	int queue = -1;
> > +	uint16_t i;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		if (lcore_params[i].port_id == port) {
> > +			if (lcore_params[i].queue_id == queue + 1)
> > +				queue = lcore_params[i].queue_id;
> > +			else
> > +				rte_exit(EXIT_FAILURE, "Queue ids of the
> > port %d must be"
> > +					 " in sequence and must start with
> 0\n",
> > +					 lcore_params[i].port_id);
> > +		}
> > +	}
> > +
> > +	return (uint8_t)(++queue);
> > +}
> > +
> > +static int
> > +init_lcore_rx_queues(void)
> > +{
> > +	uint16_t i, nb_rx_queue;
> > +	uint8_t lcore;
> > +
> > +	for (i = 0; i < nb_lcore_params; ++i) {
> > +		lcore = lcore_params[i].lcore_id;
> > +		nb_rx_queue = lcore_conf[lcore].n_rx_queue;
> > +		if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {
> > +			printf("Error: too many queues (%u) for lcore: %u\n",
> > +			       (unsigned int)nb_rx_queue + 1, (unsigned
> int)lcore);
> > +			return -1;
> > +		}
> > +
> > +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id =
> > lcore_params[i].port_id;
> > +		lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id =
> > lcore_params[i].queue_id;
> > +		lcore_conf[lcore].n_rx_queue++;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int
> > +validate_config(void)
> > +{
> > +	int rc = -1;
> > +
> > +	if (check_lcore_params() < 0) {
> > +		printf("check_lcore_params() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	if (init_lcore_rx_queues() < 0) {
> > +		printf("init_lcore_rx_queues() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	if (check_port_config() < 0) {
> > +		printf("check_port_config() failed\n");
> > +		goto exit;
> > +	}
> > +
> > +	return 0;
> > +
> > +exit:
> > +	return rc;
> > +}
> > +
> > +#define MEMPOOL_CACHE_SIZE 256
> > +
> > +/*
> > + * This expression is used to calculate the number of mbufs needed
> > + * depending on user input, taking  into account memory for rx and
> > + * tx hardware rings, cache per lcore and mtable per port per lcore.
> > + * RTE_MAX is used to ensure that NB_MBUF never goes below a
> minimum
> > + * value of 8192
> > + */
> > +#define NB_MBUF(nports)                                                                            \
> > +	RTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores *
> > RTE_GRAPH_BURST_SIZE +       \
> > +		 nports * nb_tx_queue * nb_txd + nb_lcores *
> > MEMPOOL_CACHE_SIZE),                  \
> > +		8192u)
> > +
> > +static int
> > +init_mem(uint16_t portid, uint32_t nb_mbuf)
> > +{
> > +	uint32_t lcore_id;
> > +	int socketid;
> > +	char s[64];
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		if (numa_on)
> > +			socketid = rte_lcore_to_socket_id(lcore_id);
> > +		else
> > +			socketid = 0;
> > +
> > +		if (socketid >= NB_SOCKETS) {
> > +			rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out
> of
> > range %d\n",
> > +				 socketid, lcore_id, NB_SOCKETS);
> > +		}
> > +
> > +		if (pktmbuf_pool[portid][socketid] == NULL) {
> > +			snprintf(s, sizeof(s), "mbuf_pool_%d:%d", portid,
> > socketid);
> > +			/* Create a pool with priv size of a cacheline */
> > +			pktmbuf_pool[portid][socketid] =
> > rte_pktmbuf_pool_create(
> > +				s, nb_mbuf, MEMPOOL_CACHE_SIZE,
> > RTE_CACHE_LINE_SIZE,
> > +				RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
> > +			if (pktmbuf_pool[portid][socketid] == NULL)
> > +				rte_exit(EXIT_FAILURE, "Cannot init mbuf
> pool
> > on socket %d\n",
> > +					 socketid);
> > +			else
> > +				printf("Allocated mbuf pool on socket %d\n",
> > socketid);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +check_all_ports_link_status(uint32_t port_mask)
> > +{
> > +#define CHECK_INTERVAL 100 /* 100ms */
> > +#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */
> > +	uint8_t count, all_ports_up, print_flag = 0;
> > +	struct rte_eth_link link;
> > +	uint16_t portid;
> > +	int ret;
> > +	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];
> > +
> > +	printf("\nChecking link status");
> > +	fflush(stdout);
> > +	for (count = 0; count <= MAX_CHECK_TIME; count++) {
> > +		if (force_quit)
> > +			return;
> > +		all_ports_up = 1;
> > +		RTE_ETH_FOREACH_DEV(portid) {
> > +			if (force_quit)
> > +				return;
> > +			if ((port_mask & (1 << portid)) == 0)
> > +				continue;
> > +			memset(&link, 0, sizeof(link));
> > +			ret = rte_eth_link_get_nowait(portid, &link);
> > +			if (ret < 0) {
> > +				all_ports_up = 0;
> > +				if (print_flag == 1)
> > +					printf("Port %u link get failed: %s\n",
> > portid,
> > +					       rte_strerror(-ret));
> > +				continue;
> > +			}
> > +			/* Print link status if flag set */
> > +			if (print_flag == 1) {
> > +				rte_eth_link_to_str(link_status_text,
> > sizeof(link_status_text),
> > +						    &link);
> > +				printf("Port %d %s\n", portid,
> link_status_text);
> > +				continue;
> > +			}
> > +			/* Clear all_ports_up flag if any link down */
> > +			if (link.link_status == RTE_ETH_LINK_DOWN) {
> > +				all_ports_up = 0;
> > +				break;
> > +			}
> > +		}
> > +		/* After finally printing all link status, get out */
> > +		if (print_flag == 1)
> > +			break;
> > +
> > +		if (all_ports_up == 0) {
> > +			printf(".");
> > +			fflush(stdout);
> > +			rte_delay_ms(CHECK_INTERVAL);
> > +		}
> > +
> > +		/* Set the print_flag if all ports up or timeout */
> > +		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
> > +			print_flag = 1;
> > +			printf("Done\n");
> > +		}
> > +	}
> > +}
> > +
> > +static void
> > +signal_handler(int signum)
> > +{
> > +	if (signum == SIGINT || signum == SIGTERM) {
> > +		printf("\n\nSignal %d received, preparing to exit...\n",
> signum);
> > +		force_quit = true;
> > +	}
> > +	prompt_exit();
> > +}
> > +
> > +int
> > +graph_main_loop(void *conf)
> > +{
> > +	struct lcore_conf *qconf;
> > +	struct rte_graph *graph;
> > +	uint32_t lcore_id;
> > +
> > +	RTE_SET_USED(conf);
> > +
> > +	lcore_id = rte_lcore_id();
> > +	qconf = &lcore_conf[lcore_id];
> > +	graph = qconf->graph;
> > +
> > +	if (!graph) {
> > +		RTE_LOG(INFO, TEST_GRAPH, "Lcore %u has nothing to
> do\n",
> > lcore_id);
> > +		return 0;
> > +	}
> > +
> > +	RTE_LOG(INFO, TEST_GRAPH, "Entering main loop on lcore %u,
> > graph %s(%p)\n", lcore_id,
> > +		qconf->name, graph);
> > +
> > +	while (likely(!force_quit && !graph_walk_quit)) {
> > +		if (likely(run_graph_walk))
> > +			rte_graph_walk(graph);
> > +	}
> 
> graph_walk_quit could be changed to true in destroy stage,  we should set it
> false in create stage also.
> Otherwise, it will walk only once for interactive-mode.
> 
> I am agree to use different switch for different stage to start/stop,
> create/destroy and termination.
> force_quit + graph_walk_quit + run_graph_walk looks little overcomplicated.
> Could it only use
> force_quit to pause the walk in worker?

Sure, will simplify the path.

> 
> > +
> > +	return 0;
> > +}
> > +
> > +struct rte_graph_cluster_stats *
> > +create_graph_cluster_stats(void)
> > +{
> > +	struct rte_graph_cluster_stats_param s_param;
> > +	struct rte_graph_cluster_stats *stat;
> > +	const char *pattern = "worker_*";
> > +
> > +	/* Prepare stats object */
> > +	memset(&s_param, 0, sizeof(s_param));
> > +	s_param.f = stdout;
> > +	s_param.socket_id = SOCKET_ID_ANY;
> > +	s_param.graph_patterns = &pattern;
> > +	s_param.nb_graph_patterns = 1;
> > +
> > +	stat = rte_graph_cluster_stats_create(&s_param);
> > +	if (stat == NULL)
> > +		rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
> > +
> > +	return stat;
> > +}
> > +
> > +static void
> > +print_stats(void)
> > +{
> > +	const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
> > +	const char clr[] = {27, '[', '2', 'J', '\0'};
> > +
> > +	while (!force_quit) {
> > +		/* Clear screen and move to top left */
> > +		printf("%s%s", clr, topLeft);
> > +		rte_graph_cluster_stats_get(stats, 0);
> > +		rte_delay_ms(1E3);
> > +	}
> > +
> > +	rte_graph_cluster_stats_destroy(stats);
> > +}
> > +
> > +int
> > +parse_config(const char *q_arg)
> > +{
> > +	enum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE,
> > _NUM_FLD };
> > +	unsigned long int_fld[_NUM_FLD];
> > +	const char *p, *p0 = q_arg;
> > +	char *str_fld[_NUM_FLD];
> > +	uint32_t size;
> > +	char s[256];
> > +	char *end;
> > +	int i;
> > +
> > +	nb_lcore_params = 0;
> > +
> > +	while ((p = strchr(p0, '(')) != NULL) {
> > +		++p;
> > +		p0 = strchr(p, ')');
> > +		if (p0 == NULL)
> > +			goto exit;
> > +
> > +		size = p0 - p;
> > +		if (size >= sizeof(s))
> > +			goto exit;
> > +
> > +		memcpy(s, p, size);
> > +		s[size] = '\0';
> > +		if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') !=
> _NUM_FLD)
> > +			goto exit;
> > +		for (i = 0; i < _NUM_FLD; i++) {
> > +			errno = 0;
> > +			int_fld[i] = strtoul(str_fld[i], &end, 0);
> > +			if (errno != 0 || end == str_fld[i])
> > +				goto exit;
> > +		}
> > +
> > +		if (nb_lcore_params >= MAX_LCORE_PARAMS) {
> > +			printf("Exceeded max number of lcore params:
> %hu\n",
> > nb_lcore_params);
> > +			goto exit;
> > +		}
> > +
> > +		if (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS ||
> > int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {
> > +			printf("Invalid port/lcore id\n");
> > +			goto exit;
> > +		}
> > +
> > +		lcore_params_array[nb_lcore_params].port_id =
> > (uint8_t)int_fld[FLD_PORT];
> > +		lcore_params_array[nb_lcore_params].queue_id =
> > (uint8_t)int_fld[FLD_QUEUE];
> > +		lcore_params_array[nb_lcore_params].lcore_id =
> > (uint8_t)int_fld[FLD_LCORE];
> > +		++nb_lcore_params;
> > +	}
> > +	lcore_params = lcore_params_array;
> > +
> > +	return 0;
> > +exit:
> > +	/* Revert to default config */
> > +	lcore_params = lcore_params_array_default;
> > +	nb_lcore_params = RTE_DIM(lcore_params_array_default);
> > +
> > +	return -1;
> > +}
> > +
> > +int
> > +parse_node_patterns(const char *q_arg)
> > +{
> > +	const char *p, *p0 = q_arg;
> > +	int ret = -EINVAL;
> > +	uint32_t size;
> > +
> > +	num_patterns = 0;
> > +
> > +	p = strchr(p0, '(');
> > +	if (p != NULL) {
> > +		++p;
> > +		while ((p0 = strchr(p, ',')) != NULL) {
> > +			size = p0 - p;
> > +			if (size >= RTE_NODE_NAMESIZE)
> > +				goto exit;
> > +
> > +			if (num_patterns >= MAX_NODE_PATTERNS) {
> > +				printf("Too many nodes passed.\n");
> > +				goto exit;
> > +			}
> > +
> > +			memcpy(node_pattern[num_patterns++], p, size);
> 
> Here, if not make node_pattern clean, node_pattern[0](size,MAX) will be
> tainted by previous create.

Ack. will fix it.
> 
> > +			p = p0 + 1;
> > +		}
> > +
> > +		p0 = strchr(p, ')');
> > +		if (p0 != NULL) {
> > +			size = p0 - p;
> > +			if (size >= RTE_NODE_NAMESIZE)
> > +				goto exit;
> > +
> > +			if (num_patterns >= MAX_NODE_PATTERNS) {
> > +				printf("Too many nodes passed.\n");
> > +				goto exit;
> > +			}
> > +
> > +			memcpy(node_pattern[num_patterns++], p, size);
> > +		} else {
> > +			goto exit;
> > +		}
> > +	} else {
> > +		goto exit;
> > +	}
> > +
> > +	return 0;
> > +exit:
> > +	return ret;
> > +}
> > +
> > +static void
> > +set_default_node_pattern(void)
> > +{
> > +	uint16_t idx;
> > +
> > +	for (idx = 0; idx < test_node_list[0].size; idx++)
> > +		strcpy(node_pattern[num_patterns++],
> > test_node_list[0].nodes[idx]);
> > +}
> > +
> > +int
> > +validate_node_names(uint64_t *valid_nodes)
> 
> Actually, this func limits the node name to follow  test_node_list.
> It's OK if there are few nodes/edges.
> 
> But how to describe much nodes/edges like
> graph_dot {
>   A->B;
>   A->C;
>   B->D;
>   C->D;
> }
> 
> If update edges implicitly, there is no need to specific the node sequence.
> Both of (A,B,C,D) and (A,C,B,D) are correct.

Correct, will rework on interpreting /parsing it as expected by the topology.
> 
> > +{
> > +	rte_node_t node_cnt = rte_node_max_count();
> > +	bool pattern_matched = false;
> > +	rte_node_t id = 0;
> > +	int ret = -EINVAL;
> > +	uint16_t idx, i, j;
> > +
> > +	for (idx = 0; idx < num_patterns; idx++) {
> > +		for (id = 0; id < node_cnt; id++) {
> > +			if (strncmp(node_pattern[idx],
> > rte_node_id_to_name(id),
> > +				    RTE_GRAPH_NAMESIZE) == 0)
> > +				break;
> > +		}
> > +		if (node_cnt == id) {
> > +			printf("Invalid node name passed\n");
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	for (i = 0; i < RTE_DIM(test_node_list); i++) {
> > +		idx = 0;
> > +		if (test_node_list[i].size == num_patterns) {
> > +			for (j = 0; j < num_patterns; j++) {
> > +				if (strncmp(node_pattern[j],
> > test_node_list[i].nodes[j],
> > +				    RTE_GRAPH_NAMESIZE) == 0)
> > +					idx++;
> > +			}
> > +			if (idx == num_patterns)
> > +				pattern_matched = true;
> > +		}
> > +	}
> > +
> > +	if (!pattern_matched) {
> > +		printf("Unsupported node pattern passed\n\n");
> > +		printf("Test supported node patterns are:\n");
> > +		for (i = 0; i < RTE_DIM(test_node_list); i++) {
> > +			printf("(");
> > +			for (j = 0; j < (test_node_list[i].size - 1); j++)
> > +				printf("%s,", test_node_list[i].nodes[j]);
> > +			printf("%s", test_node_list[i].nodes[j]);
> > +			printf(")\n");
> > +		}
> > +
> > +		return ret;
> > +	}
> > +
> > +	for (i = 0; i < RTE_DIM(supported_nodes); i++) {
> > +		for (j = 0; j < num_patterns; j++) {
> > +			if (strncmp(node_pattern[j],
> > supported_nodes[i].nodes[0],
> > +				    RTE_GRAPH_NAMESIZE) == 0) {
> > +				*valid_nodes |= supported_nodes[i].test_id;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void
> > +cleanup_node_pattern(void)
> > +{
> > +	while (num_patterns) {
> > +		memset(node_pattern[num_patterns - 1], 0,
> > RTE_GRAPH_NAMESIZE);
> > +		num_patterns--;
> > +	}
> > +}
> > +
> > +static int
> > +ethdev_tx_node_configure(void)
> > +{
> > +	struct ethdev_tx_node_main *tx_node_data;
> > +	struct rte_node_register *tx_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	uint16_t port_id;
> > +	uint32_t id;
> > +
> > +	tx_node_data = ethdev_tx_node_data_get();
> > +	tx_node = ethdev_tx_node_get();
> > +
> > +	RTE_ETH_FOREACH_DEV(port_id) {
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << port_id)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", port_id);
> > +			continue;
> > +		}
> > +
> > +		if (!rte_eth_dev_is_valid_port(port_id))
> > +			return -EINVAL;
> > +
> > +		/* Create a per port tx node from base node */
> > +		snprintf(name, sizeof(name), "%u", port_id);
> > +		id = rte_node_clone(tx_node->id, name);
> > +		tx_node_data->nodes[port_id] = id;
> > +
> > +		printf("ethdev:: Tx node %s-%s: is at %u\n", tx_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +punt_kernel_node_configure(void)
> > +{
> > +	struct rte_node_register *punt_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	uint32_t id;
> > +
> > +	punt_node = punt_kernel_node_get();
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Create a per lcore punt_kernel node from base node */
> > +		snprintf(name, sizeof(name), "%u", lcore_id);
> > +		id = rte_node_clone(punt_node->id, name);
> > +		strcpy(qconf->punt_kernel_node_name,
> > rte_node_id_to_name(id));
> > +
> > +		printf("punt_kernel node %s-%s: is at %u\n", punt_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +kernel_recv_node_configure(void)
> > +{
> > +	struct rte_node_register *recv_node;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	uint32_t id;
> > +
> > +	recv_node = kernel_recv_node_get();
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Create a per lcore kernel_recv node from base node */
> > +		snprintf(name, sizeof(name), "%u", lcore_id);
> > +		id = rte_node_clone(recv_node->id, name);
> > +		strcpy(qconf->kernel_recv_node_name,
> > rte_node_id_to_name(id));
> > +
> > +		printf("kernel_recv node %s-%s: is at %u\n", recv_node-
> >name,
> > name, id);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +ethdev_rx_node_configure(struct rte_node_ethdev_config *conf,
> uint16_t
> > nb_confs)
> > +{
> > +	char name[RTE_NODE_NAMESIZE];
> > +	uint16_t i, j, port_id;
> > +	uint32_t id;
> > +
> > +	for (i = 0; i < nb_confs; i++) {
> > +		port_id = conf[i].port_id;
> > +
> > +		if (!rte_eth_dev_is_valid_port(port_id))
> > +			return -EINVAL;
> > +
> > +		/* Create node for each rx port queue pair */
> > +		for (j = 0; j < conf[i].num_rx_queues; j++) {
> > +			struct ethdev_rx_node_main *rx_node_data;
> > +			struct rte_node_register *rx_node;
> > +			ethdev_rx_node_elem_t *elem;
> > +
> > +			rx_node_data = ethdev_rx_get_node_data_get();
> > +			rx_node = ethdev_rx_node_get();
> > +			snprintf(name, sizeof(name), "%u-%u", port_id, j);
> > +			/* Clone a new rx node with same edges as parent */
> > +			id = rte_node_clone(rx_node->id, name);
> > +			if (id == RTE_NODE_ID_INVALID)
> > +				return -EIO;
> > +
> > +			/* Add it to list of ethdev rx nodes for lookup */
> > +			elem = malloc(sizeof(ethdev_rx_node_elem_t));
> > +			if (elem == NULL)
> > +				return -ENOMEM;
> > +			memset(elem, 0, sizeof(ethdev_rx_node_elem_t));
> > +			elem->ctx.port_id = port_id;
> > +			elem->ctx.queue_id = j;
> > +			elem->nid = id;
> > +			elem->next = rx_node_data->head;
> > +			rx_node_data->head = elem;
> > +
> > +			printf("ethdev:: Rx node %s-%s: is at %u\n",
> rx_node-
> > >name, name, id);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)
> > +{
> > +	struct ethdev_rx_node_main *rx_node_data;
> > +	ethdev_rx_node_elem_t *elem;
> > +	char *next_nodes[16];
> > +	rte_edge_t count;
> > +	uint16_t i;
> > +
> > +	count = rte_node_edge_count(id);
> > +	rte_node_edge_get(id, next_nodes);
> > +
> > +	for (i = 0; i < count; i++) {
> > +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> > +			rx_node_data = ethdev_rx_get_node_data_get();
> > +			elem = rx_node_data->head;
> > +			while (elem->next != rx_node_data->head) {
> > +				if (elem->nid == id)
> > +					break;
> > +				elem = elem->next;
> > +			}
> > +
> > +			if (elem->nid == id)
> > +				elem->ctx.cls_next = i;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_ethdev_rx_to_tx_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"ethdev_tx-*"};
> > +	uint16_t queue, queue_id, port_id;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	uint32_t rx_id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +		port_id = qconf->rx_queue_list[queue].port_id;
> > +		queue_id = qconf->rx_queue_list[queue].queue_id;
> > +
> > +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", port_id,
> > queue_id);
> > +		rx_id = rte_node_from_name(name);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		/* Prepare the actual name of the cloned node */
> > +		snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> > +
> > +		/* Update ethdev_rx node edges */
> > +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> > &next_node, 1);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		/* Update node_next details */
> > +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"punt_kernel-*"};
> > +	uint16_t queueid, portid, queue;
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	rte_node_t rx_id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +
> > +		portid = qconf->rx_queue_list[queue].port_id;
> > +		queueid = qconf->rx_queue_list[queue].queue_id;
> > +
> > +		snprintf(name, sizeof(name), "ethdev_rx-%u-%u", portid,
> > queueid);
> > +		rx_id = rte_node_from_name(name);
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], name);
> > +
> > +		next_node = qconf->punt_kernel_node_name;
> > +
> > +		/* Fill node pattern */
> > +		strcpy(node_pattern[num_patterns++], next_node);
> > +
> > +		/* Update ethdev_rx node edges */
> > +		rte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID,
> > &next_node, 1);
> > +
> > +		/* Update node_next details */
> > +		update_ethdev_rx_node_next(rx_id, pattern[0]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +update_kernel_recv_node_next(rte_node_t id, const char *edge_name)
> > +{
> > +	struct kernel_recv_node_main *rx_node_data;
> > +	kernel_recv_node_elem_t *elem;
> > +	char *next_nodes[16];
> > +	rte_edge_t count;
> > +	uint16_t i;
> > +
> > +	count = rte_node_edge_count(id);
> > +	rte_node_edge_get(id, next_nodes);
> > +
> > +	for (i = 0; i < count; i++) {
> > +		if (fnmatch(edge_name, next_nodes[i], 0) == 0) {
> > +			rx_node_data = kernel_recv_node_data_get();
> > +			elem = rx_node_data->head;
> > +			while (elem->next != rx_node_data->head) {
> > +				if (elem->nid == id)
> > +					break;
> > +				elem = elem->next;
> > +			}
> > +
> > +			if (elem->nid == id)
> > +				elem->ctx.recv_info->cls_next = i;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)
> > +{
> > +	const char * const pattern[] = {"ethdev_tx-*"};
> > +	char name[RTE_NODE_NAMESIZE];
> > +	const char *next_node = name;
> > +	struct lcore_conf *qconf;
> > +	uint16_t port_id;
> > +	rte_node_t id;
> > +
> > +	qconf = &lcore_conf[lcore_id];
> > +
> > +	id = rte_node_from_name(qconf->kernel_recv_node_name);
> > +
> > +	/* Fill node pattern */
> > +	strcpy(node_pattern[num_patterns++], qconf-
> > >kernel_recv_node_name);
> > +
> > +	port_id = lcore_id;
> > +
> > +	if ((enabled_port_mask & (1 << port_id)) == 0) {
> > +		/* Use available port_id */
> > +		RTE_ETH_FOREACH_DEV(port_id) {
> > +			if ((enabled_port_mask & (1 << port_id)) != 0)
> > +				break;
> > +		}
> > +	}
> > +
> > +	if (!rte_eth_dev_is_valid_port(port_id)) {
> > +		printf("Port %u is not present on the board\n", port_id);
> > +		return -1;
> > +	}
> > +
> > +	/* Prepare the actual name of the cloned node */
> > +	snprintf(name, sizeof(name), "ethdev_tx-%u", port_id);
> > +
> > +	/* Update ethdev_rx node edges */
> > +	rte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node,
> 1);
> > +
> > +	/* Fill node pattern */
> > +	strcpy(node_pattern[num_patterns++], name);
> > +
> > +	/* Update node_next details */
> > +	update_kernel_recv_node_next(id, pattern[0]);
> > +
> > +	return 0;
> > +}
> 
> These explicitly update node and link could be put in a independent stage,
> like graph_config.
> 
> Also, I wonder if update link could be more generic. Cause they have some
> similar steps.
> 
> BTY, In this patch, it could support rx->tx first.
> Then add a new patch to support other topo(kernel_recv, punt_kernel and
> l3fwd).
> And we could enable some help function in another patch.
> It could make the patch small.

Yes, make sense, will rework on this.
> 
> > +
> > +uint32_t
> > +ethdev_ports_setup(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	uint32_t nb_tx_queue, nb_lcores;
> > +	uint32_t nb_ports, nb_conf = 0;
> > +	uint8_t nb_rx_queue;
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	nb_ports = rte_eth_dev_count_avail();
> > +	nb_lcores = rte_lcore_count();
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		struct rte_eth_conf local_port_conf = port_conf;
> > +
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", portid);
> > +			continue;
> > +		}
> > +
> > +		/* Init port */
> > +		printf("Initializing port %d ... ", portid);
> > +		fflush(stdout);
> > +
> > +		nb_rx_queue = get_port_n_rx_queues(portid);
> > +		nb_tx_queue = nb_lcores;
> > +		if (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)
> > +			nb_tx_queue = MAX_TX_QUEUE_PER_PORT;
> > +		printf("Creating queues: nb_rxq=%d nb_txq=%u...\n",
> > nb_rx_queue, nb_tx_queue);
> > +
> > +		rte_eth_dev_info_get(portid, &dev_info);
> > +
> > +		if (dev_info.tx_offload_capa &
> > RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE)
> > +			local_port_conf.txmode.offloads |=
> > RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;
> > +
> > +		local_port_conf.rx_adv_conf.rss_conf.rss_hf &=
> > dev_info.flow_type_rss_offloads;
> > +		if (local_port_conf.rx_adv_conf.rss_conf.rss_hf !=
> > +		    port_conf.rx_adv_conf.rss_conf.rss_hf) {
> > +			printf("Port %u modified RSS hash function based on
> > hardware support,"
> > +			       "requested:%#" PRIx64 " configured:%#" PRIx64
> > "\n",
> > +			       portid, port_conf.rx_adv_conf.rss_conf.rss_hf,
> > +			       local_port_conf.rx_adv_conf.rss_conf.rss_hf);
> > +		}
> > +
> > +		ret = rte_eth_dev_configure(portid, nb_rx_queue,
> nb_tx_queue,
> > &local_port_conf);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "Cannot configure device:
> > err=%d, port=%d\n", ret,
> > +				 portid);
> > +
> > +		ret = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd,
> > &nb_txd);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE,
> > +				 "Cannot adjust number of descriptors:
> > err=%d,"
> > +				 "port=%d\n",
> > +				 ret, portid);
> > +
> > +		/* Init memory */
> > +		if (!per_port_pool)
> > +			ret = init_mem(0, NB_MBUF(nb_ports));
> > +		else
> > +			ret = init_mem(portid, NB_MBUF(1));
> > +
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "init_mem() failed\n");
> > +
> > +		/* Setup ethdev node config */
> > +		ethdev_conf[nb_conf].port_id = portid;
> > +		ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
> > +		ethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;
> > +		if (!per_port_pool)
> > +			ethdev_conf[nb_conf].mp = pktmbuf_pool[0];
> > +
> > +		else
> > +			ethdev_conf[nb_conf].mp = pktmbuf_pool[portid];
> > +		ethdev_conf[nb_conf].mp_count = NB_SOCKETS;
> > +
> > +		nb_conf++;
> > +	}
> > +
> > +	return nb_conf;
> > +}
> > +
> > +void
> > +ethdev_rxq_configure(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	uint16_t queueid, portid;
> > +	struct lcore_conf *qconf;
> > +	uint8_t queue, socketid;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +		qconf = &lcore_conf[lcore_id];
> > +		printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
> > +		fflush(stdout);
> > +
> > +		/* Init RX queues */
> > +		for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
> > +			struct rte_eth_rxconf rxq_conf;
> > +
> > +			portid = qconf->rx_queue_list[queue].port_id;
> > +			queueid = qconf->rx_queue_list[queue].queue_id;
> > +
> > +			if (numa_on)
> > +				socketid =
> > (uint8_t)rte_lcore_to_socket_id(lcore_id);
> > +			else
> > +				socketid = 0;
> > +
> > +			printf("rxq=%d,%d,%d ", portid, queueid, socketid);
> > +			fflush(stdout);
> > +
> > +			rte_eth_dev_info_get(portid, &dev_info);
> > +			rxq_conf = dev_info.default_rxconf;
> > +			rxq_conf.offloads = port_conf.rxmode.offloads;
> > +			if (!per_port_pool)
> > +				ret = rte_eth_rx_queue_setup(portid,
> queueid,
> > nb_rxd, socketid,
> > +							     &rxq_conf,
> > pktmbuf_pool[0][socketid]);
> > +			else
> > +				ret = rte_eth_rx_queue_setup(portid,
> queueid,
> > nb_rxd, socketid,
> > +							     &rxq_conf,
> > +
> > pktmbuf_pool[portid][socketid]);
> > +			if (ret < 0)
> > +				rte_exit(EXIT_FAILURE,
> > "rte_eth_rx_queue_setup: err=%d, port=%d\n",
> > +					 ret, portid);
> > +
> > +			snprintf(qconf->rx_queue_list[queue].node_name,
> > RTE_NODE_NAMESIZE,
> > +				 "ethdev_rx-%u-%u", portid, queueid);
> > +		}
> > +	}
> > +	printf("\n");
> > +}
> > +
> > +void
> > +ethdev_txq_configure(void)
> > +{
> > +	struct rte_eth_dev_info dev_info;
> > +	struct rte_eth_txconf *txconf;
> > +	uint16_t queueid, portid;
> > +	uint32_t lcore_id;
> > +	uint8_t socketid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		struct rte_eth_conf local_port_conf = port_conf;
> > +
> > +		/* Skip ports that are not enabled */
> > +		if ((enabled_port_mask & (1 << portid)) == 0) {
> > +			printf("\nSkipping disabled port %d\n", portid);
> > +			continue;
> > +		}
> > +
> > +		rte_eth_dev_info_get(portid, &dev_info);
> > +
> > +		/* Init one TX queue per (lcore,port) pair */
> > +		queueid = 0;
> > +		for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +			if (rte_lcore_is_enabled(lcore_id) == 0)
> > +				continue;
> > +
> > +			if (numa_on)
> > +				socketid =
> > (uint8_t)rte_lcore_to_socket_id(lcore_id);
> > +			else
> > +				socketid = 0;
> > +
> > +			printf("txq=%u,%d,%d ", lcore_id, queueid,
> socketid);
> > +			fflush(stdout);
> > +
> > +			txconf = &dev_info.default_txconf;
> > +			txconf->offloads = local_port_conf.txmode.offloads;
> > +			ret = rte_eth_tx_queue_setup(portid, queueid,
> nb_txd,
> > socketid, txconf);
> > +			if (ret < 0)
> > +				rte_exit(EXIT_FAILURE,
> > "rte_eth_tx_queue_setup: err=%d, port=%d\n",
> > +					 ret, portid);
> > +			queueid++;
> > +		}
> > +	}
> > +	printf("\n");
> > +}
> > +
> > +int
> > +configure_graph_nodes(uint64_t valid_nodes)
> > +{
> > +	int ret = 0;
> > +
> > +	if (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {
> > +		ret = ethdev_rx_node_configure(ethdev_conf, nb_conf);
> > +		if (ret) {
> > +			printf("ethdev_rx_node_configure: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {
> > +		ret = ethdev_tx_node_configure();
> > +		if (ret) {
> > +			printf("ethdev_tx_node_configure: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {
> > +		ret = punt_kernel_node_configure();
> > +		if (ret) {
> > +			printf("punt_kernel_node_configure: err=%d\n",
> ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +	if (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {
> > +		ret = kernel_recv_node_configure();
> > +		if (ret) {
> > +			printf("kernel_recv_node_configure: err=%d\n",
> ret);
> > +			goto exit;
> > +		}
> > +	}
> > +
> > +exit:
> > +	return ret;
> > +}
> > +
> > +static int
> > +link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)
> > +{
> > +	int ret = 0;
> > +
> > +	num_patterns = 0;
> > +
> > +	if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> > TEST_GRAPH_ETHDEV_RX_NODE)) {
> > +		ret = link_ethdev_rx_to_tx_node(lcore_id);
> > +		if (ret) {
> > +			printf("link_ethdev_rx_to_tx_node: err=%d\n", ret);
> > +			goto exit;
> > +		}
> > +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE |
> > TEST_GRAPH_PUNT_KERNEL_NODE)) {
> > +		link_ethdev_rx_to_punt_kernel_node(lcore_id);
> > +	} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE |
> > TEST_GRAPH_KERNEL_RECV_NODE)) {
> > +		link_kernel_recv_to_ethdev_tx_node(lcore_id);
> > +	} else {
> > +		printf("Invalid node map\n");
> > +		ret = -EINVAL;
> > +	}
> > +
> > +exit:
> > +	return ret;
> > +}
> > +
> > +void
> > +start_eth_ports(void)
> > +{
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		if ((enabled_port_mask & (1 << portid)) == 0)
> > +			continue;
> > +
> > +		ret = rte_eth_dev_start(portid);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d,
> > port=%d\n", ret, portid);
> > +
> > +		if (promiscuous_on)
> > +			rte_eth_promiscuous_enable(portid);
> > +	}
> > +}
> > +
> > +void
> > +stop_eth_ports(void)
> > +{
> > +	uint16_t portid;
> > +	int ret;
> > +
> > +	RTE_ETH_FOREACH_DEV(portid) {
> > +		if ((enabled_port_mask & (1 << portid)) == 0)
> > +			continue;
> > +		printf("Closing port %d...", portid);
> > +		ret = rte_eth_dev_stop(portid);
> > +		if (ret != 0)
> > +			printf("Failed to stop port %u: %s\n", portid,
> > rte_strerror(-ret));
> > +		rte_eth_dev_close(portid);
> close should be in exit stage or when application is killed.

ack
> 
> > +	}
> > +}
> > +
> > +int
> > +create_graph(uint64_t valid_nodes)
> > +{
> > +	struct rte_graph_param graph_conf;
> > +	struct lcore_conf *qconf;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	node_patterns = malloc(MAX_NODE_PATTERNS *
> > sizeof(*node_patterns));
> > +	if (!node_patterns)
> > +		return -ENOMEM;
> > +
> > +	memset(&graph_conf, 0, sizeof(graph_conf));
> > +	graph_conf.node_patterns = node_patterns;
> > +
> > +	for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
> > +		rte_graph_t graph_id;
> > +		rte_edge_t i;
> > +
> > +		if (rte_lcore_is_enabled(lcore_id) == 0)
> > +			continue;
> > +
> > +		qconf = &lcore_conf[lcore_id];
> > +
> > +		/* Skip graph creation if no source exists */
> > +		if (!qconf->n_rx_queue)
> > +			continue;
> > +
> > +		ret = link_graph_nodes(valid_nodes, lcore_id);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "link_graph_nodes():
> failed\n");
> > +
> > +		for (i = 0; i < num_patterns; i++) {
> > +			graph_conf.node_patterns[i] = node_pattern[i];
> > +			printf("%s,", graph_conf.node_patterns[i]);
> > +		}
> > +		printf("\n");
> > +
> > +		graph_conf.nb_node_patterns = num_patterns;
> > +
> > +		graph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);
> > +
> > +		snprintf(qconf->name, sizeof(qconf->name), "worker_%u",
> > lcore_id);
> > +
> > +		graph_id = rte_graph_create(qconf->name, &graph_conf);
> > +		if (graph_id == RTE_GRAPH_ID_INVALID)
> > +			rte_exit(EXIT_FAILURE, "rte_graph_create():
> graph_id
> > invalid for lcore%u\n",
> > +				 lcore_id);
> > +
> > +		qconf->graph_id = graph_id;
> > +		qconf->graph = rte_graph_lookup(qconf->name);
> > +
> > +		if (!qconf->graph)
> > +			rte_exit(EXIT_FAILURE, "rte_graph_lookup(): graph
> %s
> > not found\n",
> > +				 qconf->name);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int
> > +destroy_graph(void)
> > +{
> > +	uint32_t lcore_id;
> > +	rte_graph_t id;
> > +	int ret;
> > +
> > +	RTE_LCORE_FOREACH_WORKER(lcore_id)
> > +	{
> > +		if (lcore_conf[lcore_id].graph) {
> > +			id =
> rte_graph_from_name(lcore_conf[lcore_id].name);
> > +			if (rte_graph_destroy(id)) {
> > +				printf("graph_id %u destroy failed.\n", id);
> > +				ret = -1;
> > +			}
> > +		}
> > +	}
> > +
> > +	if (node_patterns)
> > +		free(node_patterns);
> 
> Cause this cmd could be called twice in interactive-mode, we should set it to
> NULL after free.

ack
> 
> > +
> > +	return ret;
> > +}
> > +
> > +int
> > +main(int argc, char **argv)
> > +{
> > +	uint64_t valid_nodes;
> > +	uint32_t lcore_id;
> > +	int ret;
> > +
> > +	graph_walk_quit = false;
> > +	force_quit = false;
> > +	interactive = 0;
> > +
> > +	node_patterns = NULL;
> > +
> > +	signal(SIGINT, signal_handler);
> > +	signal(SIGTERM, signal_handler);
> > +
> > +	testgraph_logtype = rte_log_register("testgraph");
> > +	if (testgraph_logtype < 0)
> > +		rte_exit(EXIT_FAILURE, "Cannot register log type");
> > +
> > +	set_default_node_pattern();
> > +
> > +	rte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);
> > +
> > +	ret = rte_eal_init(argc, argv);
> > +	if (ret < 0)
> > +		rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n",
> > rte_strerror(rte_errno));
> > +	argc -= ret;
> > +	argv += ret;
> > +
> > +	if (argc > 1) {
> > +		ret = parse_cmdline_args(argc, argv);
> > +		if (ret < 0)
> > +			rte_exit(EXIT_FAILURE, "Invalid command line
> > parameters\n");
> > +	}
> > +
> > +#ifdef RTE_LIB_CMDLINE
> > +	if (init_cmdline() != 0)
> > +		rte_exit(EXIT_FAILURE, "Could not initialise cmdline
> > context.\n");
> > +
> > +	if (interactive == 1) {
> > +		prompt();
> > +	} else
> > +#endif
> > +	{
> > +		if (validate_config() < 0)
> > +			rte_exit(EXIT_FAILURE, "Config validation failed.\n");
> > +
> > +		ret = validate_node_names(&valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "validate_node_names:
> > err=%d\n", ret);
> > +
> > +		nb_conf = ethdev_ports_setup();
> > +
> > +		ethdev_rxq_configure();
> > +
> > +		ethdev_txq_configure();
> > +
> > +		ret = configure_graph_nodes(valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "configure_graph_nodes:
> > err=%d\n", ret);
> > +
> > +		ret = create_graph(valid_nodes);
> > +		if (ret)
> > +			rte_exit(EXIT_FAILURE, "create_graph: err=%d\n",
> ret);
> > +
> > +		stats = create_graph_cluster_stats();
> > +		if (stats == NULL)
> > +			rte_exit(EXIT_FAILURE,
> "create_graph_cluster_stats()
> > failed\n");
> > +
> > +		check_all_ports_link_status(enabled_port_mask);
> > +
> > +		start_eth_ports();
> > +
> > +		/* Launch per-lcore init on every worker lcore */
> > +		rte_eal_mp_remote_launch(graph_main_loop, NULL,
> > SKIP_MAIN);
> > +
> > +		/* Accumulate and print stats on main until exit */
> > +		if (rte_graph_has_stats_feature())
> > +			print_stats();
> > +
> > +		/* Wait for worker cores to exit */
> > +		RTE_LCORE_FOREACH_WORKER(lcore_id) {
> > +			ret = rte_eal_wait_lcore(lcore_id);
> > +			if (ret < 0)
> > +				break;
> > +		}
> > +
> > +		ret = destroy_graph();
> > +
> > +		stop_eth_ports();
> > +	}
> > +
> > +	/* clean up the EAL */
> > +	ret = rte_eal_cleanup();
> > +	if (ret != 0)
> > +		rte_exit(EXIT_FAILURE, "EAL cleanup failed: %s\n", strerror(-
> > ret));
> > +
> > +	return EXIT_SUCCESS;
> > +}
> > diff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h
> > new file mode 100644
> > index 0000000000..ea1f8ef4fa
> > --- /dev/null
> > +++ b/app/test-graph/testgraph.h
> > @@ -0,0 +1,91 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(C) 2023 Marvell International Ltd.
> > + */
> > +
> > +#ifndef _TESTGRAPH_H_
> > +#define _TESTGRAPH_H_
> > +
> > +#include <stdbool.h>
> > +
> > +#include <rte_ethdev.h>
> > +#include <rte_graph.h>
> > +#include <rte_node_eth_api.h>
> > +
> > +#include <cmdline.h>
> > +#include <cmdline_parse.h>
> > +
> > +
> > +#define MAX_LCORE_PARAMS 1024
> > +#define MAX_NODE_PATTERNS 128
> > +
> > +#ifndef BIT_ULL
> > +#define BIT_ULL(nr) (1ULL << (nr))
> > +#endif
> > +
> > +#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)
> > +#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)
> > +#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)
> > +#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)
> > +#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)
> > +#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)
> > +#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)
> > +#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)
> > +#define TEST_GRAPH_NULL_NODE BIT_ULL(8)
> > +
> > +static volatile bool force_quit;
> > +
> > +extern struct rte_node_ethdev_config
> ethdev_conf[RTE_MAX_ETHPORTS];
> > +extern uint32_t enabled_port_mask;
> > +extern uint32_t nb_conf;
> > +
> > +extern int promiscuous_on;	   /**< Ports set in promiscuous mode off by
> > default. */
> > +extern uint8_t interactive;	   /**< interactive mode is disabled by
> default.
> > */
> > +extern uint32_t enabled_port_mask; /**< Mask of enabled ports */
> > +extern int numa_on;		   /**< NUMA is enabled by default.
> */
> > +extern int per_port_pool;
> > +
> > +extern volatile bool graph_walk_quit;
> > +extern volatile bool run_graph_walk;
> > +extern struct rte_graph_cluster_stats *stats;
> > +
> > +extern char
> node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];
> > +extern uint8_t num_patterns;
> > +
> > +struct node_list {
> > +	const char *nodes[MAX_NODE_PATTERNS];
> > +	uint64_t test_id;
> > +	uint8_t size;
> > +};
> > +
> > +struct lcore_params {
> > +	uint16_t port_id;
> > +	uint8_t queue_id;
> > +	uint8_t lcore_id;
> > +} __rte_cache_aligned;
> > +
> > +void prompt(void);
> > +void prompt_exit(void);
> > +int init_cmdline(void);
> > +int validate_config(void);
> > +int parse_cmdline_args(int argc, char **argv);
> > +uint32_t ethdev_ports_setup(void);
> > +void ethdev_rxq_configure(void);
> > +void ethdev_txq_configure(void);
> > +void start_eth_ports(void);
> > +void stop_eth_ports(void);
> > +int create_graph(uint64_t valid_nodes);
> > +int destroy_graph(void);
> > +int graph_main_loop(void *conf);
> > +struct rte_graph_cluster_stats *create_graph_cluster_stats(void);
> > +void check_all_ports_link_status(uint32_t port_mask);
> > +int configure_graph_nodes(uint64_t valid_nodes);
> > +
> > +int parse_config(const char *q_arg);
> > +int parse_node_patterns(const char *q_arg);
> > +int validate_node_names(uint64_t *valid_nodes);
> > +void cleanup_node_pattern(void);
> > +
> > +#define TESTGRAPH_LOG(level, fmt, args...)                                                         \
> > +	rte_log(RTE_LOG_##level, testgraph_logtype, "testgraph: " fmt,
> ##args)
> > +
> > +#endif /* _TESTGRAPH_H_ */
> > diff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst
> > index 6f84fc31ff..f18c508fa2 100644
> > --- a/doc/guides/tools/index.rst
> > +++ b/doc/guides/tools/index.rst
> > @@ -20,6 +20,7 @@ DPDK Tools User Guides
> >      cryptoperf
> >      comp_perf
> >      testeventdev
> > +    testgraph
> >      testregex
> >      testmldev
> >      dts
> > diff --git a/doc/guides/tools/testgraph.rst
> b/doc/guides/tools/testgraph.rst
> > new file mode 100644
> > index 0000000000..3c1e058724
> > --- /dev/null
> > +++ b/doc/guides/tools/testgraph.rst
> > @@ -0,0 +1,131 @@
> > +..  SPDX-License-Identifier: BSD-3-Clause
> > +    Copyright(C) 2023 Marvell International Ltd.
> > +
> > +dpdk-test-graph Application
> > +===========================
> > +
> > +The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK)
> application
> > that allows
> > +exercising various graph library features. This application has a generic
> > framework to add
> > +new test configurations and expand test coverage to verify the
> functionality of
> > graph nodes
> > +and observe the graph cluster statistics.
> > +
> > +Running the Application
> > +-----------------------
> > +
> > +The application has a number of command line options:
> > +
> > +.. code-block:: console
> > +
> > +   dpdk-test-eventdev [EAL Options] -- [application options]
> > +
> > +EAL Options
> > +~~~~~~~~~~~
> > +
> > +The following are the EAL command-line options that can be used in
> > conjunction
> > +with the ``dpdk-test-graph`` application.
> > +See the DPDK Getting Started Guides for more information on these
> options.
> > +
> > +*   ``-c <COREMASK>`` or ``-l <CORELIST>``
> > +
> > +        Set the hexadecimal bitmask of the cores to run on. The corelist is a
> > +        list of cores to use.
> > +
> > +Application Options
> > +~~~~~~~~~~~~~~~~~~~
> > +
> > +The following are the application command-line options:
> > +
> > +* ``-p <n>``
> > +
> > +        Set the ethdev port mask.
> > +
> > +* ``-P``
> > +
> > +        Set the ethdev ports in promiscuous mode.
> > +
> > +* ``--config <config>``
> > +
> > +        Set the Rxq configuration.
> > +        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).
> > +
> > +* ``--node-pattern <n>``
> > +
> > +        Set the node patterns to use in graph creation.
> > +        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).
> > +
> > +* ``--per-port-pool``
> > +
> > +        Use separate buffer pool per port.
> > +
> > +* ``--no-numa``
> > +
> > +        Disable numa awareness.
> > +
> > +* ``--interactive``
> > +
> > +        Switch to interactive mode.
> > +
> > +Running the Tool
> > +~~~~~~~~~~~~~~~~
> > +
> > +Here is the sample command line to run simple iofwd test::
> > +
> > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,ethdev_tx)"
> > +
> > +Below is a sample command line to punt rx packets to kernel::
> > +
> > +       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \
> > +       --config "(0,0,2),(1,0,2)" --node-pattern "(ethdev_rx,punt_kernel)"
> > +
> > +Interactive mode
> > +~~~~~~~~~~~~~~~~
> > +
> > +Tool uses ``--interactive`` command line option to enter interactive mode
> and
> > use cmdline options
> > +to setup the required node configurations, create graph and than start
> > graph_walk.
> > +
> > +
> > +testgraph> help
> > +
> > +Help is available for the following sections:
> > +
> > +    help control                    : Start and stop graph walk.
> > +    help display                    : Displaying port, stats and config information.
> > +    help config                     : Configuration information.
> > +    help all                        : All of the above sections.
> > +
> > +testgraph> help all
> > +
> > +Control forwarding:
> > +
> > +start graph_walk
> > + Start graph_walk on worker threads.
> > +
> > +stop graph_walk
> > + Stop worker threads from running graph_walk.
> > +
> > +quit
> > + Quit to prompt.
> > +
> > +
> > +Display:
> > +
> > +show node_list
> > + Display the list of supported nodes.
> > +
> > +show graph_stats
> > + Display the node statistics of graph cluster.
> > +
> > +
> > +Configuration:
> > +
> > +set lcore_config (port_id0,rxq0,lcore_idX),........,(port_idX,rxqX,lcoreidY)
> > + Set lcore configuration.
> > +
> > +create_graph (node0_name,node1_name,...,nodeX_name)
> > + Create graph instances using the provided node details.
> > +
> > +destroy_graph
> > + Destroy the graph instances.
> > +
> > +testgraph>
> > --
> > 2.25.1


  reply	other threads:[~2023-05-22  7:07 UTC|newest]

Thread overview: 182+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-04-21  6:02 [PATCH 0/4] app: introduce " Vamsi Attunuru
2023-04-21  6:02 ` [PATCH 1/4] node: add pkt punt to kernel node Vamsi Attunuru
2023-05-29 17:29   ` Jerin Jacob
2023-05-31 12:36     ` [EXT] " Vamsi Krishna Attunuru
2023-04-21  6:02 ` [PATCH 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
2023-05-29 17:39   ` Jerin Jacob
2023-06-01  2:40     ` [EXT] " Vamsi Krishna Attunuru
2023-04-21  6:02 ` [PATCH 3/4] node: remove hardcoded node next details Vamsi Attunuru
2023-04-21  6:02 ` [PATCH 4/4] app: add testgraph application Vamsi Attunuru
2023-05-09  6:09   ` Jerin Jacob
2023-04-25 13:15 ` [PATCH v2 0/4] app: introduce " Vamsi Attunuru
2023-04-25 13:15   ` [PATCH v2 1/4] node: add pkt punt to kernel node Vamsi Attunuru
2023-05-30  8:16     ` Nithin Dabilpuram
2023-05-31 12:35       ` [EXT] " Vamsi Krishna Attunuru
2023-04-25 13:15   ` [PATCH v2 2/4] node: add a node to receive pkts from kernel Vamsi Attunuru
2023-04-25 13:15   ` [PATCH v2 3/4] node: remove hardcoded node next details Vamsi Attunuru
2023-04-25 13:15   ` [PATCH v2 4/4] app: add testgraph application Vamsi Attunuru
2023-05-09  2:34     ` [EXT] " Sunil Kumar Kori
2023-05-09  3:39       ` Vamsi Krishna Attunuru
2023-05-09  8:53         ` Sunil Kumar Kori
2023-05-09 12:24           ` Vamsi Krishna Attunuru
2023-05-12 11:08     ` Yan, Zhirun
2023-05-22  7:07       ` Vamsi Krishna Attunuru [this message]
2023-05-30  7:34         ` Jerin Jacob
2023-06-01  2:44           ` [EXT] " Vamsi Krishna Attunuru
2023-07-20 15:52             ` Rakesh Kudurumalla
2023-07-21  6:48               ` Jerin Jacob
2023-07-21  7:01                 ` Sunil Kumar Kori
2023-07-21  7:39                   ` Rakesh Kudurumalla
2023-09-08 11:00                     ` Sunil Kumar Kori
2023-09-08 10:49     ` [PATCH v3 1/1] app/graph: add example for different usecases skori
2023-09-09  1:18       ` [EXT] " Nithin Kumar Dabilpuram
2023-09-19 16:04       ` [PATCH v4 01/14] app/graph: add application framework to read CLI skori
2023-09-19 16:04         ` [PATCH v4 02/14] app/graph: add telnet connectivity framework skori
2023-09-20  4:34           ` Jerin Jacob
2023-09-20  8:14             ` Bruce Richardson
2023-09-19 16:04         ` [PATCH v4 03/14] app/graph: add parser utility APIs skori
2023-09-19 16:04         ` [PATCH v4 04/14] app/graph: add mempool command line interfaces skori
2023-09-19 16:04         ` [PATCH v4 05/14] app/graph: add ethdev " skori
2023-09-19 16:04         ` [PATCH v4 06/14] app/graph: add ipv4_lookup " skori
2023-09-19 16:04         ` [PATCH v4 07/14] app/graph: add ipv6_lookup " skori
2023-09-19 16:04         ` [PATCH v4 08/14] app/graph: add neigh " skori
2023-09-19 16:04         ` [PATCH v4 09/14] app/graph: add ethdev_rx " skori
2023-09-19 16:04         ` [PATCH v4 10/14] app/graph: add graph " skori
2023-09-19 16:04         ` [PATCH v4 11/14] app/graph: add CLI option to enable graph stats skori
2023-09-19 16:04         ` [PATCH v4 12/14] app/graph: add l3fwd usecase skori
2023-09-19 16:04         ` [PATCH v4 13/14] doc: add graph application user guide skori
2023-09-20  4:28           ` Jerin Jacob
2023-09-19 16:04         ` [PATCH v4 14/14] maintainers: add maintainers for graph app skori
2023-09-20  4:40         ` [PATCH v4 01/14] app/graph: add application framework to read CLI Jerin Jacob
2023-09-21 10:08         ` [PATCH v5 00/12] add CLI based graph application skori
2023-09-21 10:08           ` [PATCH v5 01/12] app/graph: add application framework to read CLI skori
2023-09-21 10:08           ` [PATCH v5 02/12] app/graph: add telnet connectivity framework skori
2023-09-21 10:08           ` [PATCH v5 03/12] app/graph: add parser utility APIs skori
2023-09-21 10:08           ` [PATCH v5 04/12] app/graph: add mempool command line interfaces skori
2023-09-21 10:08           ` [PATCH v5 05/12] app/graph: add ethdev " skori
2023-09-21 10:08           ` [PATCH v5 06/12] app/graph: add ipv4_lookup " skori
2023-09-21 10:08           ` [PATCH v5 07/12] app/graph: add ipv6_lookup " skori
2023-09-21 10:08           ` [PATCH v5 08/12] app/graph: add neigh " skori
2023-09-21 10:08           ` [PATCH v5 09/12] app/graph: add ethdev_rx " skori
2023-09-21 10:08           ` [PATCH v5 10/12] app/graph: add graph " skori
2023-09-21 10:08           ` [PATCH v5 11/12] app/graph: add CLI option to enable graph stats skori
2023-09-21 10:08           ` [PATCH v5 12/12] app/graph: add l3fwd usecase skori
2023-09-26 10:57             ` [PATCH v6 00/12] add CLI based graph application skori
2023-09-26 10:57               ` [PATCH v6 01/12] app/graph: add application framework to read CLI skori
2023-09-26 10:57               ` [PATCH v6 02/12] app/graph: add telnet connectivity framework skori
2023-09-26 10:57               ` [PATCH v6 03/12] app/graph: add parser utility APIs skori
2023-09-26 10:57               ` [PATCH v6 04/12] app/graph: add mempool command line interfaces skori
2023-09-26 10:57               ` [PATCH v6 05/12] app/graph: add ethdev " skori
2023-09-26 10:57               ` [PATCH v6 06/12] app/graph: add ipv4_lookup " skori
2023-09-26 10:57               ` [PATCH v6 07/12] app/graph: add ipv6_lookup " skori
2023-09-26 10:57               ` [PATCH v6 08/12] app/graph: add neigh " skori
2023-09-26 10:57               ` [PATCH v6 09/12] app/graph: add ethdev_rx " skori
2023-09-26 10:57               ` [PATCH v6 10/12] app/graph: add graph " skori
2023-09-26 10:57               ` [PATCH v6 11/12] app/graph: add CLI option to enable graph stats skori
2023-09-26 10:57               ` [PATCH v6 12/12] app/graph: add l3fwd use case skori
2023-09-27 11:54                 ` [PATCH v7 00/12] add CLI based graph application skori
2023-09-27 11:54                   ` [PATCH v7 01/12] app/graph: add application framework to read CLI skori
2023-09-27 11:54                   ` [PATCH v7 02/12] app/graph: add telnet connectivity framework skori
2023-09-27 11:54                   ` [PATCH v7 03/12] app/graph: add parser utility APIs skori
2023-09-27 11:54                   ` [PATCH v7 04/12] app/graph: add mempool command line interfaces skori
2023-09-27 11:54                   ` [PATCH v7 05/12] app/graph: add ethdev " skori
2023-09-27 11:54                   ` [PATCH v7 06/12] app/graph: add ipv4_lookup " skori
2023-09-27 11:54                   ` [PATCH v7 07/12] app/graph: add ipv6_lookup " skori
2023-09-27 11:54                   ` [PATCH v7 08/12] app/graph: add neigh " skori
2023-09-27 11:54                   ` [PATCH v7 09/12] app/graph: add ethdev_rx " skori
2023-09-27 11:54                   ` [PATCH v7 10/12] app/graph: add graph " skori
2023-09-27 11:54                   ` [PATCH v7 11/12] app/graph: add CLI option to enable graph stats skori
2023-09-27 11:54                   ` [PATCH v7 12/12] app/graph: add l3fwd use case skori
2023-09-29  9:58                     ` [PATCH v8 00/12] add CLI based graph application skori
2023-09-29  9:58                       ` [PATCH v8 01/12] app/graph: add application framework to read CLI skori
2023-10-16  9:00                         ` Jerin Jacob
2023-10-17  6:19                           ` [EXT] " Sunil Kumar Kori
2023-10-18  6:33                         ` [PATCH v9 00/12] add CLI based graph application skori
2023-10-18  6:33                           ` [PATCH v9 01/12] app/graph: support application CLI framework skori
2023-10-18  6:33                           ` [PATCH v9 02/12] app/graph: support telnet connectivity framework skori
2023-10-18  6:33                           ` [PATCH v9 03/12] app/graph: support parser utility APIs skori
2023-10-18  6:33                           ` [PATCH v9 04/12] app/graph: support mempool command line interfaces skori
2023-10-18  6:33                           ` [PATCH v9 05/12] app/graph: support ethdev " skori
2023-10-18  6:33                           ` [PATCH v9 06/12] app/graph: support IPv4 lookup " skori
2023-10-18  6:33                           ` [PATCH v9 07/12] app/graph: support IPv6 " skori
2023-10-18  6:33                           ` [PATCH v9 08/12] app/graph: support neigh " skori
2023-10-18  6:33                           ` [PATCH v9 09/12] app/graph: support ethdev Rx " skori
2023-10-18  6:33                           ` [PATCH v9 10/12] app/graph: support graph " skori
2023-10-18  6:33                           ` [PATCH v9 11/12] app/graph: support CLI option to enable graph stats skori
2023-10-18  6:33                           ` [PATCH v9 12/12] app/graph: support l3fwd use case skori
2023-10-18 10:38                             ` Jerin Jacob
2023-10-19 10:49                             ` [PATCH v10 00/12] add CLI based graph application skori
2023-10-19 10:49                               ` [PATCH v10 01/12] app/graph: support application CLI framework skori
2023-10-19 10:49                               ` [PATCH v10 02/12] app/graph: support telnet connectivity framework skori
2023-10-19 10:49                               ` [PATCH v10 03/12] app/graph: support parser utility APIs skori
2023-10-19 10:49                               ` [PATCH v10 04/12] app/graph: support mempool command line interfaces skori
2023-10-19 10:49                               ` [PATCH v10 05/12] app/graph: support ethdev " skori
2023-10-19 10:49                               ` [PATCH v10 06/12] app/graph: support IPv4 lookup " skori
2023-10-19 10:49                               ` [PATCH v10 07/12] app/graph: support IPv6 " skori
2023-10-19 10:49                               ` [PATCH v10 08/12] app/graph: support neigh " skori
2023-10-19 10:49                               ` [PATCH v10 09/12] app/graph: support ethdev Rx " skori
2023-10-19 10:49                               ` [PATCH v10 10/12] app/graph: support graph " skori
2023-10-19 10:49                               ` [PATCH v10 11/12] app/graph: support CLI option to enable graph stats skori
2023-10-19 10:50                               ` [PATCH v10 12/12] app/graph: support l3fwd use case skori
2023-10-19 12:28                                 ` [EXT] " Jerin Jacob Kollanukkaran
2023-10-23  7:06                                   ` Nithin Dabilpuram
2023-10-19 17:29                                 ` [PATCH v11 00/12] add CLI based graph application skori
2023-10-19 17:30                                   ` [PATCH v11 01/12] app/graph: support application CLI framework skori
2023-10-23  7:03                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 02/12] app/graph: support telnet connectivity framework skori
2023-10-23  7:03                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 03/12] app/graph: support parser utility APIs skori
2023-10-23  7:03                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 04/12] app/graph: support mempool command line interfaces skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 05/12] app/graph: support ethdev " skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 06/12] app/graph: support IPv4 lookup " skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 07/12] app/graph: support IPv6 " skori
2023-10-23  7:04                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 08/12] app/graph: support neigh " skori
2023-10-23  7:05                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 09/12] app/graph: support ethdev Rx " skori
2023-10-23  7:05                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 10/12] app/graph: support graph " skori
2023-10-23  7:06                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 11/12] app/graph: support CLI option to enable graph stats skori
2023-10-23  7:06                                     ` Nithin Dabilpuram
2023-10-19 17:30                                   ` [PATCH v11 12/12] app/graph: support l3fwd use case skori
2023-10-23 13:03                                   ` [PATCH v11 00/12] add CLI based graph application Sunil Kumar Kori
2023-11-03 16:44                                   ` Thomas Monjalon
2023-09-29  9:58                       ` [PATCH v8 02/12] app/graph: add telnet connectivity framework skori
2023-10-16  9:04                         ` Jerin Jacob
2023-10-17  6:21                           ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 03/12] app/graph: add parser utility APIs skori
2023-10-16  9:05                         ` Jerin Jacob
2023-09-29  9:58                       ` [PATCH v8 04/12] app/graph: add mempool command line interfaces skori
2023-10-16  9:09                         ` Jerin Jacob
2023-10-17  6:22                           ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 05/12] app/graph: add ethdev " skori
2023-10-16 13:20                         ` Jerin Jacob
2023-10-16 14:10                           ` Bruce Richardson
2023-10-17  6:26                             ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 06/12] app/graph: add ipv4_lookup " skori
2023-10-16 15:47                         ` Jerin Jacob
2023-10-17  6:30                           ` [EXT] " Sunil Kumar Kori
2023-09-29  9:58                       ` [PATCH v8 07/12] app/graph: add ipv6_lookup " skori
2023-09-29  9:58                       ` [PATCH v8 08/12] app/graph: add neigh " skori
2023-09-29  9:58                       ` [PATCH v8 09/12] app/graph: add ethdev_rx " skori
2023-09-29  9:58                       ` [PATCH v8 10/12] app/graph: add graph " skori
2023-09-29  9:58                       ` [PATCH v8 11/12] app/graph: add CLI option to enable graph stats skori
2023-10-16 15:55                         ` Jerin Jacob
2023-09-29  9:58                       ` [PATCH v8 12/12] app/graph: add l3fwd use case skori
2023-10-16 16:01                       ` [PATCH v8 00/12] add CLI based graph application Jerin Jacob
2023-10-16 16:27                         ` Stephen Hemminger
2023-06-02 16:22   ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Attunuru
2023-06-02 16:22     ` [PATCH v3 1/3] node/kernel_tx: support packet transmit to kernel Vamsi Attunuru
2023-06-05 12:47       ` Nithin Dabilpuram
2023-06-12 19:32         ` Thomas Monjalon
2023-06-02 16:22     ` [PATCH v3 2/3] node/kernel_rx: support receiving packets from kernel Vamsi Attunuru
2023-06-05 12:50       ` Nithin Dabilpuram
2023-06-02 16:22     ` [PATCH v3 3/3] node/ethdev_rx: remove hardcoded node next details Vamsi Attunuru
2023-06-05 12:51       ` Nithin Dabilpuram
2023-06-12 16:12     ` [PATCH v3 0/3] node: Introduce kernel_rx & kernel_tx nodes Vamsi Krishna Attunuru
2023-06-12 19:31     ` 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=MW4PR18MB5244614BD3F23CE1734B498EA6439@MW4PR18MB5244.namprd18.prod.outlook.com \
    --to=vattunuru@marvell.com \
    --cc=cunming.liang@intel.com \
    --cc=dev@dpdk.org \
    --cc=haiyue.wang@intel.com \
    --cc=jerinj@marvell.com \
    --cc=ndabilpuram@marvell.com \
    --cc=skori@marvell.com \
    --cc=thomas@monjalon.net \
    --cc=zhirun.yan@intel.com \
    /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).